上一篇文章介绍了使用Authorize特性实现了ASP.NET MVC中针对Controller或者Action的授权功能,实际上这个特性是MVC功能的一部分,被称为过滤器(Filter),它是一种面向切面编程(AOP)的实现,本章将从以下几个方面来介绍ASP.NET MVC中的过滤器。
● ASP.NET MVC 中的过滤器及其类型
● ASP.NET MVC 中常用的过滤器
● ASP.NET MVC 过滤器的应用方法
● ASP.NET MVC Action方法的调用与Filter的执行
● ASP.NET MVC 过滤器的创建与获取
● ASP.NET MVC Action及Result过滤器的管道执行
ASP.NET MVC中的过滤器及其类型
在之前的Entity Framework文章中介绍了EF自带的拦截器(interceptors)功能,ASP.NET MVC中的过滤器也和拦截器一样是一种面向切面(AOP)的编程方式,是一种不修改源代码的前提下对应用程序进行拓展的编程方式。一般AOP用来处理日志记录、性能统计、安全控制、事务处理、异常处理等不会对原有业务数据进行修改的功能。
ASP.NET MVC中把过滤器分为以下几类,每一类都是通过一个对应的接口定义的:
● 身份验证过滤器(IAuthenticationFilter):这个过滤器是在MVC5中加入的,它是所有过滤器中优先级最高的,使用身份验证过滤器可以为Action、Controller或者所有的Controller添加身份验证逻辑。身份验证过滤器的核心在于根据请求信息创建一个Principal对象(注:使用Identity的身份验证功能实际上也是创建一个Principal对象),以下是IAuthenticationFilter的定义:
其中身份验证上下文有一个IPrincipal的属性:
● 授权过滤器(IAuthorizationFilter):授权过滤器用来处理Controller以及Action的访问限制。
● Action方法过滤器(IActionFilter):Action过滤器可用于在Action方法执行前和执行后添加逻辑。
● 结果过滤器(IResultFilter):结果过滤器可以在结果执行前和执行后添加逻辑。(注:ASP.NET MVC中的Action返回结果为ActionResult类型,该抽象类型定义了一个执行方法ExecuteResult,结果的执行实际上是对返回结果的处理)
比如FileResult的执行实际上是在Http响应头中添加了适当的参数然后将文件的二进制数据写到了响应体中,相当于文件的下载功能。
更多结果执行内容会在后续文章中介绍。
● 异常过滤器(IExceptionFilter):异常过滤器就是Action方法在执行的过程中抛出异常时,用来添加异常处理逻辑的过滤器。
ASP.NET MVC 中常用的过滤器
上面介绍了过滤器的类别,现在介绍一下每一个类别下常用的过滤器有哪些:
● 身份验证过滤器(IAuthenticationFilter):由于身份验证过程可以使用Identity等成熟组件来完成,所以身份验证过滤器暂时没有找到适合的可以用的过滤器。如果系统有需求可自定义。
● 授权过滤器(IAuthorizationFilter):
○ Authorize:基于用户名、角色的用户授权。
○ RequireHttps:基于Https的访问授权。
○ ValidateInput:ASP.NET MVC在执行前会验证请求信息中是否包含HTML等不合法信息以避免XSS攻击,但是有的时候需求就是要提交HTML数据,在提交这些数据时可以使用该过滤器将EnableValidation设为false,MVC将跳过数据验证。
○ ValidateAntiForgeryToken:该过滤器可以对HtmlHelper的AntiForgeryToken方法生成防伪令牌进行校验,以避免CSRF跨站伪造攻击。
● Action过滤器(IActionFilter):一般根据需求自定义实现。
● 异常过滤器(IExceptionFilter):
○ HandleError:用于处理Action方法抛出的异常(默认的MVC模板会添加一个全局HandleError过滤器)。
另外还需要注意的是ASP.NET MVC中的Controller实际上也是一个过滤器,因为Controller基类实现了所有过滤器接口:
所以如果某一Controller中有特殊的处理需求,无需定义过滤器,在Controller中实现重载对应过滤器的方法即可:
ASP.NET MVC 过滤器的应用方法
ASP.NET MVC中的过滤器可以通过以下几种方法使用:
1. 通过特性的方式在Controller以及Action上标记使用,但是要注意的是以特性方式使用的过滤器除了实现对应的过滤器接口外还需要将其封装为一个.Net特性并实现IMvcFilter接口,最为方便的是直接继承FilterAttribute类型实现,如:
2. 通过全局过滤器表添加过滤器,这样添加的过滤器会对所有Controller的Action方法生效。
3. 在Controller类型中通过重载对应过滤器方法的方式实现,上面说明了Controller本身就是一个实现了所有过滤器的类型。
ASP.NET MVC Action方法的调用与Filter的执行
过滤器是在Action方法执行的过程中调用执行的,所以首先要了解Action的执行过程,在之前的文章中介绍了Controller的创建与执行《ASP.NET没有魔法——ASP.NET MVC Controller的实例化与执行》,而这里就基于该文章,来对Action的执行过程进行介绍,Controller的执行是通过Controller类型的ExecuteCore方法完成的:
而从代码中也可以看到Controller的执行实际上是通过ActionInvoker根据Action的名称来调用Action方法的执行,在ASP.NET MVC中默认使用一个名为 AsyncControllerActionInvoker的异步Action调用器:
它除了异步功能外还继承了同步的ControllerActionInvoker类型,异步主要是为了提高请求处理的吞吐量,这里将使用同步版本的代码进行Action与Filter执行介绍。
ControllerActionInvoker:
从代码定义中可以看出以下几点:
1. 它的核心方法是InvokeAction,它处理了所有的过滤器、Action方法的调用处理逻辑。
2. GetFilters方法,它用于获取所有相关的过滤器。
3. InvokeActionMethodWithFilters、InvokActionResultWitherFilters、InvokeAuthenticationFilters、InvokeAuthenticationFiltersChallenge、InvokeAuthorizationFilters、InvokeExceptionFilters等相关方法就是用来调用对应类型的过滤器执行的方法。
这里通过源码分析的方式来介绍过滤器在ActionInvoker的执行过程:
1 /// <summary>Invokes the specified action by using the specified controller context.</summary> 2 /// <returns>The result of executing the action.</returns> 3 /// <param name="controllerContext">The controller context.</param> 4 /// <param name="actionName">The name of the action to invoke.</param> 5 /// <exception cref="T:System.ArgumentNullException">The <paramref name="controllerContext" /> parameter is null.</exception> 6 /// <exception cref="T:System.ArgumentException">The <paramref name="actionName" /> parameter is null or empty.</exception> 7 /// <exception cref="T:System.Threading.ThreadAbortException">The thread was aborted during invocation of the action.</exception> 8 /// <exception cref="T:System.Exception">An unspecified error occurred during invocation of the action.</exception> 9 public virtual bool InvokeAction(ControllerContext controllerContext, string actionName) 10 { 11 if (controllerContext == null) 12 { 13 throw new ArgumentNullException("controllerContext"); 14 } 15 if (string.IsNullOrEmpty(actionName) && !controllerContext.RouteData.HasDirectRouteMatch()) 16 { 17 throw new ArgumentException(MvcResources.Common_NullOrEmpty, "actionName"); 18 } 19 ControllerDescriptor controllerDescriptor = this.GetControllerDescriptor(controllerContext); 20 ActionDescriptor actionDescriptor = this.FindAction(controllerContext, controllerDescriptor, actionName);//根据Controller信息及Action名称获取Action的描述信息 21 if (actionDescriptor != null) 22 { 23 FilterInfo filters = this.GetFilters(controllerContext, actionDescriptor);//获取所有过滤器 24 try 25 { 26 AuthenticationContext authenticationContext = this.InvokeAuthenticationFilters(controllerContext, filters.AuthenticationFilters, actionDescriptor);//调用身份验证过滤器 27 if (authenticationContext.Result != null) 28 { 29 AuthenticationChallengeContext authenticationChallengeContext = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, authenticationContext.Result); 30 this.InvokeActionResult(controllerContext, authenticationChallengeContext.Result ?? authenticationContext.Result); 31 } 32 else 33 { 34 AuthorizationContext authorizationContext = this.InvokeAuthorizationFilters(controllerContext, filters.AuthorizationFilters, actionDescriptor);//调用授权过滤器 35 if (authorizationContext.Result != null) 36 { 37 AuthenticationChallengeContext authenticationChallengeContext2 = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, authorizationContext.Result); 38 this.InvokeActionResult(controllerContext, authenticationChallengeContext2.Result ?? authorizationContext.Result); 39 } 40 else 41 { 42 if (controllerContext.Controller.ValidateRequest)//判断是否需要验证请求,使用ValidateInput特性并设置EnableValidation为False时跳过验证 43 { 44 ControllerActionInvoker.ValidateRequest(controllerContext); 45 } 46 IDictionary<string, object> parameterValues = this.GetParameterValues(controllerContext, actionDescriptor); 47 ActionExecutedContext actionExecutedContext = this.InvokeActionMethodWithFilters(controllerContext, filters.ActionFilters, actionDescriptor, parameterValues);//执行Action过滤器和Action方法 48 AuthenticationChallengeContext authenticationChallengeContext3 = this.InvokeAuthenticationFiltersChallenge(controllerContext, filters.AuthenticationFilters, actionDescriptor, actionExecutedContext.Result); 49 this.InvokeActionResultWithFilters(controllerContext, filters.ResultFilters, authenticationChallengeContext3.Result ?? actionExecutedContext.Result);//执行Result过滤器及Result 50 } 51 } 52 } 53 catch (ThreadAbortException) 54 { 55 throw; 56 } 57 catch (Exception exception) 58 { 59 ExceptionContext exceptionContext = this.InvokeExceptionFilters(controllerContext, filters.ExceptionFilters, exception);//当捕获异常时执行异常过滤器 60 if (!exceptionContext.ExceptionHandled)//如果异常过滤器并没有对异常进行处理则继续抛出异常 61 { 62 throw; 63 } 64 this.InvokeActionResult(controllerContext, exceptionContext.Result); 65 } 66 return true; 67 } 68 return false; 69 }
通过对上面代码的分析得出以下几个结论:
1. 通过Controller上下文及Action的信息找到真实的Action方法后,获取所有过滤器。
2. 先执行身份验证过滤器。
3. 通过身份验证过滤器后执行授权过滤器。
4. 授权过滤器通过后,执行Action过滤器及Action方法。
5. 执行Result过滤器及Result。
ASP.NET MVC 过滤器的创建与获取
根据上面的介绍知道了可以通过全局过滤器、特性标记以及重载Controller过滤器方法这三种方式来应用过滤器的,那么在执行过程中是如何通过ActionInvoker的GetFilters方法创建和获取它们的呢?
● 过滤器提供器(FilterProvider):ASP.NET MVC中有一个过滤器提供器的概念和实际对象,它有三种实现分别对应上述的三种应用方式:
○ GlobalFilterCollection:用于保存全局过滤器实例,可以直接通过它添加和获取过滤器实例,通过它创建的过滤器的Scope为Gobal,创建时可以通过Order参数来决定全局过滤器的执行顺序:
○ FilterAttributeFilterProvider:过滤器特性提供器,通过查找Controller以及Action上的特性来创建过滤器,根据特性标记位置将Scope分为Controller以及Action,在应用特性时可以设置特性的Order属性来决定过滤器的执行顺序:
○ ControllerInstanceFilterProvider:控制器实例过滤器提供器,它用于获取当前Controller实例的过滤器,并且过滤器的Scope为First:
● 过滤器提供器集合(FilterProviderCollection):它包含了上述的所有过滤器提供器,ActionInvoker就是通过它来获取所有相关过滤器的:
FitlerProviderCollection获取过滤器时通过以上三种提供器获取所有相关的过滤器并根据Scope以及Order对过滤器进行排序,以决定过滤器执行顺序。
ASP.NET MVC Action及Result过滤器的管道执行
在Action和Result过滤器的定义中都有两个方法,分别是OnXXXExecuting以及OnXXXExecuted,它们对应Action或者Reuslt执行前和执行后。当一个Action上存在多个Action或者Result过滤器时就会形成一个过滤器管道,其执行方式如下图所示:
小结
本文除了介绍ASP.NET MVC的过滤器功能及常用的过滤器外,还通过代码的形式分析了它创建与执行的过程。在一般的项目中使用ASP.NET MVC自带的过滤器就可以满足需求,如授权、错误处理等,但过滤器作为ASP.NET MVC中的一种重要的AOP拓展方式,合理的运用过滤器可以实现,如日志记录、性能分析、Action的事务执行(http://blog.gauffin.org/2012/06/how-to-handle-transactions-in-asp-net-mvc3/,这篇文章就利用Action过滤器实现了数据库的事务)等等功能,并且可以灵活的在不影响原有代码逻辑的情况下对系统进行拓展。
参考:
https://docs.microsoft.com/en-us/aspnet/mvc/overview/older-versions-1/controllers-and-routing/understanding-action-filters-cs
http://blog.gauffin.org/2012/06/how-to-handle-transactions-in-asp-net-mvc3/