一、传统缓存方式
比如将可重复利用的东西放到Application或是Session中去保存。
Session["Count"] = 100;
Application["Count"] = 100;
二、页面输出缓存
页面输出缓存是最为简单的缓存机制,该机制将整个ASP.NET页面内容保存在服务器内存中。当用户请求该页面时,系统从内存中输出相关数据,直到缓存数据过期。在这个过程中,缓存内容直接发送给用户,而不必再次经过页面处理生命周期。通常情况下,页面输出缓存对于那些包含不需要经常修改内容,需要大量处理才能编译完成的页面特别有用。需要注意的是,页面输出缓存是将页面全部内容都保存在内存中,并用于完成客户端请求。
设置页面输出缓存可以使用以下两种方式:一种是使用@OutputCache指令,另一种是使用页面输出缓存API。页面输出缓存API主要是指HttpCachePolicy类。
@ OutputCache以声明的方式控制 ASP.NET 页或页中包含的用户控件的输出缓存策略。
语法如下:
<%@ OutputCache Duration="#ofseconds" Location="Any | Client | Downstream | Server | None | ServerAndClient " Shared="True | False" VaryByControl="controlname" VaryByCustom="browser | customstring" VaryByHeader="headers" VaryByParam="parametername" CacheProfile="cache profile name | ''" NoStore="true | false" SqlDependency="database/table name pair | CommandNotification" %>
Duration
页或用户控件进行缓存的时间(以秒计)。在页或用户控件上设置该属性为来自对象的 HTTP 响应建立了一个过期策略,并将自动缓存页或用户控件输出。
Location
用于指定输出缓存项的位置。其属性值是OutputCacheLocation枚举值,它们是Any、Client、Downstream、None、Server和ServerAndClient。默认值是Any,表示输出缓存可用于所有请求,包括客户端浏览器、代理服务器或处理请求的服务器上。需要注意的是,包含在用户控件中的@OutputCache指令不支持此属性。
Shared
一个布尔值,确定用户控件输出是否可以由多个页共享。默认值为 false。
NoStore
该属性定义一个布尔值,用于决定是否阻止敏感信息的二级存储。需要注意的是,包含在用户控件中的
@OutputCache指令不支持此属性。
SqlDependency
提供了这样一种能力:当被监测的数据库中的数据发生变化时,SqlDependency会自动触发OnChange事件来通知应用程序,从而达到让系统自动更新数据(或缓存)的目的。
VaryByControl
该属性使用一个分号分隔的字符串列表,来更改用户控件的输出缓存。这些字符串代表在用户控件中声明的ASP.NET服务器控件的ID属性值。除非已经包含了VaryByParam属性,否则在@ OutputCache指令中,该属性是必需的。
VaryByCustom
用于自定义输出缓存要求的任意文本。如果赋予该属性值是browser,缓存将随浏览器名称和主要版本信息的不同而异。如果输入了自定义字符串,则必须在应用程序的Global.asax文件中重写HttpApplication.GetVaryByCustomString方法。
VaryByHeader
该属性中包含由分号分隔的HTTP标头列表,用于使输出缓存发生变化。当将该属性设为多标头时,对于每个指定的标头,输出缓存都包含一个请求文档的不同版本。VaryByHeader属性在所有HTTP 1.1缓存中启用缓存项,而不仅限于ASP.NET缓存。用户控件中的@ OutputCache指令不支持此属性。
三、页面输出缓存API
Response类的Cache属性用于获取页面缓存策略,Cache属性的核心是调用System.Web.HttpCachePolicy。主要用于设置缓存特定的HTTP标头的方法和用于控制ASP.NET页面输出缓存的方法。由于HttpCachePolicy类方法众多,下面简要说明几个常用方法。
SetExpires
用于设置缓存过期的绝对时间。它的参数是一个DataTime类的实例,表示过期的绝对时间。
SetLastModified
用于设置页面的Last-Modified HTTP标头。Last-Modified HTTP标头表示页面上次修改时间,缓存将依靠它来进行计时。如果违反了缓存限制层次结构,此方法将失败。该方法的参数是一个DataTime类的实例。
SetSlidingExpiration
该方法将缓存过期从绝对时间设置为可调时间。其参数是一个布尔值。当参数为true时,Cache-Control HTTP标头将随每个响应而更新。当参数为false时,将保留该设置,且任何启用可调整过期的尝试都将静态失败。此方法不直接映射到HTTP标头,它由后续模块或辅助请求来设置源服务器缓存策略。
SetCacheability
用于设置页面的Cache-Control HTTP标头。该标头用于控制在网络上缓存文档的方式。该方法有两种重载方式,所不同的是参数。一种重载方法的参数是HttpCacheability枚举值,包括NoCache、Private、Public、Server、ServerAndNoCache和ServerAndPrivate。另一种方法的参数有两个,一个参数是HttpCacheability枚举值,另一个参数是字符串,表示添加到标头的缓存控制扩展。需要注意的是,仅当与Private或NoCache指令一起使用时,字段扩展名才有效。如果组合不兼容的指令和扩展,则此方法将引发无效参数异常。
Response.Cache.SetExpires(DateTime.Now.AddSeconds(60));
Response.Cache.SetExpires(DateTime.Parse("6:00:00PM"));
Response类的Cache属性用于获取页面缓存策略。该属性的数据类型是HttpCachePolicy。可通过调用Response.Cache来获取HttpCachePolicy实例,进而实现对于当前页面输出缓存的设置。如上代码所示,第一行代码表示输出缓存时间是60秒,并且页面不随任何GET或POST参数改变,等同于“<%@ OutputCache Duration="60" VaryByParam="none" %>”。第二行代码设置缓存过期的绝对时间是当日下午6时整。
四、页面部分缓存
有时缓存整个页面是不现实的,因为页的某些部分可能在每次请求时都需要变化。在这些情况下,只能缓存页的一部分。顾名思义,页面部分缓存是将页面部分内容保存在内存中以便响应用户请求,而页面其他部分内容则为动态内容。在页面中经常存在某些部分固定不变(如导航栏,页面右侧的排行榜等信息),而其他内容则需要根据用户请求的不同动态生成。所以我们可以将固定部分的内容缓存在服务器内存中,以减少服务器查询数据时间。
页面部分缓存也称为控件缓存,这种方式允许将需要缓存的信息包含在一个用户控件内。然后,将该用户控件标记为可缓存的,以此来缓存页面输出的部分内容。这种方式允许缓存页面中的特定内容,而不缓存整个页面。例如,如果要创建一个显示大量动态内容(如股票信息)的页,其中有些部分为静态内容(如每周总结),这时可以将静态部分放在用户控件中,并允许缓存这些内容。
页面部分缓存示例如下:
在CacheControl.ascx的页头代码中添加声明语句:
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="CacheControl.ascx.cs" Inherits="CacheControl" %>
<%@ OutputCache Duration="60" VaryByParam="none" %>
<%=DateTime.Now %>
调用该控件的页面代码:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="CacheDemo.aspx.cs"%> <%@ Register src="CacheControl.ascx" tagname="CacheControl" tagprefix="uc" %> <head runat="server"> <title>CacheDemo</title> </head> <body> <form id="form1" runat="server"> 页面的时间:<%=DateTime.Now %> <br/> 控件的时间:<uc:CacheControl ID="CacheControl" runat="server" /><br/> </form> </body> </html>
当刷新页面时,“页面的时间”每次刷新都变化,而“控件的时间”的数据却是60秒才变化一次。说明对页面的“局部”控件实现了缓存,而页面其它部分不受影响。
五、 应用程序数据缓存
应用程序数据缓存提供了一种编程方式,可通过键/值对将任意数据存储在内存中。使用应用程序缓存与使用应用程序状态类似。但是,与应用程序状态不同的是,应用程序数据缓存中的数据是易失的,即数据并不是在整个应用程序生命周期中都存储在内存中。应用程序数据缓存的优点是由ASP.NET管理缓存,它会在项过期、无效,或内存不足时移除缓存中的项。还可以配置应用程序缓存,以便在移除项时通知应用程序。
如下所示插入一个字符串进缓存:
Cache["name"]="CacheDemo";
这个存储的字串值可以像这样得到:
if (Cache["name"] != null) { string name = Cache["name"].ToString(); }
六、 缓存依赖
前文的几种方式都可以实现数据缓存功能,但问题是数据有时候是在变化的。这样用户可能在缓存期间查询的数据就是过期的数据,从而导致数据的不一致。那有没有办法在数据变化时,系统能自动更新缓存中的数据,从而让用户得到实时有效的数据。
.NET已经为我们提供了这样一种非常好的解决方法:SqlCacheDependency数据库缓存依赖。下面就让我们看一下如何实现数据库缓存依赖功能:
1. 配置SqlCacheDependency
修改web.config,让项目启用SqlCacheDependency。
<?xml version="1.0"?> <configuration> <appSettings/> <connectionStrings> <add name="strcodematic" connectionString="data source=dbserver;initial catalog=codematic;user id=sa;password=sa" providerName="System.Data.SqlClient" /> </connectionStrings> <system.web> <caching> <sqlCacheDependency enabled="true" pollTime="6000"> <databases> <add name="codematic" connectionStringName="strcodematic" /> </databases> </sqlCacheDependency> </caching> <compilation debug="true"> </compilation> <authentication mode="Windows"/> </system.web> </configuration>
注意:在<databases>节的<add name="codematic" connectionStringName="strcodematic" />中的name属性值必须和第三步的Page_Load代码中System.Web.Caching.SqlCacheDependency("codematic", "P_Product"); 中的第一个参数(数据库名称)相一致。
这里的connectionStringName指定了在<connectionStrings>中添加的某一个连接字符串。name则是为该SqlCacheDependency起的名字,这个名字将在第三步中用到。
SqlCacheDependency类会自动完成对此配置节信息的读取,以建立和数据库之间的联系。
2.启用缓存依赖
如果要启用SqlCacheDependency,则需要以命令行的方式执行。
aspnet_regsql.exe工具位于Windows\\Microsoft.NET\\Framework\\[版本]文件夹中。
aspnet_regsql -C "data source=.;initial catalog=codematic;user id=sa;password=123456" -ed -et -t "BookInfo"
参数-C后面的字符串是连接字符串(请替换成自己所需要的值),参数-t后面的字符串是数据表的名字。
运行结果如图所示:
命令执行后,在指定的数据库中会多出一个AspNet_SqlCacheTablesForChangeNotification表。
要使得7.0或者2000版本以上的SQLServer支持SqlCacheDependency特性,需要对数据库服务器执行相关的配置。
有两种方法配置SQLServer:
一 使用aspnet_regsql命令行工具。
二 使用SqlCacheDependencyAdmin类。(msdn看到的,没用过)
例如:
aspnet_regsql -S "server" -E -d "database" –ed
或者
aspnet_regsql -S "server" -E -d "database" -et -t "table"
如果是Sql验证的话要把-E换成,-U (用户名),-P (密码)
以下是该工具的命令参数说明:
-? 显示该工具的帮助功能;
-S 后接的参数为数据库服务器的名称或者IP地址;
-U 后接的参数为数据库的登陆用户名;
-P 后接的参数为数据库的登陆密码;
-E 使用当前登录用户的 Windows 集成认证进行身份验证;
-d 后接参数为对哪一个数据库采用SqlCacheDependency功能;
-C 连接数据库的连接字符串。如果您指定服务器(-S)和登录(-U和-P,或 -E)信息,则此选项不是必需的,因为连接字符串已经包含这些信息;
-t 后接参数为对哪一个表采用SqlCacheDependency功能;
-ed 允许对数据库使用SqlCacheDependency功能;
-dd 禁止对数据库采用SqlCacheDependency功能;
-et 允许对数据表采用SqlCacheDependency功能;
-dt 禁止对数据表采用SqlCacheDependency功能;
-lt 列出当前数据库中有哪些表已经采用sqlcachedependency功能;
3.0使用SqlCacheDependency
在代码中使用缓存,并为其设置SqlCacheDependency依赖:
/// <summary> /// 获取当前应用程序指定CacheKey的Cache对象值 /// </summary> /// <param name="CacheKey">索引键值</param> /// <returns>返回缓存对象</returns> public static object GetCache(string CacheKey) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; return objCache[CacheKey]; } /// <summary> /// 设置以缓存依赖的方式缓存数据 /// </summary> /// <param name="CacheKey">索引键值</param> /// <param name="objObject">缓存对象</param> /// <param name="cacheDepen">依赖对象</param> public static void SetCache(string CacheKey, object objObject, System.Web.Caching.CacheDependency dep) { System.Web.Caching.Cache objCache = HttpRuntime.Cache; objCache.Insert( CacheKey, objObject, dep, System.Web.Caching.Cache.NoAbsoluteExpiration,//从不过期 System.Web.Caching.Cache.NoSlidingExpiration,//禁用可调过期 System.Web.Caching.CacheItemPriority.Default, null); } protected void Page_Load(object sender, EventArgs e) { string CacheKey = "cachetest"; object objModel = GetCache(CacheKey);//从缓存中获取 if (objModel == null)//缓存里没有 { objModel = GetData();//把当前时间进行缓存 if (objModel != null) { //依赖数据库codematic中的P_Product表变化 来更新缓存 System.Web.Caching.SqlCacheDependency dep = new System.Web.Caching.SqlCacheDependency("CodeMatic", "P_Product"); SetCache(CacheKey, objModel, dep);//写入缓存 } } GridView1.DataSource = (DataSet)objModel; GridView1.DataBind(); }
从以上代码可以看出,和文件依赖基本相同,只是在存放缓存SetCache时存入的依赖对象不同。这里用的是SqlCacheDependency。
其中,创建SqlCacheDependency的构造方法:
public SqlCacheDependency (string databaseEntryName,string tableName) //databaseEntryName:是在Web.config文件的caching节的sqlCacheDependency的databases元素中定义的数据库的名称。 //tableName:与SqlCacheDependency关联的数据库表的名称。 //这样,只有当P_Product表的内容发生变化时,查询操作才会重新查询数据更新缓存的内容,可以大大减少数据库的重复查询和提高系统的性能和运行效率。
七、缓存管理类
首先,缓存管理类外部代码定义了各种过期时间的常量,主要用于方便设定缓存的过期时间
/// <summary> /// 全站缓存类 /// </summary> public sealed class SiteCache { // 一天 public const int DayFactor = 17280; // 一小时 public const int HourFactor = 720; // 一分钟 public const int MinuteFactor = 12; // 一秒 public const double SecondFactor = 0.2; private static int factor = 5; /// <summary> /// 时间因子 /// </summary> public static int Factor { get { return factor; } set { factor = value; } } public static Cache CurrentCache { get { return HttpRuntime.Cache; } } }
//Clear方法用于移除所有存储在内存当中的缓存,代码中cacheEnum变量通过GetEnumerator方法获得所有缓存列表,再根据缓存列表来移除缓存。 public static void Clear() { //获取缓存中的键设置及其值的字典枚举数 IDictionaryEnumerator cacheEnum = siteCache.GetEnumerator(); ArrayList al = new ArrayList(); //循环添加到数组列表 while (cacheEnum.MoveNext()) { al.Add(cacheEnum.Key); } //循环从缓存中移除 foreach (string key in al) { siteCache.Remove(key); } }
//RemoveByPattern方法是通过正则移除对应的缓存。 public static void RemoveByPattern(string pattern) { IDictionaryEnumerator cacheEnum = siteCache.GetEnumerator(); Regex regex = new Regex(pattern, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled); ArrayList al = new ArrayList(); //循环从缓存中取出键进行正则条件匹配 while (cacheEnum.MoveNext()) { //匹配正确则添加到数组列表 if (regex.IsMatch(cacheEnum.Key.ToString())) { al.Add(cacheEnum.Key); } } //移除匹配正则的缓存 foreach (string key in al) { siteCache.Remove(key); } }
//Insert方法是通过Cache的插入方法而实现的,主要能够把缓存键、缓存对象、缓存依赖、缓存过期时间、缓存级别这些缓存项插入到内存中。 /// <summary> /// 插入缓存项 /// </summary> public static void Insert(string key, object value, System.Web.Caching.CacheDependency dep, int seconds, CacheItemPriority priority) { if (value != null) { siteCache.Insert(key, value, dep, DateTime.Now.AddSeconds(Factor * seconds), TimeSpan.Zero, priority, null); } }
//Get方法通过指定的键从Cache中获取缓存 public static object Get(string key) { return siteCache[key]; } }
八、缓存的应用规则
缓存分配的有效期不要太短。
缓存那些经常被访问,同时变化频率不大的数据。
缓存整个应用程序都要使用的设置或对象,并且这些设置和对象在生存期内不会频繁变化。
不要缓存太多项,缓存每个项均有开销。
不要缓存很少使用的项。
不要缓存容易重新计算或随时都可能会修改的对象,如购物车。
不要缓存敏感信息,否则其他人很容易取得这些信息。
比如:可以缓存网站信息配置
GetConfig是一个泛型方法,网站信息配置里的所有功能属性都是通过它来获得对象。方法后面跟着一个where子句,用于指定对类型的约束。其中class限制了类型参数必须是引用类型, new()限制了该类型参数必须具有无参数的公共构造函数。
方法内首先获取了T变量的类型。然后根据类型名拼装成configCacheKey缓存键,通过SiteCache类的Get方法获取缓存的值并存储到configObject变量中。接着判断该缓存是否不存在值,如果不存在,使用GetConfigPath方法获得了T变量的物理文件地址。
public static T GetConfig<T>() where T : class, new() { Type configClassType = typeof(T); string configCacheKey = "CK_SiteConfigCode_" + configClassType.Name; object configObject = SiteCache.Get(configCacheKey); if (configObject == null) { string configFilePath = GetConfigPath<T>(); //下面检查T变量的物理文件地址是否存在,如果存在即读取T变量的配置信息,并将信息插入到缓存中。其中Insert方法使用了缓存依赖CacheDependency实例监视文件或目录路径,当该资源更改时,缓存的对象将过时,从缓存中移除。 if (File.Exists(configFilePath)) { using (XmlTextReader xmlTextReader = new XmlTextReader(configFilePath)) { XmlSerializer xmlSerializer = new XmlSerializer(configClassType); configObject = xmlSerializer.Deserialize(xmlTextReader); } SiteCache.Insert(configCacheKey, configObject, new CacheDependency(configFilePath)); } } //最后,把缓存对象转换为T泛型变量。判断是否为空,如果是则创建新的T实例返回,否则将原有值返回。 T config = configObject as T; if (config == null) { return new T(); } else { return config; } }