ASP.NET MVC 缓存控制
缓存(cache)
1.缓存是一种以空间换时间的技术, 比如, cpU的二级缓存,Windows的文件缓存。
2.减少服务器的负荷,默认存放在内存里面,不过是可以修改的。
3.缓存存在失效的情况。Asp.net 缓存主要分为页面缓存,数据源缓存,数据缓存。
4. 页面缓存: %@OutPutCache Duration="15" VaryByParam="none" %
属性说明:
VaryByParam:是指页面根据使用 POST 或 GET 发送的名称/值对(参数)来更新缓存的内容,多个参数用分号隔开。如果不希望根据任何参数来改变缓存内容,请将值设置为 none。如果希望通过所有的参数值改变都更新缓存,请将属性设置为星号 (*)。
例如: http://localhost:1165/16-4-3/WebForm1.aspx?p=1
则可以在WebForm1.aspx页面头部声明缓存:<%@ OutputCache Duration="60" VaryByParam="p" %>
以上代码设置页面缓存时间是60秒,并根据p参数的值来更新缓存,即p的值发生变化才更新缓存。
如果一直是WebForm1.aspx?p=1访问该页,则页面会缓存当前数据,当p=2时又会执行后台代码更新缓存内容。
如果有多个参数时,如:http://localhost:1165/16-4-3/WebForm1.aspx?p=1&n=1
可以这样声明:<%@ OutputCache Duration="60" VaryByParam="p;n" %>
CacheProfile:用于调用Web.config配置文件中设置的缓存时间。这是可选属性,默认值为空字符("")。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using MvcApplicationCacheSample.Models; using System.Web.UI; namespace MvcApplicationCacheSample.Controllers { public class HomeController : Controller { // // GET: /Home/ [OutputCache(CacheProfile="employee")] public ActionResult Index() { Response.Cache.SetOmitVaryStar(true); ViewBag.CurrentTime = DateTime.Now.ToString(); //这里目前作为演示,是直接硬编码,实际上可能是读取数据库的数据 var employees = new[]{ new Employee(){ID=1,Name="ares",Gender="Male"} }; return View(employees); } } }
例如在Web.config中加入配置:
<system.web> <caching> <outputCacheSettings> <outputCacheProfiles> <add name="employee" duration="10" enabled="true" location="ServerAndClient" varyByParam="none"/> </outputCacheProfiles> </outputCacheSettings> </caching> </system.web>
页面中声明:
注意:包含在用户控件(.ascx 文件)中的 @ OutputCache 指令不支持此属性。在页中指定此属性时,属性值必须与 outputCacheSettings 节下面的 outputCacheProfiles 元素中的一个可用项的名称匹配。如果此名称与配置文件项不匹配,将引发异常。
如果每个页面的缓存时间相同,则不需要每个页面设置,而是通过统一一个地方控制,这样就可以更好的统一控制所有页面的缓存时间。如果想改变缓存时间,只需要改一下web.config的配置信息即可,而不用每个页面去修改。
VaryByControl:通过用户控件文件中包含的服务器控件来改变缓存(值是控件ID,多控件用分号隔开)。
在 ASP.NET 页和用户控件上使用 @ OutputCache 指令时,需要该属性或 VaryByParam 属性。
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="WebForm2.aspx.cs" Inherits="CacheWebApp._16_4_3.WebForm2" %> <%@ OutputCache Duration="60" VaryByParam="none" VaryByControl="DropDownList1" %> <html xmlns="http://www.w3.org/1999/xhtml" > <head runat="server"> <title>根据控件页面缓存</title> </head> <body> <form id="form1" runat="server"> <div> <%=DateTime.Now %> <br> <asp:DropDownList ID="DropDownList1" runat="server"> <asp:ListItem>beijing</asp:ListItem> <asp:ListItem>shanghai</asp:ListItem> <asp:ListItem>guangzhou</asp:ListItem> </asp:DropDownList> <asp:Button ID="Button1" runat="server" Text="提交" /> </div> </form> </body> </html>
以上代码设置缓存有效期是60秒,并且页面不随任何GET或POST参数改变(即使不使用VaryByParam属性,但是仍然需要在@ OutputControl指令中显式声明该属性)。如果用户控件中包含ID属性为“DropDownList1”的服务器控件(例如下拉框控件),那么缓存将根据该控件的变化来更新页面数据。
VaryByHeader:可以根据用户请求中所提供的一些Header信息不同而决定是否读取缓存。我们可以看到在每个请求中都会包含一些Header信息。
例如根据不同的语言,我们显然是有不同的版本的。或者根据用户浏览器不同,也可以缓存不同的版本。可以通过这样设置
VaryByHeader=”Accept-Language,User-Agent”
VaryByContentEncoding:一般设置为Accept-Encoding里面可能的Encoding名称,从上图也可以看出,Request里面是包含这个标头的。
VaryByCustom:则是一个完全可以定制的设置,例如我们可能需要根据用户角色来决定不同的缓存版本,或者根据浏览器的一些小版本号来区分不同的缓存版本,我们可以这样设置:VaryByCustom=”Role,BrowserVersion”,这些名称是你自己定义的,光这样写当然是没有用的,我们还需要在Global.asax文件中,添加一个特殊的方法,来针对这种特殊的需求进行处理。
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Routing; using System.Web.Security; namespace MvcApplicationCacheSample { // Note: For instructions on enabling IIS6 or IIS7 classic mode, // visit http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { public static void RegisterGlobalFilters(GlobalFilterCollection filters) { filters.Add(new HandleErrorAttribute()); } public static void RegisterRoutes(RouteCollection routes) { routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); routes.MapRoute( "Default", // Route name "{controller}/{action}/{id}", // URL with parameters new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults ); } protected void Application_Start() { AreaRegistration.RegisterAllAreas(); RegisterGlobalFilters(GlobalFilters.Filters); RegisterRoutes(RouteTable.Routes); } public override string GetVaryByCustomString(HttpContext context, string custom) { switch(custom) { case "Role": { return string.Join(",", Roles.GetRolesForUser()); } case "BrowserVersion": { return context.Request.Browser.Type; } default: break; } return string.Empty; } } }
页面输出缓存API
Response类的Cache属性用于获取页面缓存策略。该方式的核心是调用System.Web.HttpCachePolicy。该类主要包含用于设置缓存特定的HTTP标头的方法和用于控制ASP.NET页面输出缓存的方法。与.NET Framework 1.x中的HttpCachePolicy类相比,.NET Framework 2.0中的HttpCachePolicy类得到了扩充和发展。主要是增加了一些重要方法,例如,SetOmitVarStar方法等。由于HttpCachePolicy类方法众多,下面简要说明几个常用方法。
SetExpires方法:用于设置缓存过期的绝对时间。它的参数是一个DataTime类的实例,表示过期的绝对时间。
{
// 通过API设置缓存
//相当于@OutputCache指令中的Duration属性
Response.Cache.SetExpires(DateTime.Now.AddSeconds(10));
Response.Cache.SetExpires(DateTime.Parse("6:00:00PM"));
}
如上代码,第一行代码表示输出缓存时间是60秒,并且页面不随任何GET或POST参数改变,等同于“<%@ OutputCache Duration="60" VaryByParam="none" %>”。第二行代码设置缓存过期的绝对时间是当日下午6时整。
SetLastModified方法:用于设置页面的Last-Modified HTTP标头。Last-Modified HTTP标头表示页面上次修改时间,缓存将依靠它来进行计时。如果违反了缓存限制层次结构,此方法将失败。该方法的参数是一个DataTime类的实例。
SetSlidingExpiration方法:该方法将缓存过期从绝对时间设置为可调时间。其参数是一个布尔值。当参数为true时,Cache-Control HTTP标头将随每个响应而更新。此过期模式与相对于当前时间将过期标头添加到所有输出集的IIS配置选项相同。当参数为False时,将保留该设置,且任何启用可调整过期的尝试都将静态失败。此方法不直接映射到HTTP标头。它由后续模块或辅助请求来设置源服务器缓存策略。
SetOmitVaryStar方法:ASP.NET 2.0新增的方法。用于指定在按参数进行区分时,响应是否应该包含vary:*标头。方法参数是一个布尔值,若要指示HttpCachePolicy不对其VaryByHeaders属性使用*值,则为true;否则为false。
SetCacheability方法:用于设置页面的Cache-Control HTTP标头。该标头用于控制在网络上缓存文档的方式。该方法有两种重载方式,所不同的是参数。一种重载方法的参数是HttpCacheability枚举值,包括NoCache、Private、Public、Server、ServerAndNoCache和ServerAndPrivate(有关这些枚举值的定义,可参考MSDN)。另一种方法的参数有两个,一个参数是HttpCacheability枚举值,另一个参数是字符串,表示添加到标头的缓存控制扩展。需要注意的是,仅当与Private或NoCache指令一起使用时,字段扩展名才有效。如果组合不兼容的指令和扩展,则此方法将引发无效参数异常。
1。整页缓存
[OutputCache(Duration = 3600, Location = OutputCacheLocation.Server, VaryByParam = "none")] public ActionResult Index() { //Do something. }
此外还可以在View窗口声明
<%@ OutputCache Duration="#ofseconds" Location="Any | Client | Downstream | Server | None | ServerAndClient " Shared="True | False" VaryByControl="controlname" VaryByCustom="browser | customstring" VaryByHeader="headers" VaryByParam="parametername" VaryByContentEncoding="encodings" CacheProfile="cache profile name | ''" NoStore="true | false" SqlDependency="database/table name pair | CommandNotification" %>
2 数据源缓存
<asp:ObjectDataSource EnableCaching="true" CacheDuration="20" ID="ObjectDataSource1" runat="server" SelectMethod="GetList" TypeName="BLL.Classes"></asp:ObjectDataSource>
3:自定义缓存
List<MODEL.Classes> list=null; if (Cache["myDog"] == null) { Cache["myDog"] = new Dog() { StrName = "小瑞瑞", StrType = "柯基犬" }; Response.Write("保存了一只狗狗"); //查询数据库 存入缓存 list = new BLL.Classes().GetList(); Cache["list"] = list; } else { Dog myDog = Cache["myDog"] as Dog; Response.Write("myDog="+myDog.StrName); //从缓存里获取数据 list = Cache["list"] as List<MODEL.Classes>; } myDog, list 就可以使用了
4.文件依赖项自定义缓存(新建一个文件 这里是txt)
List<MODEL.Classes> list=null; if (Cache["list"] == null) { //查询数据库 存入缓存 list = new BLL.Classes().GetList(); //设置绝对过期时间 //Cache.Insert("list", list, null, DateTime.Now.AddSeconds(20),System.Web.Caching.Cache.NoSlidingExpiration); //设置滑动过期时间 //Cache.Insert("list", list, null, System.Web.Caching.Cache.NoAbsoluteExpiration, new TimeSpan(10000)); //带 文件缓存依赖 string strPath = Server.MapPath("1.txt"); //文件缓存依赖项 System.Web.Caching.CacheDependency fileDep = new System.Web.Caching.CacheDependency(strPath); //创建带文件依赖的缓存 键值项 Cache.Insert("list", list, fileDep, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration); Response.Write("依赖文件被修改了,又重新读取数据列表存入缓存中了~~!"); } else { //从缓存里获取数据 list = Cache["list"] as List<MODEL.Classes>; } 使用缓存list
一、MVC缓存问题:
在 ASP.NET MVC 3 中如果使用了 OutputCache,一定要在 Action 中添加下面的代码,切记!
Response.Cache.SetOmitVaryStar(true);
这是一个伴随ASP.NET从1.0到4.0的OutputCache Bug,ASP.NET MVC 3 是基于 ASP.NET 4.0 的,所以也躲不过。
问题演示
下面先来体验一下不加 Response.Cache.SetOmitVaryStar(true); 的情况。
示例Action代码:
[OutputCache(Duration = 120)] public ActionResult SiteHome(int? pageIndex) { ... }
注:OutputCache.Location的默认值是OutputCacheLocation.Any(服务端、客户端、代理服务器端等都进行缓存)
第一次请求:
第二次请求(F5刷新浏览器):
第三次请求(F5刷新浏览器):
接着第四次请求会返回304,第五次请求又返回200。。。
再体验一下加 Response.Cache.SetOmitVaryStar(true); 的情况。
[OutputCache(Duration = 120)] public ActionResult SiteHome(int? pageIndex) { Response.Cache.SetOmitVaryStar(true); ... }
第一次请求:
第二次请求(F5刷新浏览器):
第三次请求(F5刷新浏览器):
注:只要在缓存有效期内,服务器一直返回304。
问题分析
1. 200与304的区别
当返回状态码是200时,服务器端会将当前请求的整个页面全部发送给客户端(消耗下行带宽)。
当返回状态码是304时,由于客户端浏览器提供的 Last-Modified 时间在服务器端的缓存有效期内,服务器端只发送这个状态码,不发送页面的任何内容(几乎不消耗下行带宽),浏览器直接从本地缓存中获取内容。
所以,304的好处就是节约带宽,响应速度更快。
2. 对服务端缓存的影响
加不加 Response.Cache.SetOmitVaryStar(true),服务端的缓存情况都是一样的。只是不加 SetOmitVaryStar(true) 时,对于同一个客户端浏览器,每隔一次请求,服务器端就不管客户端浏览器的缓存,重新发送页面内容,但是只要在缓存有效期内,内容还是从服务器端缓存中读取。
问题危害
ASP.NET 缓存的这个诡异行为,让你在不知不觉中浪费了带宽资源。
感想
用 ASP.NET 开发多年,这个伴随 ASP.NET 从 1.0 到 4.0 的 OutputCache Bug 自己竟然在去年才发现。之前测试时第一次请求后按F5看返回304就以为没问题,而问题恰恰就在下一下F5,偶尔多按一下F5出现200也没特别留意。由此可见,细心对程序员来说是多么重要,很多bug、很多性能问题往往不是水平不够,而是不够细心。
优秀的程序员都是细心的人,不仅在写代码的时候细心,在生活中也同样细心。别看他木讷的样子,你对他所做的一切,他都会细心地观察到、体会到。做细心的程序员,珍惜细心的程序员!
二、ASP.NET MVC Action Filter - 缓存与压缩
缓存在开发高扩充性WEB程序的时候扮演着很重要的角色.我们可以将HTTP请求在一个定义的时间内缓存在用户的浏览器中,如果用户在定义的时间内请求同一个URL,那么用户的请求将会从用户浏览器的缓存中加载,而不是从服务器.你可以在ASP.NET MVC应用程序中使用下面的Action Filter来实现同样的事情:
using System; using System.Web; using System.Web.Mvc; public class CacheFilterAttribute : ActionFilterAttribute { /// <summary> /// Gets or sets the cache duration in seconds. The default is 10 seconds. /// </summary> /// <value>The cache duration in seconds.</value> public int Duration { get; set; } public CacheFilterAttribute() { Duration = 10; } public override void OnActionExecuted(FilterExecutedContext filterContext) { if (Duration <= 0) return; HttpCachePolicyBase cache = filterContext.HttpContext.Response.Cache; TimeSpan cacheDuration = TimeSpan.FromSeconds(Duration); cache.SetCacheability(HttpCacheability.Public); cache.SetExpires(DateTime.Now.Add(cacheDuration)); cache.SetMaxAge(cacheDuration); cache.AppendCacheExtension("must-revalidate, proxy-revalidate"); } }
你可以好像下面一样在你的Controller Action 方法中使用这个Filter :
[CacheFilter(Duration = 60)] public void Category(string name, int? page)
下面是在firebug中当 缓存Filter 没有应用的时候的截图 :
下面的截图是应用了 Cache Filter 时候的截图 :
另外一个很重要的事情就是压缩.现在的浏览器都可以接收压缩后的内容,这可以节省大量的带宽.你可以在你的ASP.NET MVC 程序中应用下面的Action Filter 来压缩你的Response :
using System.Web; using System.Web.Mvc; public class CompressFilter : ActionFilterAttribute { public override void OnActionExecuting(FilterExecutingContext filterContext) { HttpRequestBase request = filterContext.HttpContext.Request; string acceptEncoding = request.Headers["Accept-Encoding"]; if (string.IsNullOrEmpty(acceptEncoding)) return; acceptEncoding = acceptEncoding.ToUpperInvariant(); HttpResponseBase response = filterContext.HttpContext.Response; if (acceptEncoding.Contains("GZIP")) { response.AppendHeader("Content-encoding", "gzip"); response.Filter = new GZipStream(response.Filter, CompressionMode.Compress); } else if (acceptEncoding.Contains("DEFLATE")) { response.AppendHeader("Content-encoding", "deflate"); response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress); } } }
然后将这个Filter应用到你的Controller Action 中 :
[CompressFilter] public void Category(string name, int? page)
下面是没有应用压缩的时候的截图 :
下面的截图是应用了压缩Filter后的情形 :
你当然也可以将这两个Filter都应用到同一个Action方法上,就好像下面所示 :
[CompressFilter(Order = 1)] [CacheFilter(Duration = 60, Order = 2)] public void Category(string name, int? page)
下面是截图 :
Enjoy!!!
下载源码: Source.zip