1、介绍 api
Asp.Net Core Filter 使得能够在请求处理管道的特定阶段的先后执行代码,咱们能够建立自定义的 filter 用于处理横切关注点。 横切关注点的示例包括错误处理、缓存、配置、受权和日志记录。 filter 使得能够避免重复代码。
缓存
Asp.Net Core 提供了5中过滤器类型,分别是:cookie
一、Authorization filters,受权过滤器是最早执行而且决定请求用户是否通过受权认证,若是请求未获受权,受权过滤器可让管道短路。
mvc
二、Resource filters,资源过滤器在Authorization filter执行完后执行,它有两个方法,OnResourceExecuting能够在filter管道的其他阶段以前运行代码,例如能够在模型绑定以前运行。OnResourceExecuted则能够在filter管道的其他阶段以后运行代码异步
三、Action filters,操做过滤器能够在调用单个操做方法以前和以后当即运行代码,它能够用于处理传入某个操做的参数以及从该操做返回的结果。 须要注意的是,Aciton filter不能够在 Razor Pages 中使用。async
四、Exception filters,异常过滤器经常被用于在向响应正文写入任何内容以前,对未经处理的异常应用全局策略。ide
五、Result filters,结果过滤器能够在执行单个操做结果以前和以后运行代码。 可是只有在方法成功执行时,它才会运行。函数
至于它们在filter管道中的交互方式能够看下图。ui
2、实现this
Asp.Net Core提供了同步和异步两种filter接口定义,经过实现不一样的接口能够实现同步或异步的过滤器,可是若是一个类同时实现了同步和异步的接口,Asp.Net Core的filter 管道只会调用异步的方法,即异步优先。具体的作法能够参考微软官方文档 https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters?view=aspnetcore-2.2。
如下是IActionFilter和IAsyncActionFilter的实现
public class MySampleActionFilter : IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { // Do something before the action executes. } public void OnActionExecuted(ActionExecutedContext context) { // Do something after the action executes. } } public class SampleAsyncActionFilter : IAsyncActionFilter { public async Task OnActionExecutionAsync( ActionExecutingContext context, ActionExecutionDelegate next) { // Do something before the action executes. // next() calls the action method. var resultContext = await next(); // resultContext.Result is set. // Do something after the action executes. } }
除了直接实现Filter接口,Asp.Net Core同时提供了一些基于特性的过滤器,咱们能够继承相应的特性来实现自定义的过滤器。这些特性包括ActionFilterAttribute、ExceptionFilterAttribute、ResultFilterAttribute、FormatFilterAttribute、ServiceFilterAttribute、TypeFilterAttribute。
下面是微软文档的一个例子,在Result Filter给响应添加Header。
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); } } [AddHeader("Author", "Joe Smith")] public class SampleController : Controller { public IActionResult Index() { return Content("Examine the headers using the F12 developer tools."); } [ShortCircuitingResourceFilter] public IActionResult SomeResource() { return Content("Successful access to resource - header is set."); } }
3、Filter 的做用域和执行顺序
能够将咱们自定义的filter添加到咱们的代码中进行调用,添加的方式有三种,对应其三个做用域:在Action上添加特性、在Controller上添加特性和添加全局filter。
下面先来看下添加全局filter的作法,咱们知道,在Asp.Net MVC中,filter都是在控制器实例化以后才能生效的,其Filter应该是肯定的,不能被注入参数,可是到了Asp.Net Core,全局注册是在ConfigureServices方法中完成的,它能够被注入参数。
public void ConfigureServices(IServiceCollection services) { services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); services.AddMvc(options => { options.Filters.Add(new AddHeaderAttribute("name", "Jesen")); options.Filters.Add(typeof(AddLogActionAttribute)); options.Filters.Add(typeof(ExceptionHandlerAttribute)); }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2); }
上述代码在addMvc中将咱们自定义的 filter 添加到全局当中,这样对全部的控制器和action都产生了做用。而其余两种做用域的用法就是直接将特性添加到其上面。
[AddHeader("", "")] public class HomeController : Controller { [AddLogAction] public IActionResult Index() { return View(); } }
那么这三种做用域的默认执行顺序是怎样的,下图很好的进行了展现。
那么咱们是否能够改变其默认执行顺序呢,答案固然是确定的。咱们能够经过实现 IOrderFilter来重写默认执行序列。 IOrderedFilter
公开了Order 属性来肯定执行顺序,该属性优先于做用域。 具备较低的 Order
值的 filter 在具备较高的 Order
值的filter以前运行 before 代码,在具备较高的 Order
值的 filter 以后运行 after 代码。具体作法能够在使用构造函数参数时设置Order 属性。
[CustomFilter(Name = "Controller Level Attribute", Order=1)]
在改变了Order属性后的执行顺序以下图所示
4、依赖注入
在前面添加filter事,咱们在全局添加方式中知道了 filter 能够经过类型添加,也能够经过实例添加,经过实例添加,则该实例会被应用于全部的请求,按照类型添加则将激活该类型,这意味着它会为每一个请求建立一个实例,依赖注入将注入全部构造函数的依赖项。
可是若是经过特性添加到Controller或者Action上时,该 filter 不能由依赖注入提供构造函数依赖项,这是由于特性在添加时必须提供它的构造函数参数,这是因为特性的原理决定的。那是否是经过Controller或Action的特性不能有构造函数参数呢,确定不是的,能够经过如下特性来得到依赖注入:ServiceFilterAttribute、TypeFilterAttribute和在特性上实现 IFilterFactory。
namespace FilterDemo.Filter { public class AddLogActionAttribute : ActionFilterAttribute { private readonly ILogger _logger; public AddLogActionAttribute(ILoggerFactory loggerFactory) { this._logger = loggerFactory.CreateLogger<AddLogActionAttribute>(); } public override void OnActionExecuting(ActionExecutingContext context) { string controllerName = (string)context.RouteData.Values["controller"]; string actionName = (string)context.RouteData.Values["action"]; this._logger.LogInformation($"{controllerName}的{actionName}开始执行了...") base.OnActionExecuting(context); } public override void OnActionExecuted(ActionExecutedContext context) { base.OnActionExecuted(context); } } }
上述代码咱们定义了带Logger参数的AddLogActionAttribute Filter,接下来实现怎么在Controller或Action上使用,首先在Startup中添加注入
services.AddScoped<AddLogActionAttribute>();
而后在Controller或Action上使用 ServiceFilter
[ServiceFilter(typeof(AddLogActionAttribute))] public class HomeController : Controller
TypeFilterAttribute与ServiceFilterAttribute相似,但不会直接从 DI 容器解析其类型。 它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 对类型进行实例化。由于不会直接从 DI 容器解析 TypeFilterAttribute
类型,因此使用 TypeFilterAttribute
引用的类型不须要注册在 DI 容器中。
[TypeFilter(typeof(AddLogActionAttribute))] public IActionResult Index() { return View(); }
5、Resource Filter
最后,咱们来看一下Asp.Net Core不一样于以前Asp.Net MVC的 ResourceFilter,在上述介绍中,咱们知道了Resource Filter在Authorization Filter执行以后执行,而后才会去实例化控制器,那么Resource Filter 就比较适合用来作缓存,接下来咱们自定义一个Resource Filter。
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FilterDemo.Filter { public class CacheResourceFilterAttribute : Attribute, IResourceFilter { private static readonly Dictionary<string, object> _Cache = new Dictionary<string, object>(); private string _cacheKey; /// <summary> /// 控制器实例化以前 /// </summary> /// <param name="context"></param> public void OnResourceExecuting(ResourceExecutingContext context) { _cacheKey = context.HttpContext.Request.Path.ToString(); if (_Cache.ContainsKey(_cacheKey)) { var cachedValue = _Cache[_cacheKey] as ViewResult; if (cachedValue != null) { context.Result = cachedValue; //设置该Result将是filter管道短路,阻止执行管道的其余阶段 } } } /// <summary> /// 把请求都处理完后执行 /// </summary> /// <param name="context"></param> public void OnResourceExecuted(ResourceExecutedContext context) { if (!String.IsNullOrEmpty(_cacheKey) && !_Cache.ContainsKey(_cacheKey)) { var result = context.Result as ViewResult; if (result != null) { _Cache.Add(_cacheKey, result); } } } } }
将其添加到HomeController的Index上
[CacheResourceFilter] [TypeFilter(typeof(AddLogActionAttribute))] public IActionResult Index() { return View(); }
运行能够发现第一次请求按照默认顺序执行,第二次请求会在Cache中查找该请求路径是否已经在Cache当中,存在则直接返回到Result,中断了请求进入管道的其余阶段。