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,中斷了請求進入管道的其餘階段。