(一)什么是缓存
缓存是指临时数据或者文件交换区。比方说CPU上的L1或是L2缓存,内存上被划分出来的缓冲区。我们知道CPU的速度是最快的,其次是内存,最后是硬盘,各个部件之间运算速度有很大的差距,但是各个部件之间又需要交互,由于部件之间运算速度差距大,若是CPU频繁的去访问内存,或者内存频繁的访问硬盘,势必很消耗性能并且效率也很低。若是他们能访问跟自己运算速度差不多的区域,如有必要再由该区域去访问比自己速度更慢的区域(如内存或是硬盘),则能带来更高的性能提升。
(二)为什么Web应用程序需要缓存
这主要是为了减轻Web服务器压力,在客户端提供了缓存机制,当用户访问的网页内容无变化的请求时就会调用缓存中的内容,这样一来减轻了服务器压力,避免无必要的重复操作,使用户网页浏览速度加快,用户体验更好。
(三)缓存依赖
被缓存的文件或者页面发生变化时,cache将会失效。
下面具体介绍各种缓存应用。
(四)WebConfig中配置Cache
在webconfig中caching节点下可以设置整个应用程序是否启用缓存,它优先于页面上的缓存配置,是对整个Web应用程序的整体设置。
1 <system.web>
2 <caching>
3 <outputCache enableOutputCache="false"/>
4 </caching>
5 </system.web>
(五)页面中的Cache
在页面的顶部设置如下代码:
<%@ OutputCache Duration="5" VaryByParam="none" %>
即可对整个页面进行缓存,下面对其中的参数做个简单的说明。
-
Duration,以秒为单位的缓存时间。
-
VaryByParam,缓存的内容根据请求参数的不同,缓存多个版本,若有多个参数用分号(;)分割。
-
VarByControl,根据用户控件中服务器控件ID缓存多个版本。
-
VaryByHeader,根据请求的HTTP头,缓存不同的版本。
下面来看两个例子。
例5.1
页面前台代码:
1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="PageCache.aspx.cs" Inherits="PageCache" %>
2 <%@ OutputCache Duration="5" VaryByParam="none" %>
3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4 <html xmlns="http://www.w3.org/1999/xhtml">
5 <head runat="server">
6 <title></title>
7 </head>
8 <body>
9 <form id="form1" runat="server">
10 <div>
11 <p>本页面时间:<%=DateTime.Now.ToString() %></p>
12 </div>
13 </form>
14 </body>
15 </html>
后台不书写任何的代码,我们可以看到该页面被缓存了5秒。
例5.2
页面前台代码:
1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="CacheProfiles.aspx.cs" Inherits="CacheProfiles" %>
2 <%@ OutputCache CacheProfile="CacheProfiles" VaryByParam="none"%>
3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4 <html xmlns="http://www.w3.org/1999/xhtml">
5 <head runat="server">
6 <title></title>
7 </head>
8 <body>
9 <form id="form1" runat="server">
10 <div>
11 <%= DateTime.Now.ToString() %>
12 </div>
13 </form>
14 </body>
15 </html>
在WebConfig文件中配置CacheProfiles,如下:
1 <system.web>
2 <caching>
3 <outputCacheSettings>
4 <outputCacheProfiles>
5 <add name="CacheProfiles" duration="10"/>
6 </outputCacheProfiles>
7 </outputCacheSettings>
8 </caching>
9 </system.web>
后台不书写任何代码,我们看到这次页面被缓存了10秒,采用这种方式可以单独对某个页面进行缓存的配置,也可以对一些页面进行统一配置,如果要修改只需修改配置文件即可。
(六)页面局部Cache
页面局部缓存分为两种,一种是对页面较少的部分进行缓存,我们可以用用户控件来实现;另一种是对页面的大部分缓存,只有少部分需要动态改变,我们可以用缓存后替换。下面分别介绍这两种方式。
6.1用户控件缓存
我们先来创建一个简单的用户控件ascx文件,前台代码如下:
1 <%@ Control Language="C#" AutoEventWireup="true" CodeFile="WebUserControl.ascx.cs" Inherits="WebUserControl" %>
2 <%@ OutputCache Duration="10" VaryByParam="none" %>
3 <span>
4 用户控件时间:<%=DateTime.Now.ToString() %>
5 </span>
没有后台代码,其中设置了过期时间为10秒。我们再来创建一个页面,前台代码如下:
1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="PartCache.aspx.cs" Inherits="PartCache" %>
2 <%@ Register Src="~/WebUserControl.ascx" tagname="UserControl" TagPrefix="MyControl" %>
3 <%@ OutputCache Duration="20" VaryByParam="none" %>
4 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
5 <html xmlns="http://www.w3.org/1999/xhtml">
6 <head runat="server">
7 <title></title>
8 </head>
9 <body>
10 <form id="form1" runat="server">
11 <p>本页面时间:<%=DateTime.Now.ToString() %></p>
12 <div>
13 <MyControl:UserControl ID="c" runat="server" />
14 </div>
15 </form>
16 </body>
17 </html>
没有后台代码,页面中缓存过期时间设置为20秒,把用户控件拖拽到该页面中。这里面有个规则,如果页面的缓存时间大于控件的缓存时间,则按照页面的缓存时间为主,与用户控件的缓存时间无关;如果页面的缓存时间小于用户控件的缓存时间,则各走个的缓存时间。
6.2缓存后替换
有两个控件支持该功能一个是Substitution控件,另一个是AdRotator控件,这里只介绍前者。前台代码如下:
1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="PartCacheV2.aspx.cs" Inherits="PartCacheV2" %>
2 <%@ OutputCache Duration="10" VaryByParam="none" %>
3 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
4 <html xmlns="http://www.w3.org/1999/xhtml">
5 <head runat="server">
6 <title></title>
7 </head>
8 <body>
9 <form id="form1" runat="server">
10 <div>
11 <asp:Substitution ID="Substitution1" runat="server" MethodName="GetTime" />
12 </div>
13 </form>
14 </body>
15 </html>
代码中设置了页面的缓存时间为10秒。后台代码:
1 public static string GetTime(HttpContext context)
2 {
3 return DateTime.Now.ToString();
4 }
通过刷新页面我们可以看到Substitution控件中的时间一直在变化,不受页面的缓存控制。
(七)基于文件的Cache
下面的两个例子都是把文件缓存了起来,第一个是缓存单个文件,后面一个是缓存多个文件,它们分别使用了不同的方式达到了同一个效果,当被缓存的文件变化或者缓存到期时,Cache将会失效。
例7.1
首先,在网站的根目录下创建一个TextFile.txt的文本文件,随意在文件中添加些内容。然后创建页面,添加前台代码:
1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="CacheFile.aspx.cs" Inherits="_Default" %>
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head runat="server">
5 <title></title>
6 </head>
7 <body>
8 <form id="form1" runat="server">
9 <div>
10 <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
11 </div>
12 </form>
13 </body>
14 </html>
添加后台代码:
1 using System.Web.Caching;
2 protected void Page_Load(object sender, EventArgs e)
3 {
4 string info = HttpRuntime.Cache["Info"] as string;
5 if (info == null)
6 {
7 //得到网站目录下的文本文件路径
8 string path = Server.MapPath("~/TextFile.txt");
9 info = System.IO.File.ReadAllText(path) + DateTime.Now.ToString();
10 //把文本文件的内容加上时间加到缓存中
11 HttpRuntime.Cache.Add("Info", info, new System.Web.Caching.CacheDependency(path),
12 System.Web.Caching.Cache.NoAbsoluteExpiration,
13 new TimeSpan(0, 0, 5), //过期策略设置为5秒
14 System.Web.Caching.CacheItemPriority.Normal,
15 null);//缓存失效时的回调函数
16 /*这里也可以使用Insert方法, 如果使用 Insert 方法向缓存添加项,并且已经存在与现有项同名的项,则缓存中的现有项将被替换。
17 而Add 方法将返回添加到缓存中的对象。另外,如果使用 Add 方法,并且缓存中已经存在与现有项同名的项,则该方法不会替换该项,并且不会引发异常。
18 HttpRuntime.Cache.Insert("Info",info,new System.Web.Caching.CacheDependency(path),
19 System.Web.Caching.Cache.NoAbsoluteExpiration,
20 new TimeSpan(0,0,5),
21 System.Web.Caching.CacheItemPriority.Normal,
22 Callback);
23 */
24 }
25 //把信息输出到label标签中
26 Label1.Text = info;
27 }
28
29 private static void Callback(string key, object value, System.Web.Caching.CacheItemRemovedReason reason)
30 {
31 //缓存过期时的通知函数,缓存失效时添加相关操作
32 }
例7.2
我们再在网站的根目录下创建一个XMLFile.xml的xml文件,随意在文件中添加些内容。然后创建页面,前台代码:
1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="CacheFileV2.aspx.cs" Inherits="CacheFile" %>
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head runat="server">
5 <title></title>
6 </head>
7 <body>
8 <form id="form1" runat="server">
9 <div>
10 <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
11 </div>
12 </form>
13 </body>
14 </html>
后台代码:
1 protected void Page_Load(object sender, EventArgs e)
2 {
3 string[] fileDependencies;
4 string fileDependency1 = Server.MapPath("TextFile.txt");
5 string fileDependency2 = Server.MapPath("XMLFile.xml");
6 fileDependencies = new String[] { fileDependency1,
7 fileDependency2 };
8 Response.AddFileDependencies(fileDependencies);
9 //Response.AddFileDependency("");//如果是单个文件,可使用该函数
10
11 Response.Cache.SetExpires(DateTime.Now.AddSeconds(5));
12 Response.Cache.SetCacheability(HttpCacheability.Public);
13 Response.Cache.SetValidUntilExpires(true);
14
15 //测试用,文件改变或者缓存到期,再次请求时,时间就会变化
16 Label1.Text = DateTime.Now.ToString();
17 }
这个例子关联了两个文件,其中任何一个改变都会引起缓存的失效。
以上这两个例子都实现了文件的缓存,但是有一个显著的不同。对于第一个例子,在缓存到期之前如果刷新了页面则缓存时间累加,也就是说页面不会更改,必须在缓存时间内没有刷新页面,过了缓存时间,缓存才会失效。对于第二个例子,不管在缓存时间内如何刷新页面,只要缓存到期,那么页面就会失效。
(八)基于sql的Cache
基于sql的缓存分为两种,一种叫做轮询,一种叫做数据库通知。
- 轮询:数据库不能通知的时候,应用程序可以主动定期访问数据库,检查数据库是否发生变化。
- 数据库通知:数据库中的数据发生变化时,主动通知应用程序(该功能只支持sql2005以及以上版本)。这里只介绍前一种。
首先我们新建一个数据库名为Student,并且创建一张STName的表,该表中有两个字段ID和Name,ID为自增行。
然后,在WebConfig中配置相关设置。
先配置缓存相关设置,其中pollTime为轮询间隔单位为毫秒,即多长时间访问一次数据库。
1 <system.web>
2 <caching>
3 <sqlCacheDependency enabled="true" pollTime="5000">
4 <databases>
5 <add connectionStringName="StudentConnectionString" name="Student"/>
6 </databases>
7 </sqlCacheDependency>
8 </caching>
9 </system.web>
再添加连接字符串。
1 <connectionStrings>
2 <add name="StudentConnectionString" connectionString="Data Source=.;Initial Catalog=Student;integrated security=true;" providerName="System.Data.SqlClient"/>
3 </connectionStrings>
前台代码:
1 <%@ Page Language="C#" AutoEventWireup="true" CodeFile="SqlCache.aspx.cs" Inherits="SqlCache" %>
2 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3 <html xmlns="http://www.w3.org/1999/xhtml">
4 <head runat="server">
5 <title></title>
6 </head>
7 <body>
8 <form id="form1" runat="server">
9 <div>
10 <asp:GridView ID="GridView1" runat="server">
11 </asp:GridView>
12 <asp:Label ID="Label1" runat="server" Text="Label"></asp:Label>
13 </div>
14 <asp:Button ID="Button1" runat="server" onclick="Button1_Click" Text="Button" />
15 </form>
16 </body>
17 </html>
后台代码:
1 using System.Data;
2 using System.Data.SqlClient;
3 using System.Configuration;
4 using System.Web.Caching;
5
6 protected void Page_Load(object sender, EventArgs e)
7 {
8 //判断缓存中是否存在STName
9 if (HttpRuntime.Cache["STName"] == null)
10 {
11 this.GridView1.DataSource = BindData();
12 this.GridView1.DataBind();
13
14 //启用更改通知
15 SqlCacheDependencyAdmin.EnableNotifications(ConfigurationManager.ConnectionStrings["StudentConnectionString"].ConnectionString);
16 //连接到 SQL Server 数据库并为 SqlCacheDependency 更改通知准备数据库表
17 SqlCacheDependencyAdmin.EnableTableForNotifications(ConfigurationManager.ConnectionStrings["StudentConnectionString"].ConnectionString, "STName");
18 //声明SqlCacheDependency其中构造函数中的这两个参数(Student必需与WebConfig配置的sqlCacheDependency的一致,STName则是缓存的key)
19 System.Web.Caching.SqlCacheDependency dep = new System.Web.Caching.SqlCacheDependency("Student", "STName");
20 //向缓存集合中插入数据
21 HttpRuntime.Cache.Insert("STName", DateTime.Now.ToString(),
22 dep,
23 System.Web.Caching.Cache.NoAbsoluteExpiration,
24 System.Web.Caching.Cache.NoSlidingExpiration,
25 System.Web.Caching.CacheItemPriority.Default,
26 null);
27 //显示缓存STName中的数据
28 string str = HttpRuntime.Cache["STName"] as string;
29 if (str != null)
30 {
31 this.Label1.Text = str;
32 }
33 }
34 else//存在则把缓存数据显示到页面
35 {
36 this.GridView1.DataSource = BindData();
37 this.GridView1.DataBind();
38 string str = HttpRuntime.Cache["STName"] as string;
39 if (str != null)
40 {
41 this.Label1.Text = str;
42 }
43 }
44 }
45
46 protected void Button1_Click(object sender, EventArgs e)
47 {
48 //更新数据库表中的数据
49 UpdateData();
50 string str = HttpRuntime.Cache["STName"] as string;
51 if (str != null)
52 {
53 this.Label1.Text = str;
54 }
55 this.GridView1.DataSource = BindData();
56 this.GridView1.DataBind();
57 }
58
59
60 private SqlConnection returnConnection()
61 {
62 string connstring = System.Configuration.ConfigurationManager.ConnectionStrings["StudentConnectionString"].ConnectionString;
63 SqlConnection conn = new SqlConnection(connstring);
64 return conn;
65 }
66
67 private DataSet BindData()
68 {
69 /* 读出表中的数据*/
70 SqlCommand cmd = new SqlCommand("SELECT * FROM STName", returnConnection());
71 SqlDataAdapter sda = new SqlDataAdapter(cmd);
72 DataSet ds = new DataSet();
73 sda.Fill(ds);
74 return ds;
75 }
76
77 private void UpdateData()
78 {
79 //更新数据
80 SqlConnection con = returnConnection();
81 SqlCommand cmd = new SqlCommand("Update STName set Name='cc'", con);
82 con.Open();
83 cmd.ExecuteNonQuery();
84 con.Close();
85 }
运行后界面:
点击按钮时,更新了Name字段中的值,一个轮询周期内,可以看到时间变了一次。
对于这个例子还有些话要说。当程序执行完上面代码中的SqlCacheDependencyAdmin.EnableTableForNotifications(ConfigurationManager.ConnectionStrings["StudentConnectionString"].ConnectionString, "STName");之后,我们就在数据库中新创建了一张表,如下图:
该表包括了设置表的要监控的字段,还有表是否发生变化的标识,就相当于一个触发器。
另外,代码中对于HttpRuntime.Cache["STName"]的处理,这里没有直接使用像HttpRuntime.Cache["STName"].ToString()代码,对于缓存来说,有可能有外界的原因导致它失效,如果说失效了那HttpRuntime.Cache["STName"]就为空,再访问它的ToString方法就会引发异常。
除了可以对单表进行缓存,也可以对多张表进行缓存,这就会用到AggregateCacheDependency对象,实现方法并不复杂,这里就不再说明。
(九)清除页面缓存
附带一个清除页面缓存的代码,如下:
1 protected void Page_Load(object sender, EventArgs e)
2 {
3 Response.Buffer = true;
4 Response.ExpiresAbsolute = DateTime.Now.AddSeconds(-1);
5 Response.Expires = 0;
6 Response.CacheControl = "no-cache";
7 Response.AddHeader("Pragma", "No-Cache");
8 }