过滤器Filter(1)
ASP.NET MVC 过滤器 可在执行管道的前后特定阶段执行代码。
过滤器可以配置为全局有效,仅对控制器有效或是仅对 Action 有效。
过滤器如何工作?
不同的过滤器类型会在执行管道的不同阶段运行,因此它们各自有一套适用场景。
根据你实际要解决的问题以及在请求管道中执行的位置来选择创建不同的过滤器。
运行于 MVC Action 调用管道内的过滤器有时被称为过滤管道,
当 MVC 选择要执行哪个Action 后就会先执行该 Action 上的过滤器。
不同过滤器在管道的不同位置运行.
像授权这样的过滤器只运行在管道的靠前位置,并且其后也不会跟随 action。
其它过滤器(如 action 过滤器等)可以在管道的其它部分之前或之后执行,
如下所示:
选择过滤器
授权过滤器 用于确定当前用户的请求是否合法。
资源过滤器 是授权之后第一个用来处理请求的过滤器,也是最后一个接触到请求的过滤器(因为之后就会离开过滤器管道)。在性能方面,资源过滤器在实现缓存或短路过滤器管道尤其有用。
Action 过滤器 包装了对单个 action 方法的调用,可以将参数传递给 action 并从中获得 action result。
异常过滤器 为 MVC 应用程序未处理异常应用全局策略。
结果过滤器 包装了单个 action result 的执行,当且仅当 action 方法成功执行完毕后方才运行。它们是理想的围绕视图执行或格式处理的逻辑(所在之处)。
实现
所有过滤器均可通过不同的接口定义来支持同步和异步实现。
根据你所需执行的任务的不同来选择同步还是异步实现。从框架的角度来讲它们是可以互换的。
同步过滤器定义了 OnStageExecuting 方法和 OnStageExecuted 方法(当然也有例外).
OnStageExecuting方法在具体事件管道阶段之前调用,而OnStageExecuted方法则在之后调用
(比如当 Stage 是 Action 时,这两个方法便是 OnActionExecuting 和 OnActionExecuted,译者注)。
using FiltersSample.Helper; using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersSample.Filters { public class SampleActionFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { // do something before the action executes(执行) } public void OnActionExecuted(ActionExecutedContext context) { // do something after the action executes(执行) } } }
异步过滤器定义了一个On\Stage\ExecutionAsync方法,可以在具体管道阶段的前后运行。
On\Stage\ExecutionAsync方法被提供了一个Stage\ExecutionDelegate委托,当调用时该委托会执行具体管道阶段的工作,然后等待其完成。
using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersSample.Filters { public class SampleAsyncActionFilter : IAsyncActionFilter { public async Task OnActionExecutionAsync( ActionExecutingContext context, ActionExecutionDelegate next) { // do something before the action executes await next(); // do something after the action executes } } }
注解
只能 实现一个过滤器接口,要么是同步版本的,要么是异步版本的,鱼和熊掌不可兼得 。
如果你需要在接口中执行异步工作,那么就去实现异步接口。否则应该实现同步版本的接口。
框架会首先检查是不是实现了异步接口,如果实现了异步接口,那么将调用它。不然则调用同步接口的方法。
如果一个类中实现了两个接口,那么只有异步方法会被调用。
最后,不管 action 是同步的还是异步的,过滤器的同步或是异步是独立于 action 的。
过滤器作用域
滤器具有三种不同级别的作用域 。
你可以在特定的 action 上用特性(Attribute)的方式使用特定的过滤器;
也可以在控制器上用特性的方式使用过滤器,这样就会将效果应用在控制器内所有的 action 上;
或者注册一个全局过滤器,它将作用于整个 MVC 应用程序下的每一个 action。
如果想要使用全局过滤器的话,在你配置 MVC 的时候在 Startup 的 ConfigureServices 方法中添加
public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { options.Filters.Add(typeof(SampleActionFilter)); // by type options.Filters.Add(new SampleGlobalActionFilter()); // an instance }); services.AddScoped<AddHeaderFilterWithDi>(); }
过滤器可通过类型添加,也可以通过实例添加。
如果通过实例添加,则该实例会被用于每一个请求。
如果通过类型添加,则将会 type-activated(意思是说每次请求都会创建一个实例,其所有构造函数依赖项都将通过 DI 来填充)。
通过类型添加过滤器相当于 filters.Add(new TypeFilterAttribute(typeof(MyFilter))) 。
把过滤器接口的实现当做 特性(Attributes) 使用是极为方便的。
过滤器特性(filter attributes)可应用于控制器(Controllers)和 Action 方法。
框架包含了内置的基于特性的过滤器,你可继承它们或另外定制。
下例过滤器继承了ResultFilterAttribute,并重写(override)了 OnResultExecuting 方法(在响应中增加了一个头信息)。
using Microsoft.AspNetCore.Mvc.Filters; namespace FiltersSample.Filters { public class AddHeaderAttribute : ResultFilterAttribute { private readonly string _name; private readonly string _value; public AddHeaderAttribute(string name, string value) { _name = name; _value = value; } public override void OnResultExecuting(ResultExecutingContext context) { context.HttpContext.Response.Headers.Add( _name, new string[] { _value }); base.OnResultExecuting(context); } } }
特性允许过滤器接收参数,如下例所示。
可将此特性加诸控制器(Controller)或 Action 方法,并为其指定所需 HTTP 头的名称和值,并将该 HTTP 头加入响应中:
[AddHeader("Author", "Steve Smith @ardalis")] public class SampleController : Controller { public IActionResult Index() { return Content("Examine the headers using developer tools."); } }
以下几种过滤器接口可以自定义为相应特性的实现。
过滤器特性:
- ActionFilterAttribute
- ExceptionFilterAttribute
- ResultFilterAttribute
- FormatFilterAttribute
- ServiceFilterAttribute
- TypeFilterAttribute
异常过滤器
异常过滤器 实现了 IExceptionFilter 接口或 IAsyncExceptionFilter 接口。
异常过滤器用于处理「未处理异常」,包括发生在 Controller 创建及 模型绑定 期间出现的异常。
它们只在管道内发生异常时才会被调用。它们提供了一个单一的位置实现应用程序内的公共异常处理策略。
框架提供了抽象的 ExceptionFilterAttribute ,你根据自己的需要继承这个类。
异常过滤器适用于捕获 MVC Action内出现的异常,但它们不及错误处理中间件(error handling middleware)灵活。
一般来讲优先使用中间件,只有在需要做一些基于所选 MVC Action 的、有别于错误处理的工作时才选择使用过滤器。
异常过滤器不应有两个事件(对于前置或后置而言),它们只实现 OnException (或 OnExceptionAsync)。 以参数形式传入 OnException 的 ExceptionContext 包含了所发生的 Exception。 如果把 context.Exception 设置为 null,其效果相当于你已处理该异常, 所以该次请求会像没发生过异常那样继续处理(一般会返回 HTTP 200 OK 状态)
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ViewFeatures; namespace FiltersSample.Filters { public class CustomExceptionFilterAttribute : ExceptionFilterAttribute { private readonly IHostingEnvironment _hostingEnvironment; private readonly IModelMetadataProvider _modelMetadataProvider; public CustomExceptionFilterAttribute(IHostingEnvironment hostingEnvironment,IModelMetadataProvider modelMetadataProvider) { _hostingEnvironment = hostingEnvironment; _modelMetadataProvider = modelMetadataProvider; } public override void OnException(ExceptionContext context) { if (!_hostingEnvironment.IsDevelopment()) { // do nothing return; } var result = new ViewResult {ViewName = "CustomError"}; result.ViewData = new ViewDataDictionary(_modelMetadataProvider,context.ModelState); result.ViewData.Add("Exception", context.Exception); // TODO: Pass additional detailed data via ViewData context.ExceptionHandled = true; // mark exception as handled context.Result = result; } } }
Action 过滤器
Action 过滤器 要么实现 IActionFilter 接口,要么实现 IAsyncActionFilter 接口,它们可以在 action 方法执行的前后被执行。 Action 过滤器非常适合放置诸如查看模型绑定结果、或是修改控制器或输入到 action 方法的逻辑。 另外,action 过滤器可以查看并直接修改 action 方法的结果。 OnActionExecuting 方法在1action方法执行之前运行,因此它可以通过改变 ActionExecutingContext.ActionArguments 来控制 action 的输入, 或是通过 ActionExecutingContext.Controller 控制控制器(Controller)。 OnActionExecuting 方法可以通过设置ActionExecutingContext.Result 来短路 action 方法的操作及其后续的过滤器。 OnActionExecuting 方法通过抛出异常也可以阻止 action 方法和后续过滤器的处理,但会当做失败(而不是成功)的结果来处理。 OnActionExecuted 方法在 action 方法执行之后才执行,并且可以通过 ActionExecutedContext.Result属性查看或控制 action 的结果。 如果 action 在执行时被其它过滤器短路,则 ActionExecutedContext.Canceled 将会被置为 true。 如果 action 或后续的 action 过滤器抛出异常,则 ActionExecutedContext.Exception 会被设置为一个非空值。 有效「处理」完异常后把 ActionExecutedContext.Exception 设置为 null,那么 ActionExectedContext.Result 会像从 action 方法正常返回值那样被处理。 对于 IAsyncActionFilter 接口来说,它的 OnActionExecutionAsync 方法结合了 OnActionExecuting 和OnActionExecuted 的所有能力。 调用 await next() 后,ActionExecutionDelegate 将会执行所有的后续 action 过滤器以及 action 方法,并返回 ActionExecutedContext 。 如果想要在 OnActionExecutionAsync 内部短路,那么就为 ActionExecutingContext.Result 分配一个结果实例,并且不要调用 ActionExecutionDelegate 即可
资源过滤器
资源过滤器 要么实现 IResourceFilter
接口,要么实现 IAsyncResourceFilter
接口,它们执行于大多数过滤器管道(只有 授权过滤器 在其之前运行,
其余所有过滤器以及 Action 处理均出现在其 OnResourceExecuting
和 OnResourceExecuted
方法之间)。
当你需要短路绝大多数正在进行的请求时,资源过滤器特别有用。资源过滤器的一个典型例子是缓存,如果响应已经被缓存,过滤器会立即将之置为结果以避免后续 Action 的多余操作过程。
上面所说的是一个 短路资源过滤器 的例子。下例是一个非常简单的缓存实现(请勿将之用于生产环境),只能与 ContentResult
配合使用,如下所示:
public class NaiveCacheResourceFilterAttribute : Attribute,IResourceFilter { private static readonly Dictionary<string, object> _cache = new Dictionary<string, object>(); private string _cacheKey; public void OnResourceExecuting(ResourceExecutingContext context) { _cacheKey = context.HttpContext.Request.Path.ToString();//先取出来路径 if (_cache.ContainsKey(_cacheKey)) { var cachedValue = _cache[_cacheKey] as string; if (cachedValue != null) { context.Result = new ContentResult(){ Content = cachedValue }; } } } public void OnResourceExecuted(ResourceExecutedContext context) { if (!String.IsNullOrEmpty(_cacheKey) && !_cache.ContainsKey(_cacheKey)) { var result = context.Result as ContentResult; if (result != null) { _cache.Add(_cacheKey, result.Content); } } } }
在 OnResourceExecuting 中,如果 结果已经在静态字段缓存中,Result 属性将被设置到 context 上,
同时 Action 被短路并返回缓存的结果。在 OnResourceExecuted 方法中,如果当前其请求的键未被使用过,
那么 Result 就会被保存到缓存中,用于之后的请求。
如下所示,把这个过滤器用于类或方法之上:
[TypeFilter(typeof(NaiveCacheResourceFilterAttribute))] public class CachedController : Controller { public IActionResult Index() { return Content("This content was generated at " + DateTime.Now); } }