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方法执行前后被调用,它们的参数类型分别为ActionExecutingContextActionExecutedContext。这两个上下文了类型均是ControllerContext的子类。

我们可以从ActionExecutingContext对象中获取到用于描述当前Action的ActionDescriptor,以及参数列表。ActionFilter可以在OnActionExecuting方法中对ActionExecutingContext对象的Result属性进行赋值来直接响应当前的请求。一旦ActionExecutingContext的Result属性被成功赋值,将会终止后续ActionFilter和最终目标方法的执行。

ActionExecutedContext具有额外的三个属性,Exception表示执行Action方法过程中抛出的异常,而ExceptionHandled是一个表示是否对异常已经做出处理的标记。Canceled属性表示没有完成整个ActionFilter链和目标Action方法的执行而中途被终止。

二、ActionFilter的执行机制

ASP.NET MVC中的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: }

ASP.NET MVC中的ActionFilter是如何执行的?运行该程序后会在浏览器中呈现出如左图所示的输出结果,从中可以看出对于应用到Action方法Index上的三个ActionFilter,当BarAttribute的OnActionExecuting方法执行并对ActionExecutingContext的Result属性进行了相应设置后,在它之前的ActionFilter的OnActionExecuted方法依然还是会执行。ASP.NET MVC中的ActionFilter是如何执行的?

这个简单的实例演示揭示了应用到同一个Action方法上的ActionFilter链的执行机制:如果某个某个ActionFilter在执行OnActionExecuting方法过程中对ActionExecutingContext的Result属性进行了设置,后续的ActionFilter和目标Action方法将不会再执行。此时ActionExecutedContext对象被创建,通过ActionExecutingContext的Result属性表示的ActionResulut对象将会赋值给ActionExecutedContext的Result属性。然后以前一个ActionFilter作为起点将创建的ActionExecutedContext对象作为输入参数调用它们的OnActionExecuted方法。右图基本上揭示了整个ActionFilter链执行的流程。顺便指出一点:某个ActionFilter在OnActionExecuted方法中对ActionExecutedContext的Result的设置对整个ActionFilter链的执行没有影响。

四、ActionFilter中的异常处理

ASP.NET MVC中的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)。


作者:蒋金楠
微信公众账号:大内老A
微博:www.weibo.com/artech
如果你想及时得到个人撰写文章以及著作的消息推送,或者想看看个人推荐的技术资料,可以扫描左边二维码(或者长按识别二维码)关注个人公众号(原来公众帐号蒋金楠的自媒体将会停用)。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
上一篇:Golang 异步对象序列化Map并发冲突和解决方法


下一篇:《C++语言基础》实践参考——三角形类1