理解 Action Filters[本文示例源码下载]
Action filter 是能够应用于 controller action --或整个controller的一个特性,它们的基类为System.Web.Mvc.FilterAttribute 。它限定了action执行的方式。ASP.NET MVC框架包含数个action filters。
使用Action Filter
action filter是一个特性. 你能够应用大部分的action filters 在单个的controller action 或者整个controller上.
例如下面的Data controller有一个返回当前时间的Index()方法.这个action拥有OutputCache
action filter. 这个过滤器导致由action返回的值能够缓存10秒钟.
VaryByParam 属性使用的设置不建议通过设置“*”的值来使用所有参数进行区分。这可能会导致缓存溢出。
- public class DataController : Controller
- {
- //
- // GET: /Data/
- [OutputCache(Duration = 20,VaryByParam ="")]
- public string Index()
- {
- return DateTime.Now.ToString();
- }
- }
如果你重复调用Index()
action(不断刷新当前页面), 那么你将看到当前的内容在Duration = 20秒内是不变的.
一个单一的action filter – OutputCache
action filter – 被应用于Index()
方法. 同样,你可以应用多个action filters 在同一个action上.
ASP.NET MVC框架支持多种不同类型的过滤器:
IAuthorizationFilter
特性.IActionFilter
特性.IResultFilter
特性.IExceptionFilter
特性.Filters 按照上面列出的顺序执行。例如, authorization filters 总是在action filters之前执行,exception filters在所有其他类型的filter之后执行.
为了使你能够更加容易的实现自定义的action filter, ASP.NET MVC框架包含一个ActionFilterAttribute
基类. 这个类实现了IActionFilter
与IResultFilter
接口,并且继承了Filter
类。
ActionFilterAttribute
基类拥有以下可以重载的方法:
使用ASP.NET MVC 框架, 简单的指定OutputCache 指令并不能达到理想的效果. 幸好, ActionFilterAttribute让你能够在 controller action执行的前后运行代码.
让我们使用类似的方法来创建OutputCache ActionFilterAttribute。
- [OutputCache(Duration = 60, VaryByParam = "*", CachePolicy = CachePolicy.Server)]
- public ActionResult Index()
- {
- //
- }
我们将使用命名为CachePolicy的枚举类型来指定OutputCache 特性应怎样以及在哪里进行缓存:
- public enum CachePolicy
- {
- NoCache = 0,
- Client = 1,
- Server = 2,
- ClientAndServer = 3
- }
事实上,这是很容易的。在view呈现前,我们将增加一些HTTP头到响应流。网页浏览器将获得这些头部,并且通过使用正确的缓存设置来回应请求。如果我们设置duration为60,浏览器将首页缓存一分钟。
- using System.Web.Mvc;
- namespace MVCActionFilters.Web.Models
- {
- public class OutputCache:System.Web.Mvc.ActionFilterAttribute
- {
- public int Duration { get; set; }
- public CachePolicy CachePolicy { get; set; }
- public override void OnActionExecuted(ActionExecutedContext filterContext)
- {
- if (CachePolicy == CachePolicy.Client || CachePolicy == CachePolicy.ClientAndServer)
- {
- if (Duration <= 0) return;
- //用于设置特定于缓存的 HTTP 标头以及用于控制 ASP.NET 页输出缓存
- 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");
- }
- }
- }
- }
Server-side 缓存有一点难度. 首要的,在输出缓存系统中,我们将不得不准备HTTP 响应为可读的。为了这样做,我们首先保存当前的HTTP context到类的一个变量中. 然后, 我们创建一个新的httpcontext ,通过它将数据写入StringWriter,同时允许读操作可以发生:
- existingContext = System.Web.HttpContext.Current;//保存当前的HTTP context到类的一个变量中
- writer = new StringWriter();
- HttpResponse response = new HttpResponse(writer);
- HttpContext context = new HttpContext(existingContext.Request, response)
- {
- User = existingContext.User
- };
- System.Web.HttpContext.Current = context;
- public override void OnResultExecuting(ResultExecutingContext filterContext)
- {
- if (CachePolicy == CachePolicy.Server || CachePolicy == CachePolicy.ClientAndServer)
- {
- //获取缓存实例
- cache = filterContext.HttpContext.Cache;
- // 获取缓存数据
- object cachedData = cache.Get(GenerateKey(filterContext));
- if (cachedData != null)
- {
- // 返回缓存数据
- cacheHit = true;
- filterContext.HttpContext.Response.Write(cachedData);
- filterContext.Cancel = true;
- }
- else
- { //重新设置缓存数据
- existingContext = System.Web.HttpContext.Current;
- writer = new StringWriter();
- HttpResponse response = new HttpResponse(writer);
- HttpContext context = new HttpContext(existingContext.Request, response)
- {
- User = existingContext.User
- };
- foreach (var key in existingContext.Items.Keys)
- {
- context.Items[key] = existingContext.Items[key];
- }
- System.Web.HttpContext.Current = context;
- }
- }
- }
利用该代码,我们能从高速缓存中检索现有项,并设置了HTTP响应能够被读取。在视图呈现之后,将数据存储在高速缓存中:
- public override void OnResultExecuted(ResultExecutedContext filterContext)
- {
- // 服务器端缓存?
- if (CachePolicy == CachePolicy.Server || CachePolicy == CachePolicy.ClientAndServer)
- {
- if (!cacheHit)
- {
- // 存储原有的context
- System.Web.HttpContext.Current = existingContext;
- // 返回呈现的数据
- existingContext.Response.Write(writer.ToString());
- //增加数据到缓存
- cache.Add(
- GenerateKey(filterContext),
- writer.ToString(),
- null,
- DateTime.Now.AddSeconds(Duration),
- Cache.NoSlidingExpiration,
- CacheItemPriority.Normal,
- null);
- }
- }
- }
你现在注意到添加了一个VaryByParam到 OutputCache ActionFilterAttribute。当缓存server-side时,我可以通过传入的参数来改变缓存存储。这个GenerateKey方法会产生一个依赖于controller,action和VaryByParam的键。
- private string GenerateKey(ControllerContext filterContext)
- {
- StringBuilder cacheKey = new StringBuilder();
- // Controller + action
- cacheKey.Append(filterContext.Controller.GetType().FullName);
- if (filterContext.RouteData.Values.ContainsKey("action"))
- {
- cacheKey.Append("_");
- cacheKey.Append(filterContext.RouteData.Values["action"].ToString());
- }
- // Variation by parameters
- List<string> varyByParam = VaryByParam.Split(';').ToList();
- if (!string.IsNullOrEmpty(VaryByParam))
- {
- foreach (KeyValuePair<string, object> pair in filterContext.RouteData.Values)
- {
- if (VaryByParam == "*" || varyByParam.Contains(pair.Key))
- {
- cacheKey.Append("_");
- cacheKey.Append(pair.Key);
- cacheKey.Append("=");
- cacheKey.Append(pair.Value.ToString());
- }
- }
- }
- return cacheKey.ToString();
- }
现在你可以增加 OutputCache attribute 到应用程序的任何一个controller 与controller action中 。
- [MVCActionFilters.Web.Common.OutputCache(Duration = 20, VaryByParam = "*",CachePolicy=Common.CachePolicy.Client)]
- public string Cache()
- {
- return DateTime.Now.ToString();
- }
总结
需注意事件的发生时间段