本篇文章介紹過濾器以下幾點知識點
1、什麼是過濾器
2、過濾器的執行流程
3、過濾器的作用域
4、過濾器的工作原理
5、過濾器的5種類型
6、取消和短路
7、過濾器的注入寫法
8、同種過濾器自定義順序
1、什麼是過濾器?
.NET 中的過濾器(Filter)是 AOP(面向切面編程) 思想的一種實現,供我們在執行管道的特定階段執行代碼,通過使用過濾器可以實現 短路請求、緩存請求結果、日誌統一記錄、參數合法性驗證、異常統一處理、返回值格式化 等等,同時使業務代碼更加簡潔單純,避免很多重複代碼。
2、MVC中過濾器執行流程
通過這個流程呢,我們可以看到它的執行順序
從網絡請求進來開始,先執行的是其他中間件(含自定義中間件,後面我們再另外寫篇關於中間件應用的)---》路由中間件(MVC自帶,路由選擇)--》控制器中的Action--》然後纔會執行過濾器。
反向的,當我們action中執行完業務代碼後,請求一層層原路返回。
所以在我們的過濾器中,大部分過濾器有開始執行action,即ing 狀態的方法,也有action業務代碼執行完後觸發的ed狀態的方法。後面看例子就明白了
3、過濾器作用域
過濾器作用域設置是非常靈活的,可以選擇爲:
- 全局有效(整個 MVC 應用程序下的每一個 Action);
- 僅對某些 Controller 有效 (控制器內所有的 Action );
- 僅對某些 Action 有效;
4、過濾器的工作原理
過濾器一般在 Asp.Net Core MVC 管道內運行,一般在操作執行之前(befor) 或者執行之後(after) 執行,以供開發者可以選擇在不同的執行階段介入處理。
5、過濾器的類型
MVC中默認定義的有以下5中類型的過濾器
授權過濾器 AuthorizeAttribute
資源過濾器 IResourceFilter
異常過濾器 IExceptionFilter
操作過濾器 ActionFilterAttribute
結果過濾器 IResultFilter
5中過濾器的對比如下圖:
同時,表中的過濾器從上到下,基本也是這5種過濾器的執行順序。
我們可以同時定義多個甚至是5個類型的過濾器,不同類型的過濾器之間也存在這執行順序的先後。
上圖,就是5種過濾器在一個完整請求到響應的執行個過程。
通過順序,我們可以看到它的執行先後(同一個過濾器,箭頭有出有入的,表示有執行前(ing)和執行後(ed)兩種狀態的方法)
請求--》中間件(Middleware)--》授權過濾器--》資源過濾器ing--》Action 操作過濾器ing--》執行Action中的業務代碼--》業務代碼執行完,執行Action操作過濾器ed--》執行Result 結果過濾器--》執行資源過濾器ed方法--》返回中間件--》響應請求。
這是一個完整的路徑。
還有另一條路徑,差別在於當在Action中拋出異常時,不再執行Action 操作過濾器ed方法,而是執行異常過濾器(只有一個方法)--》執行資源過濾器ed方法--返回中間件--》響應請求
也就是當拋異常時,就不會執行action的ed方法和result過濾器了。
下面我們介紹5中過濾器的具體代碼應用
MVC中過濾器都在命名空間:Microsoft.AspNetCore.Mvc.Filters 當中
所有過濾器我們都同時繼承Attribute,方便我們把它當做特性來使用。
A:授權過濾器
授權過濾器,主要用於權限驗證,驗證當前是否登錄,是否有權限訪問某個資源。
授權過濾器在過濾器管道中第一個被執行,通常用於驗證請求的合法性(通過實現接口 IAuthorizationFilter or IAsyncAuthorizationFilter) 在請求到達的時候最先執行,優先級最高,
主要作用是提供用戶請求權限過濾,對不滿足權限的用戶,可以在過濾器內執行拒絕操作,俗稱“管道短路”。
*注意:該過濾器只有執行之前(befor),沒有執行之後(after)的方法 通常情況下,不需要自行編寫過濾器,因爲該過濾器在 Asp.Net Core 內部已經有了默認實現,我們需要做的就是配置授權策略或者實現自己的授權策略,然後由系統內置的授權過濾器調用授權策略即可 必須將該過濾器內部可能出現的異常全部處理,因爲在授權過濾器之前,沒有任何組件能夠捕獲授權過濾器的異常,一旦授權管理器內部發生異常,該異常將直接輸出到結果中。
我們新建一個MVC項目,新建一個Filter 文件夾,存放我們定義的過濾器類。
項目環境,NETCore 3.1
定義一個授權過濾器,我們需要繼承 IAuthorizationFilter
public class MyAuthorizationFilter :Attribute,IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { var reqeust = context.HttpContext.Request; var userId = reqeust.Cookies["userId"]; if(string.IsNullOrWhiteSpace(userId)) { throw new Exception("授權過濾器,您還沒有登錄"); } } }
授權過濾器只有一個方法,通過context對象我們可以獲取當前請求的上下文對象,獲取我們所需要的參數。
示例中,我們直接拋出異常,僅是爲了方便,在實際項目中,我們還是需要友好的處理,此部分內容在下面的過濾器短路中介紹。
B:資源過濾器
資源過濾器在過濾器管道中第二個被執行,通常用於請求結果的緩存和短路過濾器管道(通過實現接口 IResourceFilter or IAsyncResourceFilter)
資源過濾器在實現緩存或短路過濾器管道尤其有用。
//我們以緩存爲例 public class MyResourceFilter : Attribute, IResourceFilter { private readonly IMemoryCache _memoryCache; public MyResourceFilter(IMemoryCache memoryCache) { _memoryCache = memoryCache; } public void OnResourceExecuted(ResourceExecutedContext context) { //我們可以將當前的結果context.Result緩存起來,當執行ing時,直接返回,爲了方便示例演示,我們用時間表示。 string content = "第一次的時間:" + DateTime.Now; _memoryCache.Set("key", content); } public void OnResourceExecuting(ResourceExecutingContext context) { var content = _memoryCache.Get("key"); //判斷內存中是否有內容,有就直接返回,不再執行action過程。 if (content != null) { context.Result = new ContentResult() { Content= content.ToString()}; } } }
在實際的項目中,資源過濾器我們比較少會用到。
一般緩存我們都在action中用redis來實現。那在資源過濾器中做資源緩存,有啥好處呢?
節省資源開支,提高性能。
因爲resourceing後面的就不用再執行了,通過context.Result直接返回,把整個請求短路了,後面的過程就不再執行了。
C:操作過濾器
Action 過濾器 要麼實現 IActionFilter 接口,要麼實現 IAsyncActionFilter 接口,它們可以在 action 方法執行的前後被執行。
Action 過濾器非常適合放置諸如查看模型綁定結果、或是修改控制器或輸入到 action 方法的邏輯。
另外,action 過濾器可以查看並直接修改 action 方法的結果,即,可以對輸出結果進行修改。
Action過濾器有兩個方法,一個是action執行之前,一個是之後。他的應用 場景也很多。
比如我們寫請求日誌吧,一般我們需要記錄時間、請求的客戶端信息、請求的路由,當前控制器名稱、action名稱等。那就在action中實現吧。
public class MyActionFilter : Attribute, IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { //有時我們視圖頁上需要顯示當前登錄賬號的名稱,我們可以通過下面代碼獲取,設置到ViewData對象中,視圖中直接使用ViewData即可顯示,替代傳統的定義基類,每個控制器都要繼承基類的寫法。這也就是AOP啊 var controller = context.Controller as Controller; controller.ViewData["name"] = "張三"; } public void OnActionExecuting(ActionExecutingContext context) { //下面是演示怎麼獲取路由信息,context 對象裏面還有更多驚喜,慢慢發掘吧 var ad = context.ActionDescriptor; //var ar= context.ActionArguments;//請求的參數信息 var str= ad.RouteValues["controller"] + "/" + ad.RouteValues["action"]; //爲了方便演示,這邊直接短路,輸出路由名稱和aciton名稱 // context.Result = new ContentResult() { Content=str}; } }
D:結果過濾器
結果過濾器適用於任何需要直接環繞 View 或格式化處理的邏輯。結果過濾器可以替換或更改 Action 結果(而後者負責產生響應)
一般項目中很少用,這邊不多介紹。
E:異常過濾器
用於實現常見的錯誤處理策略,沒有之前和之後事件,處理 Razor 頁面或控制器創建、模型綁定、操作過濾器或操作方法中發生的未經處理的異常。 但無法捕獲資源過濾器、結果過濾器或 MVC 結果執行中發生的異常 。
這裏很重要的一點就是,異常過濾器它步驟的異常範圍有限,在中間件、或者授權、資源、結果過濾器中產生的異常,是步驟不到的。
也可以簡單的理解爲,它能捕捉的基本就是業務異常。
public class MyExceptionFilter : Attribute, IExceptionFilter { public void OnException(ExceptionContext context) { var ex = context.Exception; //這裏可以寫入異常日誌 } }
6、取消和短路
這裏的取消和短路是同一個意思,不同表達而已。
在過濾器中,我們可以做一些處理,但有時候,我們不希望請求繼續往下走。
比如在授權過濾器中,我們既然知道了該用戶未登錄,是不能做任何處理的,那我們就可以給他短路,直接返回。這樣可以提高我們的代碼性能。
短路過濾器,有兩種方式,其實在上面的代碼示例中,都有用到了
第一種,就是直接拋出異常。但是這種很不友好。
如:
public void OnAuthorization(AuthorizationFilterContext context) { var reqeust = context.HttpContext.Request; var userId = reqeust.Cookies["userId"]; if(string.IsNullOrWhiteSpace(userId)) { throw new Exception("授權過濾器,您還沒有登錄"); } }
第二種就是使用context.Result直接返回結果。
如:
public void OnResourceExecuting(ResourceExecutingContext context) { var content = _memoryCache.Get("key"); //判斷內存中是否有內容,有就直接返回,不再執行action過程。 if (content != null) { context.Result = new ContentResult() { Content= content.ToString()}; } }
這裏的Result是指MVC中自帶的幾種視圖的Result類型,包含如下圖的類型
7、過濾器的注入方式
過濾器應該說有兩種注入方式
一種是針對Controller控制器或者Action行爲,直接加特性。表示控制範圍爲該控制器或者該Action
一種是全局的,直接在stratup.cs中全局注入。
第一種,就需要我們上面說的,過濾器要繼承Attribute 特性,這樣我們纔可以當做特性使用。
比如
[MyResultFilter] public IActionResult Privacy() { return View(); }
第二種呢,直接上代碼
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(options=> { options.Filters.Add<Demo3.Filter.MyActionFilter>(); }); }
當時在實際使用中,我們會碰到一些情況,就是過濾器中帶有帶參的構造參數。
這時候的參數,就需要由注入端進行寫入,通過DI依賴注入的方式獲取。
我們定義一個Student學生類爲例,然後將這個類注入。
public class Student { public string Name { get; set; } }
services.AddTransient<Student>();
public class MyResultFilter : Attribute, IResultFilter { private readonly Student _stu; /// <summary> /// 帶參構造函數,實現對name的賦值 /// </summary> /// <param name="name"></param> public MyResultFilter(Student stu) { _stu = stu; } public void OnResultExecuted(ResultExecutedContext context) { } public void OnResultExecuting(ResultExecutingContext context) { context.Result = new ContentResult() { Content = _stu.Name }; } }
這時候,我們想在action上寫MyResultFilter ,按上面的方式就不行了
也不能如下這麼寫
因爲這個變量可能在其他業務邏輯中獲取的
那這時,我們需要這麼寫
[TypeFilter(typeof(MyResultFilter))] public IActionResult Privacy() { return View(); }
後者全局,這麼寫
services.AddControllersWithViews(options=> { options.Filters.Add(typeof(Demo3.Filter.MyResultFilter)); });
8、同種過濾器的自定義順序
上面我們講到了,不同過濾器之間,有執行先後順序,這個是MVC自帶的
但是對於我們自定義的過濾器,也存在執行先後順序的問題
比如我們引用了某個第三方組件,裏面有一個action 過濾器了,這時候我們想優先執行我們自己定義的過濾器,或者都是我們自定義的過濾器,要有先後順序,應該怎麼寫呢?
其實也很簡單,定義過濾器時,繼承 IOrderedFilter
public class MyResultFilter : Attribute, IResultFilter,IOrderedFilter { private readonly Student _stu; /// <summary> /// 帶參構造函數,實現對name的賦值 /// </summary> /// <param name="name"></param> public MyResultFilter(Student stu) { _stu = stu; } public int Order { get; set; } public void OnResultExecuted(ResultExecutedContext context) { } public void OnResultExecuting(ResultExecutingContext context) { context.Result = new ContentResult() { Content = _stu.Name }; } }
在控制器或視圖中我們可以這麼寫
[TypeFilter(typeof(MyResultFilter),Order =1)] public IActionResult Privacy() { return View(); }
services.AddControllersWithViews(options=> { options.Filters.Add(typeof(Demo3.Filter.MyResultFilter),1); });
這樣就可以實現了過濾器的順序配置。
以上就是介紹的全部內容。
其他類似文章,這篇還不錯:https://blog.csdn.net/cplvfx/article/details/118118104
更多分享,請大家關注我的個人公衆號: