ASP.NET MVC3 Action Filters详解(一)


  一个Action Filter是一个特性(Attribute),定义了横切在Action方法执行前后的行为。一个Action Filter可以应用在某个Action方法上,也可以应用在整个Controller上。

  所有Action Filter必须继承自FilterAttribute,同时必须实现至少一个以下接口:

  IAuthorizationFilter,负责认证和授权,包含一个OnAuthorization方法。

  IActionFilter,在Action方法执行前后调用,包含OnActionExecuting和OnActionExecuted两个方法。

  IResultFilter,在ActionResult执行前后调用,包含OnResultExecuting和OnResultExecuted两个方法。

  IExceptionFilter,当发生异常时调用,不管是在Action方法中、Result执行中或是前面三种Filter的执行过程中产生的未处理异常都会触发IExceptionFilter的OnException方法。

  以上几种ActionFilter的执行顺序就是按照上面的顺序,相同种类的ActionFiler按顺序执行。接口中的配对方法需要配对执行。

  ASP.Net MVC内建了几个ActionFilter供我们使用:

  ActionFilterAttribute,实现了IActionFilter和IResultFilter,但是方法内部什么也不做,供继承它的子类重写。

  OutputCacheAttribute,缓存Action的执行输出结果,它继承了ActionFilterAttribute,又实现了IExceptionFilter接口。

  HandleErrorAttribute,实现了IExceptionFilter,供开发人员选择一个View来处理Exception

  AuthorizeAttribute,实现了IAuthorizationFilter,提供认证和授权的功能,开发人员可以选择继承它然后覆盖它的方法来实现自己的认证和授权机制。

 

  下面我打算结合源码深入理解一下这些Filters。

  ControllerActionInvoker类负责Action方法的调用,它的InvokeAction方法显示了Action方法调用,ActionResult执行和Filter之间的关系。

public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) {
if (controllerContext == null) {
throw new ArgumentNullException("controllerContext");
}
if (String.IsNullOrEmpty(actionName)) {
throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName");
}

ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);
ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);
if (actionDescriptor != null) {
FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

try {
AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);
if (authContext.Result != null) {
// the auth filter signaled that we should let it short-circuit the request
InvokeActionResult(controllerContext, authContext.Result);
}
else {
if (controllerContext.Controller.ValidateRequest) {
ValidateRequest(controllerContext);
}

IDictionary<string, object> parameters = GetParameterValues(controllerContext, actionDescriptor);
ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);
InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result);
}
}
catch (ThreadAbortException) {
// This type of exception occurs as a result of Response.Redirect(), but we special-case so that
// the filters don't see this as an error.
throw;
}
catch (Exception ex) {
// something blew up, so execute the exception filters
ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);
if (!exceptionContext.ExceptionHandled) {
throw;
}
InvokeActionResult(controllerContext, exceptionContext.Result);
}

return true;
}

// notify controller that no method matched
return false;
}

  这段代码先获取了Controller、Action、Filter的描述信息

  ControllerDescriptor controllerDescriptor = GetControllerDescriptor(controllerContext);

  ActionDescriptor actionDescriptor = FindAction(controllerContext, controllerDescriptor, actionName);

  FilterInfo filterInfo = GetFilters(controllerContext, actionDescriptor);

  接着使用这些描述信息,在恰当的时候先后调用了AuthorizationFilters、ActionFilters、ResultFilters、ExceptionFilters,几个关键方法是:

  AuthorizationContext authContext = InvokeAuthorizationFilters(controllerContext, filterInfo.AuthorizationFilters, actionDescriptor);//调用AuthorizationFilters

  ActionExecutedContext postActionContext = InvokeActionMethodWithFilters(controllerContext, filterInfo.ActionFilters, actionDescriptor, parameters);//执行Action方法,同时调用ActionFilters

  InvokeActionResultWithFilters(controllerContext, filterInfo.ResultFilters, postActionContext.Result); //执行ActionResult,同时调用ResultFilters

  ExceptionContext exceptionContext = InvokeExceptionFilters(controllerContext, filterInfo.ExceptionFilters, ex);//如果上面的过程中抛出未处理的异常,调用ExceptionFilter

 

  阅读这些代码,就可以详细了解Action和Filter的执行过程,我总结几点。

  1. 调用AuthorizationFilters

  ControllerActionInvoker.InvokeAuthorizationFilters方法依次调用每个注册的AuthorizationFilter的OnAuthorization方法,一旦发现在某个OnAuthorization方法调用过程中设置了ActionResult,就会停止继续执行,并且跳过Action方法,直接执行这个ActionResult,返回结果给客户端。如果在OnAuthorization方法中抛出未捕获异常,会直接跳至ExceptionFilters。

  2. 执行Action方法,同时调用ActionFilters

  ControllerActionInvoker.InvokeActionMethodWithFilters方法负责这个过程。

  

  我们先考虑只有一个ActionFilter的情况。

  首先执行ActionFilter的OnActionExecuting方法,如果在方法中设置了传入的上下文的Result属性,执行将被短路,直接返回这个Result,Action方法和ActionFilter的OnActionExecuted方法都不会被执行。

  如果ActionFilter的OnActionExecuting方法抛出异常,将直接跳转至ExceptionFilters。

  如果ActionFilter的OnActionExecuting方法正常返回且未设置Result属性,接下来执行Action方法,如果正常返回一个ActionResult,就将这个Result传递给ActionFilter的OnActionExecuted方法(作为上下文参数的Result属性)。如果Action方法中抛出异常,仍然会调用ActionFilter的OnActionExecuted方法,这时传给该方法的上下文对象的Result属性为null,Exception属性为Action方法抛出的异常对象,如果这时在OnActionExecuted方法中设置了上下文的ExceptionHandled为true,该异常不会再抛出,ExceptionHandled针对的是Action方法抛出的异常,如果是在OnActionExecuted方法的执行内部抛出异常,不管有没有设置ExceptionHandled为true都会抛出该异常,流程跳转至ExceptionFilters。

  

  现在我们考虑有多个ActionFilter的情况,假设有FilterA和FilterB,调用流程如下图

  执行FilterB的情况与只有一个ActionFilter的情形一样,前面已经讨论过了。在执行FilterA的方法时,FilterA将FilterB和Action方法看成一个整体,如果这个整体抛出异常,FilterA.OnActionExecuted方法的上下文中的Exception属性会被设为这个异常供我们处理,就像FilterB.OnActionExecuted方法的上下文中可能包含Action方法执行时抛出的异常一样。

  由此看出FilterA的执行和FilterB的执行的内部逻辑是一样的,就像同一个函数被嵌套着调用,ASP.Net MVC 源代码巧妙的使用了linq中的聚合方法Aggregate进行多个Filter的迭代,使用多层嵌套的lambda表达式来表示迭代的结果,迭代的结果是一个lambda表达式,即一个函数入口指针,指向最外面的Filter的执行起点。

protected virtual ActionExecutedContext InvokeActionMethodWithFilters(ControllerContext controllerContext, IList<IActionFilter> filters, ActionDescriptor actionDescriptor, IDictionary<string, object> parameters) {
ActionExecutingContext preContext = new ActionExecutingContext(controllerContext, actionDescriptor, parameters);
Func<ActionExecutedContext> continuation = () =>
new ActionExecutedContext(controllerContext, actionDescriptor, false /* canceled */, null /* exception */) {
Result = InvokeActionMethod(controllerContext, actionDescriptor, parameters)
};

// need to reverse the filter list because the continuations are built up backward
Func<ActionExecutedContext> thunk = filters.Reverse().Aggregate(continuation,
(next, filter) => () => InvokeActionMethodFilter(filter, preContext, next));
return thunk();
}

  

  3. 执行ActionResult,同时调用ResultFilters

  ControllerActionInvoker.InvokeActionResultWithFilters方法负责这个过程,考虑只有一个ResultFilter的情况,首先执行Filter的OnResultExecuting方法,在这个方法中可以重置ActionResult。如果在这个方法内将上下文的Cancel属性设为true,将停止ActionResult的执行,也就是说不会再执行ActionResult. ExecuteResult和Filter的OnResultExecuted方法,浏览器得不到返回。如果在OnResultExecuting方法内抛出异常,流程会跳转至ExceptionFilters。

  接着执行ActionResult,即调用该ActionRsult的ExecuteResult方法,向浏览器输出结果,如果这时抛出异常,会将这个异常先报告给Filter的OnResultExecuted方法,OnResultExecuted方法内部可以检查上下文的Exception属性,如果不为null,可以处理这个异常,如果不需要继续抛出,可以设置上下文的ExceptionHandled属性为true。

  最后执行Filter的OnResultExecuted方法。

  多个ResultFilter的情况与上面讨论多个ActionFilter的情况类似,就不赘述了。

 

  4. 如果上面的过程中抛出未处理的异常,调用ExceptionFilter

  ControllerActionInvoker.InvokeExceptionFilters方法负责这个过程。前面的Filters或Action方法执行抛出的异常都会被ExceptionFilter捕获到,可以在这里统一处理异常。InvokeExceptionFilters方法会依次调用每一个ExceptionFilter的OnException方法,如果在OnException方法中设置了上下文的Result属性并且将上下文的ExceptionHandled属性设为true,就会执行这个ActionResult输出内容给浏览器,一般会返回一个友好的错误页面,如果不设置ExceptionHandled为true,会继续抛出异常,最终用户看到异常信息的黄页。

  ASP.Net MVC 自带了一个好用的ExceptionFilter:HandleErrorAttribute,可以应用在Action方法上,它包含三个属性:ExceptionType(处理的异常类型,默认为typeof(Exception)),Master(母版页名称),View(视图名称,默认为“Error”),HandleError的OnException方法会给上下文返回一个指向属性中设置的View和Master名称的ViewResult,同时将ControllerName,ActionName还有Exception对象作为视图的Model对象的属性传给这个视图,视图的Model对象的类型为HandleErrorInfo类型,它包括ControllerName、ActionName、Exception三个属性。

 

  ASP.Net MVC 还提供了两个重要的Filter实现:

  AuthorizeAttribute:用于用户认证和授权

  OutputCacheAttribute:用于缓存Action执行结果

  我会在下一篇文章中详细介绍它们。

posted @ 2012-01-30 10:30  JSK  阅读(3424)  评论(4编辑  收藏  举报