過濾器,從咱們開始開發 Asp.Net 應用程序開始,就一直伴隨在咱們左右;Asp.Net Core 提供多種類型的過濾器,以知足多種多樣的業務應用場景;而且在 Asp.Net Core 自己,過濾器的應用也很是普遍;可是,在實際的業務場景中,大部分開發人員只使用到其中 1 到 2 種類型,固然,這其中大部分可能性是因爲業務場景的適用性使然,本文嘗試簡單介紹 Asp.Net Core 中提供的各類過濾器,以及實際的應用場景,但願對您有所幫助。git
過濾器的做用範圍
每種不一樣的過濾器都有實際的做用範圍,有一些全局過濾器還有做用域的限制,這取決於應用開發者在定義和初始化過濾器的時候的選擇,每一個過濾器自己處理任務的權限和功能都大不相同,可是他們都有一個共同點,就是經過特性標記的方式使用,好比如下代碼,對一個 Action 使用了過濾器 CustomerActionFiltergithub
[CustomerActionFilter] public ActionResult<string> Get(int id) { return "value"; }
原理解釋
過濾器通常在 Asp.Net Core MVC 管道內運行,通常在操做執行以前(befor) 或者執行以後(after) 執行,以供開發者能夠選擇在不一樣的執行階段介入處理api
類型介紹
上圖既是 Asp.Net Core 內置的各類過濾器類型,也是其執行優先級順序,相同類型的過濾器還能夠定義在某個階段執行的順序跨域
- 受權過濾器 AuthorizeAttribute
- 資源過濾器 IResourceFilter
- 異常過濾器 IExceptionFilter
- 操做過濾器 ActionFilterAttribute
- 結果過濾器 IResultFilter
3.1 使用介紹數組
在請求到達的時候最早執行,優先級最高,主要做用是提供用戶請求權限過濾,對不知足權限的用戶,能夠在過濾器內執行拒絕操做,俗稱「管道短路」
*注意:該過濾器只有執行以前(befor),沒有執行以後(after)的方法
一般狀況下,不須要自行編寫過濾器,由於該過濾器在 Asp.Net Core 內部已經有了默認實現,咱們須要作的就是配置受權策略或者實現本身的受權策略,而後由系統內置的受權過濾器調用受權策略便可
必須將該過濾器內部可能出現的異常所有處理,由於在受權過濾器以前,沒有任何組件可以捕獲受權過濾器的異常,一旦受權管理器內部發生異常,該異常將直接輸出到結果中緩存
3.2 應用場景服務器
受權管理器 AuthorizeAttribute 位於 命名空間 Microsoft.AspNetCore.Authorization 內,使用方式很是簡單,查看如下代碼app
[Authorize] [Route("api/[controller]")] public class UserController : Controller { [AllowAnonymous] [HttpGet] public ActionResult<string> Get() { return "default"; } [HttpPost] public ActionResult<string> Post() { return "default"; } }
UserController 被應用了 Authorize 特性進行標記,表示對該控制器內的任意操做執行受權驗證;可是單獨對 Get 操做進行了受權經過對標記,即 AllowAnonymous ,表示容許匿名訪問
這是很是經常使用的作法,在受權應用中,經常須要對部分操做進行單獨的受權策略
關於受權過濾器,先介紹到這裏,下一篇單獨對受權過濾器進行演示,由於關於這塊的內容,要講的實在是太多了異步
但請求進入,經過受權過濾器後,接下來將執行資源過濾器(若是有定義),使用資源過濾器甚至能夠改變綁定模型,還能夠在資源過濾器中實現緩存以提升性能async
4.1 資源管理器實現自接口 IResourceFilter 或者 IAsyncResourceFilter,如今咱們來實現一個資源過濾器,輸出一行信息,看看執行順序
public class CustomerResourceFilter : Attribute, IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { Console.WriteLine("==== OnResourceExecuted"); } public void OnResourceExecuting(ResourceExecutingContext context) { Console.WriteLine("==== OnResourceExecuting"); } }
4.2 對 HomeController 的操做應用該資源過濾器,看看對一個操做同時應用 CustomerActionFilter 和 CustomerResourceFilter ,他們的執行順序是什麼
[Route("api/[controller]")] [ApiController] public class HomeController : ControllerBase { [HttpGet] [CustomerActionFilter] [CustomerResourceFilter] public async Task<ActionResult<IEnumerable<string>>> Get() { return new string[] { "value1", "value2" }; } }
4.3 啓動程序,訪問 http://localhost:5000/api/home,輸出結果以下
能夠看到,執行順序和開篇的第一張圖例一致,首先執行時資源過濾器的 OnResourceExecuting 方法,接着請求接入了 操做過濾器的 OnActionExecuting 方法,最後執行操做過濾器的 OnResultExecuting 方法,而後把請求交給資源過濾器的 OnResourceExecuted,最後返回到客戶端
因此,從執行順序能夠看出,資源管理器的執行優先級老是高於操做過濾器
資源過濾器能夠應用於控制器或者操做,而後基於其執行優先級的特色,開發員人員能夠在資源過濾器中定義某些靜態資源或者緩存直接將數據返回給客戶端,並使其執行短路操做,減小後續管道請求步驟,以提升服務器響應性能
在服務器向客戶端寫入響應內容以前,若是系統引起了異常,異常過濾器能夠捕獲該異常,該過濾器做用於全局範圍,這也是最經常使用的過濾器
5.1 建立一個異常過濾器
public class CustomerExceptionFilter : Attribute, IExceptionFilter { public void OnException(ExceptionContext context) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("發生了異常:{0}", context.Exception.Message); Console.ForegroundColor = ConsoleColor.Gray; } }
5.2 將 CustomerExceptionFilter 應用到 HomeController 上
請注意,HomeController 上還同時應用了資源過濾器;如今要作到就是在資源過濾器內部拋出異常,看看 CustomerExceptionFilter 是否能夠捕獲該異常
public class CustomerResourceFilter : Attribute, IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { Console.WriteLine("==== OnResourceExecuted"); } public void OnResourceExecuting(ResourceExecutingContext context) { Console.WriteLine("==== OnResourceExecuting"); throw new Exception("資源管理器發生了異常"); } }
5.3 運行程序,訪問 http://localhost:5000/api/home
能夠看到,系統拋出了異常;可是,異常過濾器 CustomerExceptionFilter 並無捕獲該異常,事實證實資源過濾器的執行優先級仍是高於異常過濾器,如今咱們嘗試在操做內部引起異常
[Route("api/[controller]")] [ApiController] [CustomerResourceFilter] [CustomerExceptionFilter] public class HomeController : ControllerBase { // GET api/values [HttpGet] [CustomerActionFilter] public async Task<ActionResult<IEnumerable<string>>> Get() { throw new Exception("Get操做發生了異常"); return new string[] { "value1", "value2" }; } }
5.4 再次啓動程序,訪問 http://localhost:5000/api/home,控制檯輸出結果以下
5.5 客戶端獲得了一個友好的返回值
5.6 這是由於咱們在異常過濾器內部將異常進行了出來,並經過設置 context.ExceptionHandled = true 來標記表示異常已經被處理,而後輸出友好信息
public class CustomerExceptionFilter : Attribute, IExceptionFilter { public void OnException(ExceptionContext context) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("發生了異常:{0}", context.Exception.Message); Console.ForegroundColor = ConsoleColor.Gray; context.Result = new JsonResult(new { code = 500, message = context.Exception.Message }); context.ExceptionHandled = true; } }
異常過濾器的應用很是簡單,你能夠在其內部將異常寫入日誌,或者執行其它須要處理的邏輯
- 操做過濾器:當請求進入 API 接口的時候,操做過濾器提供了一個進入以前(before)和進入以後(after)介入功能,可使用該過濾器對進入 API 的參數和結果進行干預
- 結果過濾器:這個過濾器的做用和操做過濾器很是類似,主要其做用範圍是有微小區別的,結果過濾器是在操做即將返回結果到客戶端以前(before)或者以後(after)執行干預,好比你能夠在返回結果以後(after)去渲染視圖
6.1 之因此將這兩個過濾器放在一塊兒講,是由於,這兩個過濾器就像一對孿生兄弟同樣,正所謂善始善終,首先來看操做過濾器
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public abstract class ActionFilterAttribute : Attribute, IActionFilter, IFilterMetadata, IAsyncActionFilter, IResultFilter, IAsyncResultFilter, IOrderedFilter { protected ActionFilterAttribute(); // public int Order { get; set; } // public virtual void OnActionExecuted(ActionExecutedContext context); // public virtual void OnActionExecuting(ActionExecutingContext context); // [AsyncStateMachine(typeof(<OnActionExecutionAsync>d__6))] public virtual Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next); // public virtual void OnResultExecuted(ResultExecutedContext context); // public virtual void OnResultExecuting(ResultExecutingContext context); // [AsyncStateMachine(typeof(<OnResultExecutionAsync>d__9))] public virtual Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next); }
操做過濾器包含 6 個基礎方法,分別是執行前(before)執行後(after),寫入結果前(before)寫入後(after)
爲何會這樣呢,由於操做過濾器實現的接口中包含告終果過濾器的接口
根據官方的提示,若是你須要重寫 ActionFilterAttribute 的方法以處理自定義的業務邏輯,那麼 OnActionExecutionAsync 這個異步方法不該該和 執行前(before)執行後(after)同時共存
一樣,寫入結果前(before)寫入後(after)和 OnResultExecutionAsync 也是同樣
6.2 操做過濾器包含了 寫入結果前(before)寫入後(after)的方法,這使得咱們能夠不用去定義結果過濾器就能夠實現對寫入結果的管理
固然,最好的作法是定義結果過濾器,這有助於業務分類,且邏輯清晰明瞭,可是若是你但願可使用異步操做,很遺憾,結果過濾器不支持該方法
6.3 下面來看結果過濾的定義
public class CustomerResultFilter : Attribute, IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("OnResultExecuted"); } public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("OnResultExecuting"); } }
代碼很是簡單,就是實現接口 IResultFilter
IResultFilter 的工做原理和操做過濾器的寫入結果前(before)寫入後(after)的方法執行一致,能夠看到,他們兩個方法和參數名稱都是一致的,由於他們都是實現同一個接口 IResultFilter
6.4 利用結果過濾器實現對輸出結果的干預
下面就簡單在結果過濾器內部去對已經組織好的數據進行干預,HomeController.Get 方法本應該輸出 一個數組,咱們在Header 中增長一項輸出:Author=From Ron.liang
public class CustomerResultFilter : Attribute, IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { // ToDo } public void OnResultExecuting(ResultExecutingContext context) { // 干預結果 context.HttpContext.Response.Headers.Add("Author", "From Ron.liang"); } }
6.5 輸出結果
在上面介紹的各類各樣的過濾器中,有時候咱們可能須要讀取程序運行環境的信息,根據不一樣的環境作出不一樣的響應內容
好比,上面的結果過濾器寫入做者信息,可能咱們只但願在開發環境輸出,而在產品環境忽略
7.1 使用 GetService,以支持依賴注入
public void OnResultExecuting(ResultExecutingContext context) { var env = (IHostingEnvironment)context.HttpContext.RequestServices.GetService(typeof(IHostingEnvironment)); Console.ForegroundColor = ConsoleColor.Blue; Console.WriteLine("OnResultExecuting,{0}", env.EnvironmentName); Console.ForegroundColor = ConsoleColor.Gray; // 干預結果 if (env.IsDevelopment()) context.HttpContext.Response.Headers.Add("Author", "From Ron.liang"); }
上面的從 context.HttpContext.RequestServices.GetService(typeof(IHostingEnvironment)) 獲取了環境變量,並判斷在開發環境下爲響應頭添加內容
7.2 在過濾器中使用中間件
Asp.Net Core 提供了一個功能,使得咱們在過濾器中可使用中間件,實際上,這二者的使用方式很是相似
若是你但願這麼作,能夠定義一個包含 Configure(IApplicationBuilder applicationBuilder) 方法的類,在控制器或者操做中使用它
7.3 定義註冊管理管道類
public class RegisterManagerPipeline { public void Configure(IApplicationBuilder applicationBuilder) { CookieAuthenticationOptions options = new CookieAuthenticationOptions(); applicationBuilder.UseCors(config => { config.AllowAnyOrigin(); }); } }
RegisterManagerPipeline 定義了一個 Configure 方法,在該方法內部執行一個跨域設置,表示容許任何來源訪問該站點;而後,咱們在 UserController 中應用該管道
[Authorize] [Route("api/[controller]")] [MiddlewareFilter(typeof(RegisterManagerPipeline))] public class UserController : Controller { // GET: api/<controller> [AllowAnonymous] [HttpGet] public ActionResult<string> Get() { return "default"; } }
應用方式很是簡單,就像使用普經過濾器同樣對控制器進行特性標記便可
所不一樣的是,這裏使用的是 MiddlewareFilter 進行註冊 RegisterManagerPipeline
管道式過濾器的優先級很是高,甚至比受權過濾器的優先級還高,在使用的時候須要特別注意應用場景
相同類型的過濾器其執行順序可使用 Order 字段進行指定,該值爲一個 int32 類型,值越小表示優先級越高,該值只能做用於相同類型的過濾器
好比,定義了兩個 ActionFilter ,UserNameActionFilter,UserAgeActionFilter,分別制定其 Order 字段值爲 10,5,那麼 UserAgeActionFilter 將會在調用 ,UserNameActionFilter 以前執行
可是,即便指定了 Order ,ActionFilter 的執行優先級也不會超越受權管理器 AuthorizeAttribute,這是設計上的不一樣
8.1 Order 演示代碼
[HttpPost] [UserNameActionFilter(Order = 10)] [UserAgeActionFilter(Order = 5)] public void Post([FromBody] UserModel value) { }
8.2 輸出結果
上圖輸出的黃色部分文字清晰的說明了過濾器的執行順序
顯示執行了資源過濾器,接着執行了 Order=5 的 UserAgeActionFilter ,最後執行了 Order=10 的 UserNameActionFilter 過濾器
能夠看到,雖然操做過濾器設置了 Order=5,但其執行優先級仍然不能超越受權過濾器,甚至沒法超越資源過濾器
本文簡單介紹了 Asp.Net Core 下系統內置的各類各樣的過濾器,分別是
- 受權過濾器 AuthorizeAttribute
- 資源過濾器 IResourceFilter
- 異常過濾器 IExceptionFilter
- 操做過濾器 ActionFilterAttribute
- 結果過濾器 IResultFilter
最後介紹瞭如何在過濾器中使用中間件,以及對過濾器的執行順序進行了詳細的演示
https://github.com/lianggx/EasyAspNetCoreDemo/tree/master/Ron.FilterDemo