ASP.NET Core MVC過(guò)濾器運(yùn)行流程解析
MVC 的過(guò)濾器
MVC 的過(guò)濾器(Filters)也翻譯為“篩選器”。但是老周更喜歡翻譯為“過(guò)濾器”,意思上更好理解。
既然都叫過(guò)濾器了,就是在MVC的操作方法調(diào)用前后進(jìn)行特殊處理的類型。比如:
a、此調(diào)用是否已授權(quán)?
b、在模型綁定之前要不要修改數(shù)據(jù)源?(可能含有兒童不宜的數(shù)據(jù))
c、在調(diào)用MVC方法前要不要改一改輸入?yún)?shù)?在MVC方法調(diào)用之后要不要處理一下結(jié)果(加點(diǎn)味精,進(jìn)一步調(diào)味)
d、發(fā)生異常后怎么處理?
過(guò)濾器可解決上面一堆提問(wèn)。
在 ASP.NET Core 的 MVC 框架中,所有過(guò)濾器都實(shí)現(xiàn)共同接口 IFilterMetadata。該接口空空如也,未定義任何成員。說(shuō)白了,它的用處是作為一種“記號(hào)”。你怎么證明你就是過(guò)濾器,嗯,看看你實(shí)現(xiàn)了 IFilterMetadata 接口沒(méi)?實(shí)現(xiàn)了就認(rèn)定是過(guò)濾器。所以,該接口純粹是個(gè)角色標(biāo)簽。
過(guò)濾器專屬命名空間 Microsoft.AspNetCore.Mvc.Filters
咱們寫代碼一般不會(huì)實(shí)現(xiàn) IFilterMetadata 接口,畢竟里面什么卵方法都沒(méi)有,怎么規(guī)范類型?因此,過(guò)濾器專屬命名空間 Microsoft.AspNetCore.Mvc.Filters 下為我們公開了以下接口,方便開發(fā)者實(shí)現(xiàn):
1、IAuthorizationFilter:授權(quán)過(guò)濾器,它的優(yōu)先級(jí)最高,總是最先運(yùn)行??纯茨阌袥](méi)有權(quán)限調(diào)用 MVC 方法,若沒(méi)權(quán)限,就 See you La La。
2、IResourceFilter:資源過(guò)濾器。它在授權(quán)過(guò)濾成功后、模型綁定前運(yùn)行??梢詸z查一下用于綁定的數(shù)據(jù),要不要改一下。
3、IActionFilter:操作方法過(guò)濾器,就是針對(duì) MVC Action 的。在操作方法運(yùn)行前后運(yùn)行,可以用來(lái)修改輸入?yún)?shù)值。
4、IResultFilter:結(jié)果過(guò)濾器。當(dāng) MVC 操作方法運(yùn)行成功后就會(huì)運(yùn)行,可以用來(lái)修改運(yùn)行結(jié)果。比如加點(diǎn) HTTP 消息頭什么的。
5、IExceptionFilter:當(dāng) MVC 操作方法運(yùn)行過(guò)程中發(fā)生異常才會(huì)運(yùn)行,無(wú)異常就不會(huì)運(yùn)行。
…… 其實(shí)還有的,但這里咱們先不提,免得大伙搞得頭暈。
過(guò)濾器不止一個(gè),同一類型的過(guò)濾還可能有多個(gè),因此,它們就像中間件那樣,一個(gè)個(gè)鏈接起來(lái),形成下水溝,哦不,是調(diào)用管道,或叫調(diào)用棧。于是,這就出現(xiàn)誰(shuí)先運(yùn)行的問(wèn)題,雖然上面的介紹有說(shuō)明,不過(guò)那太抽象了。任何編程知識(shí)只要能用代碼來(lái)驗(yàn)證和觀察,就不用圖表和理論。
過(guò)濾器是怎么運(yùn)行
下面,咱們實(shí)現(xiàn)上述幾個(gè)接口,然后往控制臺(tái)上打印一些文本,來(lái)看看這些過(guò)濾器是怎么運(yùn)行的。
public class CustAuthFilter : IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { Console.WriteLine("授權(quán)過(guò)濾器運(yùn)行"); } } public class CustResourceFilter : IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { Console.WriteLine("資源過(guò)濾器 - " + $"{nameof(OnResourceExecuted)}方法運(yùn)行"); } public void OnResourceExecuting(ResourceExecutingContext context) { Console.WriteLine("資源過(guò)濾器 - " + $"{nameof(OnResourceExecuting)}方法運(yùn)行"); } } public class CustActionFilter : IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("操作過(guò)濾器 - " + $"{nameof(OnActionExecuted)}方法運(yùn)行"); } public void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("操作過(guò)濾器 - " + $"{nameof(OnActionExecuting)}方法運(yùn)行"); } } public class CustResultFilter : IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("結(jié)果過(guò)濾器 - " + $"{nameof(OnResultExecuted)}方法運(yùn)行"); } public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("結(jié)果過(guò)濾器 - " + $"{nameof(OnResultExecuting)}方法運(yùn)行"); } }
這里我沒(méi)有實(shí)現(xiàn)異常過(guò)濾器,只實(shí)現(xiàn)了授權(quán)、資源、操作方法、結(jié)果這幾個(gè)有代表性的。
授權(quán)過(guò)濾器只要實(shí)現(xiàn) OnAuthorization 方法即可。在實(shí)現(xiàn)代碼中,可以通過(guò) HttpContext 對(duì)象查找授權(quán)有關(guān)的對(duì)象,如果確認(rèn)是沒(méi)有訪問(wèn)權(quán)限的,可以設(shè)置一個(gè)自己定 Result 讓 MVC 操作方法的調(diào)用終止。
資源過(guò)濾器要實(shí)現(xiàn)兩個(gè)方法:OnResourceExecuting 方法在模型綁定前調(diào)用,這時(shí)你有機(jī)會(huì)修改數(shù)據(jù)源;OnResourceExecuted 方法是在資源過(guò)濾之后的其他過(guò)濾器運(yùn)行結(jié)束才被調(diào)用,即:
ResourceExecuting
........ 剩余過(guò)濾器.......
ResourceExecuted
Action 過(guò)濾器也要實(shí)現(xiàn)兩個(gè)方法:OnActionExecuting 在操作調(diào)用前運(yùn)行;OnActionExecuted 是在操作方法調(diào)用后運(yùn)行。
結(jié)果過(guò)濾器需要實(shí)現(xiàn)兩個(gè)方法:OnResultExecuting 方法在操作結(jié)果執(zhí)行前調(diào)用,這里可以修改 MVC 方法返回的值;OnResultExecuted 方法是在操作結(jié)果執(zhí)行之后調(diào)用,一般這里可以改改HTTP向應(yīng)頭、Cookie 什么的。
其實(shí)咱們剛實(shí)現(xiàn)的過(guò)濾器都是同步版本,這些過(guò)濾器都有配套的異步版本,接口都是以 IAsync 開頭。這里咱們先不用管同步異步,避免搞得復(fù)雜了。也不要去理會(huì)過(guò)濾器是全局的還是局部的,下面咱們統(tǒng)一把它們注冊(cè)為全局的。配置方法是通過(guò) MVC 選項(xiàng)類的 Filters 集合,把要用的過(guò)濾器添加進(jìn)去即可。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllersWithViews(options => { // 配置全局過(guò)濾器 options.Filters.Add<CustAuthFilter>(); options.Filters.Add<CustResourceFilter>(); options.Filters.Add<CustActionFilter>(); options.Filters.Add<CustResultFilter>(); }); var app = builder.Build();
添加一個(gè)“狗頭”控制器,用于測(cè)試。
public class GouTouController : Controller { public IActionResult Index() { Console.WriteLine("Index操作運(yùn)行"); return View(); } }
為了防止 ASP.NET Core 應(yīng)用程序輸出的日志干擾咱們查看控制臺(tái)內(nèi)容,咱們禁用所有日志輸出。打開 appsettings.json 文件,把所有日志類別的記錄級(jí)別改為 None。
{ "Logging": { "LogLevel": { "*": "None" } }, "AllowedHosts": "*" }
星號(hào) * 的意思就是代表所有類別的日志,LogLevel 為 None 就不會(huì)輸出日志了(貌似有個(gè)別日志禁用不了)。
運(yùn)行程序后,控制臺(tái)打印出這樣的內(nèi)容:
這個(gè)流程現(xiàn)在是不是很清晰了?咱們畫圖表了,直接這樣表達(dá)就好:
Author Resource Executing Action Executing Action Running Action Executed Result Executing Result Running Result Executed Resource Executed
局部過(guò)濾器的運(yùn)行過(guò)程與全局過(guò)濾器相同,如果局部和全局過(guò)濾器同時(shí)使用,那會(huì)發(fā)生什么呢?咱們?cè)囋嚒?/p>
局部和全局過(guò)濾器使用
接下來(lái)我們?yōu)槭跈?quán)過(guò)濾、資源過(guò)濾、操作過(guò)濾、結(jié)果過(guò)濾各創(chuàng)建兩個(gè)類——用于局部和全局。實(shí)際開發(fā)中一般不需要這樣搞,通常全局和局部寫一個(gè)類就行,畢竟過(guò)濾器類型在全局和局部是通用的。我這里只為了演示。局部過(guò)濾器是通過(guò)特性類的方式應(yīng)用到 MVC 方法上的,所以,局部過(guò)濾器除了實(shí)現(xiàn)過(guò)濾器接口,還要從 Attribute 類派生。
1、實(shí)現(xiàn)局部、全局授權(quán)過(guò)濾器。
// 授權(quán)過(guò)濾器-局部 [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class MyAuthorFilterAttribute : Attribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { Console.WriteLine("局部:授權(quán)過(guò)濾器運(yùn)行"); } } // 授權(quán)過(guò)濾器-全局 public class GlobAuthorFilter : IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { Console.WriteLine("全局:授權(quán)過(guò)濾器運(yùn)行"); } }
2、實(shí)現(xiàn)局部、全局資源過(guò)濾器。
// 資源過(guò)濾器-局部 [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class MyResourceFilterAttribute : Attribute, IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { Console.WriteLine("局部:資源過(guò)濾器-Executed"); } public void OnResourceExecuting(ResourceExecutingContext context) { Console.WriteLine("局部:資源過(guò)濾器-Executing"); } } // 資源過(guò)濾器-全局 public class GlobResourceFilter : IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { Console.WriteLine("全局:資源過(guò)濾器-Executed"); } public void OnResourceExecuting(ResourceExecutingContext context) { Console.WriteLine("全局:資源過(guò)濾器-Executing"); } }
3、實(shí)現(xiàn)局部、全局操作過(guò)濾器。
// 操作過(guò)濾器-局部 [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class MyActionFilterAttribute : Attribute, IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("局部:操作過(guò)濾器-Executed"); } public void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("局部:操作過(guò)濾器-Executing"); } } // 操作過(guò)濾器-全局 public class GlobActionFilter : IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("全局:操作過(guò)濾器-Executed"); } public void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("全局:操作過(guò)濾器-Executing"); } }
4、實(shí)現(xiàn)局部、全局結(jié)果過(guò)濾器。
// 結(jié)果過(guò)濾器-局部 [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class MyResultFilterAttribute : Attribute, IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("局部:結(jié)果過(guò)濾器-Executed"); } public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("局部:結(jié)果過(guò)濾器-Executing"); } } // 結(jié)果過(guò)濾器-全局 public class GlobResultFilter : IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("全局:結(jié)果過(guò)濾器-Executed"); } public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("全局:結(jié)果過(guò)濾器-Executing"); } }
先注冊(cè)全局過(guò)濾器。
var builder = WebApplication.CreateBuilder(args); builder.Services.AddControllers(options => { // 添加全局過(guò)濾器 options.Filters.Add<GlobActionFilter>(); options.Filters.Add<GlobAuthorFilter>(); options.Filters.Add<GlobResourceFilter>(); options.Filters.Add<GlobResultFilter>(); }); var app = builder.Build();
局部過(guò)濾器以特性方式應(yīng)用于 MVC 操作方法。
public class SpiderController : ControllerBase { [MyResourceFilter] [MyResultFilter] [MyActionFilter, MyAuthorFilter] public IActionResult Index() { Console.WriteLine("Index操作被調(diào)用"); return Content("大火燒了毛毛蟲"); } }
和上一個(gè)例子一樣,禁用日志輸出(appsettings.json文件)。
{ "Logging": { "LogLevel": { "*": "None" } }, …… }
程序運(yùn)行后,控制臺(tái)打印以下內(nèi)容:
過(guò)濾器按 授權(quán)->資源->操作->結(jié)果 運(yùn)行的次序不變。在同種過(guò)濾器中,全局過(guò)濾器優(yōu)先運(yùn)行。
全局授權(quán)過(guò)濾器
局部授權(quán)過(guò)濾器
全局資源過(guò)濾器 - 前
局部資源過(guò)濾器 - 前
全局操作過(guò)濾器 - 前
局部操作過(guò)濾器 - 前
【調(diào)用 MVC 操作方法】
局部操作過(guò)濾器 - 后
全局操作過(guò)濾器 - 后
全局結(jié)果過(guò)濾器 - 前
局部結(jié)果過(guò)濾器 - 前
【執(zhí)行操作結(jié)果】
局部結(jié)果過(guò)濾器 - 后
全局結(jié)果過(guò)濾器 - 后
局部資源過(guò)濾器 - 后
全局資源過(guò)濾器 - 后
另外,有一件事要注意:如果你的控制器的基類是 Controller,那么,還有優(yōu)先更高的 Action Filter。看看 Controller 類它實(shí)現(xiàn)了啥接口。
public abstract class Controller : ControllerBase, IActionFilter, IFilterMetadata, IAsyncActionFilter, IDisposable
咱們把剛才的控制器代碼改一下,讓它繼承 Controller 類,并重寫 OnActionExecuting、OnActionExecuted 方法。
public class SpiderController : Controller { …… public override void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("控制器實(shí)現(xiàn)的操作過(guò)濾器-Executing"); base.OnActionExecuting(context); } public override void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("控制器實(shí)現(xiàn)的操作過(guò)濾器-Executed"); base.OnActionExecuted(context); } }
然后再次運(yùn)行程序,控制臺(tái)將打印以下內(nèi)容:
看,這個(gè)由控制器類實(shí)現(xiàn)的 Action 過(guò)濾器比全局的還早運(yùn)行。
以上就是ASP.NET Core MVC過(guò)濾器運(yùn)行流程解析的詳細(xì)內(nèi)容,更多關(guān)于ASP.NET Core MVC過(guò)濾器的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
asp.net GridView控件中實(shí)現(xiàn)全選的解決方案
在GridView中我們經(jīng)常要利用復(fù)選按鈕實(shí)現(xiàn)全選的功能,下面針對(duì)這一解決方案做以總結(jié)2010-03-03asp.net sql 數(shù)據(jù)庫(kù)處理函數(shù)命令
asp.net sql 數(shù)據(jù)庫(kù)處理函數(shù)命令 ,需要的朋友可以參考下。2009-10-10Repeater對(duì)數(shù)據(jù)進(jìn)行格式化處理
最近不止一個(gè)同學(xué),問(wèn)我在Repeater里怎么格式化數(shù)據(jù),怎么處理。因?yàn)镽epeater 屬于服務(wù)器端控件。要么利用本身的控件事件來(lái)處理,要么在數(shù)據(jù)源上處理。2013-03-03ASP.NET Core實(shí)現(xiàn)文件上傳和下載
這篇文章主要為大家詳細(xì)介紹了ASP.NET Core實(shí)現(xiàn)文件上傳和下載,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-07-07ASP.NET?Core?MVC創(chuàng)建控制器與依賴注入講解
這篇文章介紹了ASP.NET?Core?MVC創(chuàng)建控制器與依賴注入,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-02-02.Net Core路由處理的知識(shí)點(diǎn)與方法總結(jié)
這篇文章主要給大家介紹了關(guān)于.Net Core路由處理的知識(shí)點(diǎn)與方法的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04asp.net利用存儲(chǔ)過(guò)程和div+css實(shí)現(xiàn)分頁(yè)(類似于博客園首頁(yè)分頁(yè))
怎么用aspnetpager.dll這個(gè)插件對(duì)服務(wù)器控件進(jìn)行分頁(yè),今天與我大家分享一下asp.net利用存儲(chǔ)過(guò)程和div+css實(shí)現(xiàn)分頁(yè)(類似于博客園首頁(yè)分頁(yè))2012-01-01ASP.NET 計(jì)劃任務(wù)實(shí)現(xiàn)方法(不使用外接程序,.net內(nèi)部機(jī)制實(shí)現(xiàn))
在asp.net中要不使用其他插件的情況下只能使用定時(shí)器來(lái)檢查, 并執(zhí)行任務(wù).2011-09-09