ASP.NET MVC中的ActionFilter是如何执行的?
在ASP.NET MVC中的四大筛选器(Filter),ActionFilter直接应用在某个Action方法上,它在目标Action方法执行前后对调用进行拦截以执行一些额外的操作。这是一种典型的AOP式的设计,如果我们需要在执行某个Action方法的前后执行一些操作,可以通过定义ActionFilter来实现。本篇文章主要讲述多一个应用到相同Action方法上的ActionFilter的执行机制。[本文已经同步到《How ASP.NET MVC Works?》中]
目录
一、ActionFilter
二、ActionFilter的执行机制
三、ActionFilter对ActionResult的设置
四、ActionFilter中的异常处理
一、ActionFilter
ActionFilter允许我们在目标Action方法执行前后对调用进行拦截以执行一些额外的操作,所有的ActionFilter实现了具有如下定义的接口IActionFilter。
1: public interface IActionFilter
2: {
3: void OnActionExecuting(ActionExecutingContext filterContext);
4: void OnActionExecuted(ActionExecutedContext filterContext);
5: }
6:
7: public class ActionExecutingContext : ControllerContext
8: {
9: public ActionExecutingContext();
10: public ActionExecutingContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary<string, object> actionParameters);
11:
12: public virtual ActionDescriptor ActionDescriptor { get; set; }
13: public virtual IDictionary<string, object> ActionParameters { get; set; }
14: public ActionResult Result { get; set; }
15: }
16:
17: public class ActionExecutedContext : ControllerContext
18: {
19: public ActionExecutedContext();
20: public ActionExecutedContext(ControllerContext controllerContext, ActionDescriptor actionDescriptor, bool canceled, Exception exception);
21:
22: public virtual ActionDescriptor ActionDescriptor { get; set; }
23: public virtual bool Canceled { get; set; }
24: public virtual Exception Exception { get; set; }
25: public bool ExceptionHandled { get; set; }
26: public ActionResult Result { get; set; }
27: }
如上面的代码片断所示,IActionFilter接口中定义了两个方法OnActionExecuting和OnActionExecuted,这两个方法分别在目标Action方法执行前后被调用,它们的参数类型分别为ActionExecutingContext和ActionExecutedContext。这两个上下文了类型均是ControllerContext的子类。
我们可以从ActionExecutingContext对象中获取到用于描述当前Action的ActionDescriptor,以及参数列表。ActionFilter可以在OnActionExecuting方法中对ActionExecutingContext对象的Result属性进行赋值来直接响应当前的请求。一旦ActionExecutingContext的Result属性被成功赋值,将会终止后续ActionFilter和最终目标方法的执行。
ActionExecutedContext具有额外的三个属性,Exception表示执行Action方法过程中抛出的异常,而ExceptionHandled是一个表示是否对异常已经做出处理的标记。Canceled属性表示没有完成整个ActionFilter链和目标Action方法的执行而中途被终止。
二、ActionFilter的执行机制
当ActionInvoker在执行目标Action方法之前,会根据Order和Scope属性对用于封装ActionFilter的Filter对象进行排序。然后根据当前ControllerContext和ActionDescriptro创建一个ActionExecutingContext对象,并将其作为参数依次调用所有ActionFilter的OnActionExecuting方法。
在这之后真正的目标Action方法被执行,ActionInvoker随后执行后续的筛选操作。具体来说,它根据当前ControllerContext、ActionDescriptro以及Action方法执行过程中抛出的异常创建一个ActionExecutedContext对象。该ActionExecutedContext的Cancel属性为False,如果Action方法返回一个ActionResult对象,该对象将会作为该ActionExecutedContext的Result属性。
接下来按照相反的次序依次调用ActionFilter对象的OnActionExecuted方法,执行过程中的ActionFilter可以修改ActionExecutedContext的Result属性。当整个ActionFilter链执行结束之后,ActionExecutedContext的Result属性返回的ActionResult将会作为对当前请求的响应。右图基本上反映了连同目标Action在内的整个ActionFilter链的执行过程。
三、ActionFilter对ActionResult的设置
上面我们已经提到过,在ActionFilter链进行OnActionExecuting方法调用的过程中,一旦某个ActionFilter为ActionExecutingContext的Result属性设置了一个ActionResult对象,后续ActionFilter和目标Action将不会被执行。实际上此时ActionInvoker此时会创建一个ActionExecutedContext对象,设置的ActionResult直接作为其Result属性,而Cancel属性被设置为True。我们现在考虑的问题是:之前的ActionFilter的OnActionExecuted是否还被执行呢?
为了弄清楚这个问题,我们来创建一个测试程序。在通过Visual Studio的ASP.NET MVC项目模板创建的空Web应用中我们定义了如下三个ActionFilter(FooAttribute、BarAttribute和BazAttribute),它们都继承自我们自定义的FilterBaseAttribute。在FilterBaseAttribute中实现的OnActionExecuting和OnActionExecuted方法中,我们将ActionFilter自身的类型和执行方法名写入当前HttpResponse并最终呈现在浏览器中。BarAttribute重写了OnActionExecuting方法,在调用基类同名方法之后为ActionExecutingContext的Result设置了一个EmptyResult对象。
1: public abstract class FilterBaseAttribute : FilterAttribute, IActionFilter
2: {
3: public virtual void OnActionExecuted(ActionExecutedContext filterContext)
4: {
5: filterContext.HttpContext.Response.Write(string.Format("{0}.OnActionExecuted()<br/>", this.GetType().Name));
6: }
7:
8: public virtual void OnActionExecuting(ActionExecutingContext filterContext)
9: {
10: filterContext.HttpContext.Response.Write(string.Format("{0}.OnActionExecuting()<br/>", this.GetType().Name));
11: }
12: }
13:
14: public class FooAttribute : FilterBaseAttribute
15: {}
16: public class BarAttribute : FilterBaseAttribute
17: {
18: public override void OnActionExecuting(ActionExecutingContext filterContext)
19: {
20: base.OnActionExecuting(filterContext);
21: filterContext.Result = new EmptyResult();
22: }
23: }
24: public class BazAttribute : FilterBaseAttribute
25: {}
然后我们定义了如下一个HomeController,上面定义的三个ActionFilter特性被应用到了Action方法Index上。我们对三个ActionFilter特性的Order属性作了相应地设置使它们可以按照我们希望的顺序(FooAttribute =>BarAttribute =>BazAttribute)执行。
1: public class HomeController : Controller
2: {
3: [Foo(Order = 1)]
4: [Bar(Order = 2)]
5: [Baz(Order = 3)]
6: public void Index()
7: {
8: Response.Write("Index...</br>");
9: }
10: }
运行该程序后会在浏览器中呈现出如左图所示的输出结果,从中可以看出对于应用到Action方法Index上的三个ActionFilter,当BarAttribute的OnActionExecuting方法执行并对ActionExecutingContext的Result属性进行了相应设置后,在它之前的ActionFilter的OnActionExecuted方法依然还是会执行。
这个简单的实例演示揭示了应用到同一个Action方法上的ActionFilter链的执行机制:如果某个某个ActionFilter在执行OnActionExecuting方法过程中对ActionExecutingContext的Result属性进行了设置,后续的ActionFilter和目标Action方法将不会再执行。此时ActionExecutedContext对象被创建,通过ActionExecutingContext的Result属性表示的ActionResulut对象将会赋值给ActionExecutedContext的Result属性。然后以前一个ActionFilter作为起点将创建的ActionExecutedContext对象作为输入参数调用它们的OnActionExecuted方法。右图基本上揭示了整个ActionFilter链执行的流程。顺便指出一点:某个ActionFilter在OnActionExecuted方法中对ActionExecutedContext的Result的设置对整个ActionFilter链的执行没有影响。
四、ActionFilter中的异常处理
通过上面的介绍我们知道了某个ActionFilter在执行OnActionExecuting/OnActionExecuted方法过程中设置ActionExecutingContext/ActionExecutedContext的Result属性进行设置后会对整个ActionFilter链的执行造成怎样的影响,接下来我们来讨论一下如果某个ActionFilter在执行OnActionExecuting/OnActionExecuted方法抛出异常,整个ActionFilter链又会如何执行。
如果第一个ActionFilter在执行OnActionExecuting或者OnActionExecuted方法的过程中出现异常,那么这个异常会被直接抛出。对于出现异常的并不是第一个ActionFilter,那么异常会被捕捉并据此创建一个ActionExecutedContext对象(其Canceled属性为False)作为参数调用前一个ActionFilter的OnActionExecuted方法。在前一个ActionFilter的OnActionExecuted方法执行之后ActionExecutedContext的ExceptionHandled属性为True,会按照正常的方式调用之前ActionFilter的OnActionExecuted方法。
反之,如果ExceptionHandled属性为False,则会直接将异常抛出来,而这个被抛出的异常又会被之前的ActionFilter捕捉到,而这个ActionFilter又会根据捕捉的异常创建一个ActionExecutedContext对象并调用自身的OnActionExecuted方法。如果异常是在非链头的ActionFilter的OnActionExecuted方法中抛出的,处理流程与此类似。
我们不妨举例说明Action链在执行过程中对异常的处理。假设具有如左图所示的4个ActionFilter被应用到目标Action方法上,现在Filter1、Filter2和Filter3的OnActionExecuting方法异常被正常调用,但是Filter4在执行OnActionExecuting方法的时候抛出一个异常。该异常会被Filter3捕捉,它会根据这个异常创建一个ActionExecutedContext对象,并作为参数调用自己的OnActionExecuted方法(步骤1)。
如果Filter3在执行OnActionExecuted方法后ActionExecutedContext的ExceptionHandled属性为False,它会直接将异常抛出来。再次抛出的异常又会被Filter2所捕捉,它按照Filter3的方式根据异常创建ActionExecutedContext对象并作为参数调用自己的OnActionExecuted方法(步骤2)。如果Filter2在执行OnActionExecuted方法的时候将ActionExecutedContext对象的ExceptionHandled属性设置为True,那么在这之后会正常地调用Filter1的OnActionExecuted方法,最终不会有异常抛出(步骤3)。