MVC 的過濾器(Filters)也翻譯為“篩選器”。但是老周更喜歡翻譯為“過濾器”,意思上更好理解。 既然都叫過濾器了,就是在MVC的操作方法調用前後進行特殊處理的類型。比如: a、此調用是否已授權? b、在模型綁定之前要不要修改數據源?(可能含有兒童不宜的數據) c、在調用MVC方法前要不要改一 ...
MVC 的過濾器(Filters)也翻譯為“篩選器”。但是老周更喜歡翻譯為“過濾器”,意思上更好理解。
既然都叫過濾器了,就是在MVC的操作方法調用前後進行特殊處理的類型。比如:
a、此調用是否已授權?
b、在模型綁定之前要不要修改數據源?(可能含有兒童不宜的數據)
c、在調用MVC方法前要不要改一改輸入參數?在MVC方法調用之後要不要處理一下結果(加點味精,進一步調味)
d、發生異常後怎麼處理?
過濾器可解決上面一堆提問。
在 ASP.NET Core 的 MVC 框架中,所有過濾器都實現共同介面 IFilterMetadata。該介面空空如也,未定義任何成員。說白了,它的用處是作為一種“記號”。你怎麼證明你就是過濾器,嗯,看看你實現了 IFilterMetadata 介面沒?實現了就認定是過濾器。所以,該介面純粹是個角色標簽。
咱們寫代碼一般不會實現 IFilterMetadata 介面,畢竟裡面什麼卵方法都沒有,怎麼規範類型?因此,過濾器專屬命名空間 Microsoft.AspNetCore.Mvc.Filters 下為我們公開了以下介面,方便開發者實現:
1、IAuthorizationFilter:授權過濾器,它的優先順序最高,總是最先運行。看看你有沒有許可權調用 MVC 方法,若沒許可權,就 See you La La。
2、IResourceFilter:資源過濾器。它在授權過濾成功後、模型綁定前運行。可以檢查一下用於綁定的數據,要不要改一下。
3、IActionFilter:操作方法過濾器,就是針對 MVC Action 的。在操作方法運行前後運行,可以用來修改輸入參數值。
4、IResultFilter:結果過濾器。當 MVC 操作方法運行成功後就會運行,可以用來修改運行結果。比如加點 HTTP 消息頭什麼的。
5、IExceptionFilter:當 MVC 操作方法運行過程中發生異常才會運行,無異常就不會運行。
…… 其實還有的,但這裡咱們先不提,免得大伙搞得頭暈。
過濾器不止一個,同一類型的過濾還可能有多個,因此,它們就像中間件那樣,一個個鏈接起來,形成下水溝,哦不,是調用管道,或叫調用棧。於是,這就出現誰先運行的問題,雖然上面的介紹有說明,不過那太抽象了。任何編程知識只要能用代碼來驗證和觀察,就不用圖表和理論。
下麵,咱們實現上述幾個介面,然後往控制臺上列印一些文本,來看看這些過濾器是怎麼運行的。
public class CustAuthFilter : IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { Console.WriteLine("授權過濾器運行"); } } public class CustResourceFilter : IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { Console.WriteLine("資源過濾器 - " + $"{nameof(OnResourceExecuted)}方法運行"); } public void OnResourceExecuting(ResourceExecutingContext context) { Console.WriteLine("資源過濾器 - " + $"{nameof(OnResourceExecuting)}方法運行"); } } public class CustActionFilter : IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("操作過濾器 - " + $"{nameof(OnActionExecuted)}方法運行"); } public void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("操作過濾器 - " + $"{nameof(OnActionExecuting)}方法運行"); } } public class CustResultFilter : IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("結果過濾器 - " + $"{nameof(OnResultExecuted)}方法運行"); } public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("結果過濾器 - " + $"{nameof(OnResultExecuting)}方法運行"); } }
這裡我沒有實現異常過濾器,只實現了授權、資源、操作方法、結果這幾個有代表性的。
授權過濾器只要實現 OnAuthorization 方法即可。在實現代碼中,可以通過 HttpContext 對象查找授權有關的對象,如果確認是沒有訪問許可權的,可以設置一個自己定 Result 讓 MVC 操作方法的調用終止。
資源過濾器要實現兩個方法:OnResourceExecuting 方法在模型綁定前調用,這時你有機會修改數據源;OnResourceExecuted 方法是在資源過濾之後的其他過濾器運行結束才被調用,即:
ResourceExecuting
........ 剩餘過濾器.......
ResourceExecuted
Action 過濾器也要實現兩個方法:OnActionExecuting 在操作調用前運行;OnActionExecuted 是在操作方法調用後運行。
結果過濾器需要實現兩個方法:OnResultExecuting 方法在操作結果執行前調用,這裡可以修改 MVC 方法返回的值;OnResultExecuted 方法是在操作結果執行之後調用,一般這裡可以改改HTTP嚮應頭、Cookie 什麼的。
其實咱們剛實現的過濾器都是同步版本,這些過濾器都有配套的非同步版本,介面都是以 IAsync 開頭。這裡咱們先不用管同步非同步,避免搞得複雜了。也不要去理會過濾器是全局的還是局部的,下麵咱們統一把它們註冊為全局的。配置方法是通過 MVC 選項類的 Filters 集合,把要用的過濾器添加進去即可。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(options => { // 配置全局過濾器 options.Filters.Add<CustAuthFilter>(); options.Filters.Add<CustResourceFilter>(); options.Filters.Add<CustActionFilter>(); options.Filters.Add<CustResultFilter>(); }); var app = builder.Build();
添加一個“狗頭”控制器,用於測試。
public class GouTouController : Controller { public IActionResult Index() { Console.WriteLine("Index操作運行"); return View(); } }
為了防止 ASP.NET Core 應用程式輸出的日誌干擾咱們查看控制台內容,咱們禁用所有日誌輸出。打開 appsettings.json 文件,把所有日誌類別的記錄級別改為 None。
{ "Logging": { "LogLevel": { "*": "None" } }, "AllowedHosts": "*" }
星號 * 的意思就是代表所有類別的日誌,LogLevel 為 None 就不會輸出日誌了(貌似有個別日誌禁用不了)。
運行程式後,控制台列印出這樣的內容:
這個流程現在是不是很清晰了?咱們畫圖表了,直接這樣表達就好:
Author
Resource Executing
Action Executing
Action Running
Action Executed
Result Executing
Result Running
Result Executed
Resource Executed
局部過濾器的運行過程與全局過濾器相同,如果局部和全局過濾器同時使用,那會發生什麼呢?咱們試試。
接下來我們為授權過濾、資源過濾、操作過濾、結果過濾各創建兩個類——用於局部和全局。實際開發中一般不需要這樣搞,通常全局和局部寫一個類就行,畢竟過濾器類型在全局和局部是通用的。我這裡只為了演示。局部過濾器是通過特性類的方式應用到 MVC 方法上的,所以,局部過濾器除了實現過濾器介面,還要從 Attribute 類派生。
1、實現局部、全局授權過濾器。
// 授權過濾器-局部 [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class MyAuthorFilterAttribute : Attribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { Console.WriteLine("局部:授權過濾器運行"); } } // 授權過濾器-全局 public class GlobAuthorFilter : IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { Console.WriteLine("全局:授權過濾器運行"); } }
2、實現局部、全局資源過濾器。
// 資源過濾器-局部 [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class MyResourceFilterAttribute : Attribute, IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { Console.WriteLine("局部:資源過濾器-Executed"); } public void OnResourceExecuting(ResourceExecutingContext context) { Console.WriteLine("局部:資源過濾器-Executing"); } } // 資源過濾器-全局 public class GlobResourceFilter : IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { Console.WriteLine("全局:資源過濾器-Executed"); } public void OnResourceExecuting(ResourceExecutingContext context) { Console.WriteLine("全局:資源過濾器-Executing"); } }
3、實現局部、全局操作過濾器。
// 操作過濾器-局部 [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class MyActionFilterAttribute : Attribute, IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("局部:操作過濾器-Executed"); } public void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("局部:操作過濾器-Executing"); } } // 操作過濾器-全局 public class GlobActionFilter : IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("全局:操作過濾器-Executed"); } public void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("全局:操作過濾器-Executing"); } }
4、實現局部、全局結果過濾器。
// 結果過濾器-局部 [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class MyResultFilterAttribute : Attribute, IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("局部:結果過濾器-Executed"); } public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("局部:結果過濾器-Executing"); } } // 結果過濾器-全局 public class GlobResultFilter : IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("全局:結果過濾器-Executed"); } public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("全局:結果過濾器-Executing"); } }
先註冊全局過濾器。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(options => { // 添加全局過濾器 options.Filters.Add<GlobActionFilter>(); options.Filters.Add<GlobAuthorFilter>(); options.Filters.Add<GlobResourceFilter>(); options.Filters.Add<GlobResultFilter>(); }); var app = builder.Build();
局部過濾器以特性方式應用於 MVC 操作方法。
public class SpiderController : ControllerBase { [MyResourceFilter] [MyResultFilter] [MyActionFilter, MyAuthorFilter] public IActionResult Index() { Console.WriteLine("Index操作被調用"); return Content("大火燒了毛毛蟲"); } }
和上一個例子一樣,禁用日誌輸出(appsettings.json文件)。
{ "Logging": { "LogLevel": { "*": "None" } }, …… }
程式運行後,控制台列印以下內容:
過濾器按 授權->資源->操作->結果 運行的次序不變。在同種過濾器中,全局過濾器優先運行。
全局授權過濾器 局部授權過濾器 全局資源過濾器 - 前 局部資源過濾器 - 前 全局操作過濾器 - 前 局部操作過濾器 - 前 【調用 MVC 操作方法】 局部操作過濾器 - 後 全局操作過濾器 - 後 全局結果過濾器 - 前 局部結果過濾器 - 前 【執行操作結果】 局部結果過濾器 - 後 全局結果過濾器 - 後 局部資源過濾器 - 後 全局資源過濾器 - 後
另外,有一件事要註意:如果你的控制器的基類是 Controller,那麼,還有優先更高的 Action Filter。看看 Controller 類它實現了啥介面。
public abstract class Controller : ControllerBase, IActionFilter, IFilterMetadata, IAsyncActionFilter, IDisposable
咱們把剛纔的控制器代碼改一下,讓它繼承 Controller 類,並重寫 OnActionExecuting、OnActionExecuted 方法。
public class SpiderController : Controller { …… public override void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("控制器實現的操作過濾器-Executing"); base.OnActionExecuting(context); } public override void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("控制器實現的操作過濾器-Executed"); base.OnActionExecuted(context); } }
然後再次運行程式,控制台將列印以下內容:
看,這個由控制器類實現的 Action 過濾器比全局的還早運行。