ASP.NET MVC的Action Filter
一年前写了一篇短文ASP.NET MVC Action Filters,整理了Action Filter方面的资源,本篇文章详细的描述Action Filter。Action Filter作为一个可以应用到Controller Action(或者是整个controller)上的属性(Attribute),改变Action执行的行为,当应用于整个Controller上时,Controller上的所有Action都应用了同样设置的Action。 使用Action Filter 可以处理缓存、 验证和错误处理您的操作使用声明性编程模型的行为。
ASP.NET MVC Framework支持四种不同类型的Filter:
- Authorization filters – 实现
IAuthorizationFilter接口的属性. - Action filters – 实现
IActionFilter接口的属性. - Result filters – 实现
IResultFilter接口的属性. - Exception filters – 实现
IExceptionFilter接口的属性. 
Filter的默认的执行顺序按上面的列表中顺序进行。如验证(authorization)Filter永远都是最开始执行的,异常(exception)Filter永远都是最后执行的,当然你也可以根据需要通过Order属性设定过滤器执行的顺序。
ASP.NET MVC Framework包括几种Action Filter:
| 名称 | 说明 | 
| OutputCacheAttribute           |         类似于 Web Form中在 OutputCache 指令。 OutputCache 属性允许在 MVC Framework 缓存控制器的输出。 | 
| ValidateInputAttribute           |                   类似于 Web Form中 ValidateRequest 属性。 MVC 框架默认将为 检查HTML 或其他危险输入传入的 HTTP 请求。 如果检测到,将引发异常。 使用此属性可以禁用请求验证。  |      
| AuthorizeAttribute | Authorize 属性,可以对控制器操做的声明性的授权检查。 该属性可以限制特定角色中的用户的操作。 当您创建只应该给管理员角色中的用户的操作时,您可以使用此属性。默认使用的ASP.NET Membership服务,如果不使用ASP.NET 的Membership服务,可以继承AuthorizeAttribute,重写实现。 | 
| ValidateAntiForgeryTokenAttribute | 此属性是一个解决方案以帮助防止跨站点的请求攻击 (CSRF)。 它允许验证的 HTTP POST 为特定于用户的标记在 Framework。 有关详细信息 CSRFs,请参阅" 使用 ASP.NET MVC AntiForgeryToken() 帮助器防止跨站点请求伪造 (CSFR)." | 
验证(authorization)filter用于实现在controller action上的验证和授权,如Authorize filter就是一个验证filter的例子;
Action filter包含一些逻辑,用于该action执行之前或者之后。比如可以使用一个action filter来修改action返回的view data;
Result filter包含一些逻辑,用于该action的view result执行之前和之后。比如可以修改一个view result在view被呈现到浏览器之前;
异常(Exception)Action用于处理异常信息,同样可以使用异常filter记录错误日志。
你同时可以创建自己的Action filter,比如说要实现一个自定义的验证系统,那么可能需要创建一个自定义的action filter,或者说当你需要改变controller action返回的view data的时候,也可以通过创建自定义action filter实现。
为了让用户更简单的创建一个自定义Action filter,ASP.NET MVC Framework提供了一个基类ActionFilterAttribute,这个类实现了IActionFilter和IResultFilter接口,并且继承了FilterAttribute类。从广义上来说,在ASP.NET MVC Framework中,任何实现filter的类型都是action filter。
ActionFilterAttribute类有以下的方法可以重写:
- OnActionExecuting – 在controller action执行之前调用
 - OnActionExecuted – 在controller action执行之后调用
 - OnResultExecuting – 在controller action result执行之前调用
 - OnResultExecuted – 在controller action result执行之后调用
 
执行的顺序如下图:
下面我们来介绍一个如何自定义一个Action Filter,这个示例的代码来自ASP.NET MVC 2示例Tailspin Travel,实现的功能是Action的执行时间,页面经常需要一个当前页面执行时间的功能,这是一个非功能性需求,ASP.NET MVC上就可以使用自定义的ActionFilter来实现,从上面的介绍,就知道我们需要重写OnActionExecuting和OnActionExecuted方法,代码如下:
namespace Microsoft.Samples.Tailspin.Web    
{     
    using System.Configuration;     
    using System.Diagnostics;     
    using System.Globalization;     
    using System.Web.Mvc; 
    public class ExecutionTimingAttribute : ActionFilterAttribute    
    {     
        private bool timingEnabled = bool.Parse(ConfigurationManager.AppSettings["TimingEnabled"]);     
        private Stopwatch timer; 
        public override void OnActionExecuting(ActionExecutingContext filterContext)    
        {     
            base.OnActionExecuting(filterContext);     
            if (this.timingEnabled)     
            {     
                this.timer = new Stopwatch();     
                this.timer.Start();     
            }     
        } 
        public override void OnActionExecuted(ActionExecutedContext filterContext)    
        {     
            base.OnActionExecuted(filterContext);     
            if (this.timingEnabled)     
            {     
                this.timer.Stop();     
                Trace.WriteLine(string.Format(CultureInfo.InvariantCulture, "Action execution time: {0}ms", this.timer.ElapsedMilliseconds));     
                if (filterContext.Result is ViewResult)     
                {     
                    ((ViewResult)filterContext.Result).ViewData["ExecutionTime"] = this.timer.ElapsedMilliseconds;     
                }     
            }     
        }     
    }     
}
通过一个配置项是否启用页面执行时间的控制,代码非常简单,使用Stopwatch来进行时间的计算,将执行的时间(毫秒为单位)存放到ViewData里ViewData["ExecutionTime"] 。    
在MasterPage里头尾部加入:
<div id="footer">    
        <% if(ViewData.ContainsKey("ExecutionTime")) { %>     
            <p>Execution Time: <%:ViewData["ExecutionTime"]%> ms.</p>     
        <%} %>     
    </div>
參考