本章將和大家分享ASP.NET Core MVC中的過濾器,下面我們將結合具體的例子來分享請求的整個生命周期中各種各樣的AOP擴展,
首先我們先來看下什么是過濾器?
.NET中的過濾器(Filter)是 AOP(面向切面編程) 思想的一種實作,供我們在執行管道的特定階段執行代碼,通過使用過濾器可以實作短路請求、快取請求結果、日志統一記錄、引數合法性驗證、例外統一處理、回傳值格式化等等,同時使業務代碼更加簡潔單純,避免很多重復代碼,
新版管道處理模型如下所示:

中間件和過濾器可以實作各種各樣的AOP擴展 ,也正因為如此,所以我們才能做到 “pay for what you use” ,在代碼開發程序中我們只需要關注核心的業務邏輯,
從新版管道處理模型中可以看出我們的請求首先先到達中間件,然后才會進入MVC流程,下面我們通過代碼來看下:
自定義防盜鏈中間件:
using Microsoft.AspNetCore.Http; using System; using System.IO; using System.Threading.Tasks; namespace MyMiddlewareAndFilter.Middlewares { /// <summary> /// 防盜鏈中間件(自定義) /// </summary> public class RefuseStealingMiddleware { private readonly RequestDelegate _next; public RefuseStealingMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { string url = context.Request.Path.Value; if (!url.Contains(".jpg")) { await Task.Run(() => { Console.WriteLine(""); Console.WriteLine("===================================Middleware==================================="); Console.WriteLine($"This is {typeof(RefuseStealingMiddleware)} begin"); }); await _next.Invoke(context);//走正常流程 await Task.Run(() => { Console.WriteLine($"This is {typeof(RefuseStealingMiddleware)} end"); Console.WriteLine("===================================Middleware==================================="); }); return; } string urlReferrer = context.Request.Headers["Referer"]; if (string.IsNullOrWhiteSpace(urlReferrer))//直接訪問 { await this.SetForbiddenImage(context);//回傳404圖片 } else if (!urlReferrer.Contains("localhost"))//非當前域名 { await this.SetForbiddenImage(context);//回傳404圖片 } else { await _next.Invoke(context);//走正常流程 } } /// <summary> /// 設定拒絕圖片 /// </summary> /// <param name="context">請求背景關系</param> /// <returns></returns> private async Task SetForbiddenImage(HttpContext context) { string defaultImagePath = "wwwroot/image/forbidden.jpg"; string path = Path.Combine(Directory.GetCurrentDirectory(), defaultImagePath); FileStream fs = File.OpenRead(path); byte[] bytes = new byte[fs.Length]; await fs.ReadAsync(bytes, 0, bytes.Length); await context.Response.Body.WriteAsync(bytes, 0, bytes.Length); } } }
修改Startup.cs檔案如下:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MyMiddlewareAndFilter.Middlewares; using MyMiddlewareAndFilter.Filter; namespace MyMiddlewareAndFilter { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseMiddleware<RefuseStealingMiddleware>(); //防盜鏈中間件 app.Use(next => //中間件1 { Console.WriteLine("middleware 1"); return async c => { await Task.Run(() => Console.WriteLine($"This is middleware 1 Start")); await next.Invoke(c); await Task.Run(() => Console.WriteLine($"This is middleware 1 End")); }; }); app.Use(next => //中間件2 { Console.WriteLine("middleware 2"); return async c => { await Task.Run(() => Console.WriteLine($"This is middleware 2 Start")); await next.Invoke(c); await Task.Run(() => Console.WriteLine($"This is middleware 2 End")); }; }); app.Use(next => //中間件3 { Console.WriteLine("middleware 3"); return async c => { await Task.Run(() => Console.WriteLine($"This is middleware 3 Start")); await next.Invoke(c); await Task.Run(() => Console.WriteLine($"This is middleware 3 End")); }; }); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } }
接下來我們來看下Home控制器:
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; using MyMiddlewareAndFilter.Filter; namespace MyMiddlewareAndFilter.Controllers { public class HomeController : Controller { private readonly ILogger<HomeController> _logger; public HomeController(ILogger<HomeController> logger) { _logger = logger; } public IActionResult Index() { Console.WriteLine($"This is {typeof(HomeController)} Index"); return View(); } } }
最后我們使用命令列方式啟動應用程式并訪問“/Home/Index”,控制臺輸出結果如下所示:

從運行結果可以看出我們的請求首先是先到達我們的中間件的,請求經過中間件之后才會進入MVC流程:

過濾器作用域:
1、全域有效(整個MVC應用程式下的每一個Action);
2、僅對某些Controller有效(控制器內所有的Action);
3、僅對某些Action有效;
過濾器的作業原理:
過濾器一般在Asp.Net Core MVC管道內運行,一般在操作執行之前(befor)或者執行之后(after)執行,以供開發者可以選擇在不同的執行階段介入處理,
過濾器型別:
授權過濾器 AuthorizeAttribute
資源過濾器 IResourceFilter
例外過濾器 IExceptionFilter
操作過濾器 ActionFilterAttribute
結果過濾器 IResultFilter

不同過濾器的執行順序:

1、授權過濾器
授權過濾器在過濾器管道中第一個被執行,通常用于驗證請求的合法性(通過實作介面 IAuthorizationFilter or IAsyncAuthorizationFilter),
在請求到達的時候最先執行,優先級最高,主要作用是提供用戶請求權限過濾,對不滿足權限的用戶,可以在過濾器內執行拒絕操作,俗稱“管道短路”,
注意:該過濾器只有執行之前(befor),沒有執行之后(after)的方法,
通常情況下,不需要自行撰寫過濾器,因為該過濾器在 Asp.Net Core 內部已經有了默認實作,我們需要做的就是配置授權策略或者實作自己的授權策略,然后由系統內置的授權過濾器呼叫授權策略即可,
PS:必須將該過濾器內部可能出現的例外全部處理,因為從過濾器的執行順序中可以發現授權過濾器是在例外過濾器之前執行的,故例外過濾器是沒有辦法捕獲到授權過濾器內部發生的例外,一旦授權過濾器內部發生例外,該例外將直接輸出到結果中,
using Microsoft.AspNetCore.Mvc.Filters; using System; namespace MyMiddlewareAndFilter.Filter { /// <summary> /// Authorization授權過濾器 /// </summary> public class CustomerAuthorizationFilterAttribute : Attribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { try { Console.WriteLine($"This is {typeof(CustomerAuthorizationFilterAttribute)} OnAuthorization"); } catch (Exception ex) { Console.WriteLine($"{typeof(CustomerAuthorizationFilterAttribute)} OnAuthorization發生例外:{ex}"); //例外處理 } } } }
2、資源過濾器
資源過濾器在過濾器管道中第二個被執行,通常用于請求結果的快取和短路過濾器管道(通過實作介面 IResourceFilter or IAsyncResourceFilter),
資源過濾器的應用場景:在性能方面,資源過濾器在實作快取或短路過濾器管道尤其有用,
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; namespace MyMiddlewareAndFilter.Filter { /// <summary> /// Resource資源過濾器 /// </summary> public class CustomerResourceFilterAttribute : Attribute, IResourceFilter, IFilterMetadata, IOrderedFilter { public int Order { get; set; } public void OnResourceExecuted(ResourceExecutedContext context) { Console.WriteLine($"This is {typeof(CustomerResourceFilterAttribute)} OnResourceExecuted"); if (context.HttpContext.Request.Query["type"].Equals("6")) { ////這里無效,已經來不及了 //context.Result = new JsonResult(new //{ // Remark = $"This is {typeof(CustomerResourceFilterAttribute)} OnResourceExecuted", // Type = context.HttpContext.Request.Query["type"] //}); } Console.WriteLine("****************************************************"); Console.WriteLine(""); } public void OnResourceExecuting(ResourceExecutingContext context) { Console.WriteLine(""); Console.WriteLine("****************************************************"); Console.WriteLine($"This is {typeof(CustomerResourceFilterAttribute)} OnResourceExecuting"); if (context.HttpContext.Request.Query["type"].Equals("5")) { context.Result = new JsonResult(new { Remark = $"This is {typeof(CustomerResourceFilterAttribute)} OnResourceExecuting", Type = context.HttpContext.Request.Query["type"] }); //連控制器都沒進去 } } } }
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; using System.Collections.Generic; namespace MyMiddlewareAndFilter.Filter { /// <summary> /// 資源過濾器(用于頁面快取) /// </summary> public class CustomerCacheResourceFilterAttribute : Attribute, IResourceFilter, IFilterMetadata, IOrderedFilter { public int Order { get; set; } private static Dictionary<string, IActionResult> _customerCacheResourceFilterAttributeDictionary = new Dictionary<string, IActionResult>(); public void OnResourceExecuting(ResourceExecutingContext context) { Console.WriteLine($"This is {typeof(CustomerCacheResourceFilterAttribute)} OnResourceExecuting"); string key = context.HttpContext.Request.Path; if (_customerCacheResourceFilterAttributeDictionary.ContainsKey(key)) { context.Result = _customerCacheResourceFilterAttributeDictionary[key]; //短路器 } } public void OnResourceExecuted(ResourceExecutedContext context) { Console.WriteLine($"This is {typeof(CustomerCacheResourceFilterAttribute)} OnResourceExecuted"); string key = context.HttpContext.Request.Path; _customerCacheResourceFilterAttributeDictionary.Add(key, context.Result); } } }
3、Action過濾器
Action過濾器要么實作IActionFilter介面,要么實作IAsyncActionFilter介面,它們可以在Action方法執行的前后被執行,另外,Action過濾器可以查看并直接修改Action方法的結果(即,可以對輸出結果進行修改),
OnActionExecuting方法在action方法執行之前運行,因此它可以通過改變ActionExecutingContext.ActionArguments來控制action的輸入(即獲取引數),或是通過ActionExecutingContext.Controller控制控制器(Controller),
OnActionExecuting方法可以通過設定ActionExecutingContext.Result來短路action方法的操作及其后續的過濾器,OnActionExecuting方法通過拋出例外也可以阻止action方法和后續過濾器的處理,但會當做失敗(而不是成功)的結果來處理,
OnActionExecuted方法在action方法執行之后才執行,并且可以通過ActionExecutedContext.Result屬性查看或控制action的結果,如果action在執行時被其它過濾器短路,則ActionExecutedContext.Canceled將會被置為true,如果action或后續的action過濾器拋出例外,則ActionExecutedContext.Exception會被設定為一個非空值,有效「處理」完例外后把ActionExecutedContext.Exception設定為null(即例外處理),那么ActionExectedContext.Result會像從action方法正常回傳值那樣被處理,
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; namespace MyMiddlewareAndFilter.Filter { /// <summary> /// Action操作過濾器 /// </summary> public class CustomerActionFilterAttribute : Attribute, IActionFilter, IFilterMetadata, IOrderedFilter { public int Order { get; set; } public void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine($"This is {typeof(CustomerActionFilterAttribute)} OnActionExecuted"); if (context.HttpContext.Request.Query["type"].Equals("8")) { context.Result = new JsonResult(new { Remark = $"This is {typeof(CustomerActionFilterAttribute)} OnActionExecuted", Type = context.HttpContext.Request.Query["type"] }); } } public void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine($"This is {typeof(CustomerActionFilterAttribute)} OnActionExecuting"); if (context.HttpContext.Request.Query["type"].Equals("7")) { context.Result = new JsonResult(new { Remark = $"This is {typeof(CustomerActionFilterAttribute)} OnActionExecuting", Type = context.HttpContext.Request.Query["type"] }); } } } }
4、例外過濾器
說明:用于實作常見的錯誤處理策略,沒有之前和之后事件,處理Razor頁面或控制器創建、模型系結、操作過濾器或操作方法中發生的未經處理的例外,
PS:無法捕獲授權過濾器、資源過濾器、結果過濾器或MVC結果執行中發生的例外,(這點從過濾器的執行順序就可以看出)
實作:繼承Attribute類,實作IExceptionFilter介面,重寫OnException方法, 或者直接繼承ExceptionFilterAttribute類,觀察原始碼可知,該類繼承了Attribute類,而且還實作IExceptionFilter介面,(異步的話實作IAsyncExceptionFilter介面,重寫OnExceptionAsync方法)
用途:全域捕獲例外,進行相關處理,
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ViewFeatures; using System; namespace MyMiddlewareAndFilter.Filter { /// <summary> /// Exception例外過濾器 /// </summary> public class CustomerExceptionFilterAttribute : Attribute, IExceptionFilter { private readonly IModelMetadataProvider _modelMetadataProvider; public CustomerExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider) { _modelMetadataProvider = modelMetadataProvider; } public void OnException(ExceptionContext context) { //獲取例外資訊 string ex = context.Exception.ToString(); //這邊可以處理例外資訊的記錄 Console.WriteLine($"This is {typeof(CustomerExceptionFilterAttribute)} OnException"); //跳轉到自定義例外頁面 var result = new ViewResult { ViewName = "Error" }; result.ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState); result.ViewData.Add("Exception", context.Exception); //視圖包引數 context.ExceptionHandled = false;//表示例外已經被處理過, context.Result = result; //回傳自定義的視圖內容 } } }
5、結果過濾器
說明:在Action過濾器之后,結果(如:頁面渲染)的前后運行,
實作:繼承Attribute類,實作IResultFilter介面,重寫OnResultExecuting和OnResultExecuted方法,或者直接繼承ResultFilterAttribute類,(或ActionFilterAttribute類),觀察原始碼可知,該類繼承了Attribute類,而且還實作IResultFilter介面,(異步的話實作IAsyncActionFilter介面,重寫OnActionExecutionAsync方法)還可以實作:IAlwaysRunResultFilter 或 IAsyncAlwaysRunResultFilter 介面,
用途:可以獲取action的返回結果,進行一些處理,
實作了IResultFilter或IAsyncResultFilter介面的結果過濾器在Action Result執行體的周圍執行,當Action或Action過濾器產生Action結果時,只有成功運行的才會執行結果過濾器,如果例外過濾器處理了例外,那么結果過濾器就不會運行,除非例外過濾器將例外設定為null(Exception = null),
OnResultExecuting方法運行于Action結果執行之前,故其可通過ResultExecutingContext.Result操作Action結果,如果將ResultExecutingContext.Cancel設定為 true,則OnResultExecuting方法可短路Action結果以及后續結果過濾器的執行,如果發生了短路,MVC將不會修改回應,所以當發生短路時,為避免生成空回應,你一般應該直接去修改回應物件,如果在OnResultExecuting方法內拋出例外,那么也將阻止Action結果以及后續過濾器的執行,但會被當做失敗結果(而非成功結果),
OnResultExecuted方法運行于Action結果執行之后,也就是說,如果沒有拋出例外,回應可能就會被發送到客戶端且不可再修改,如果Action結果在執行中被其它過濾器短路,則ResultExecutedContext.Canceled將被置為true,如果Action結果或后續結果過濾器拋出例外,則ResultExecutedContext.Exception將被置為非空值(non-null value),把ResultExecutedContext.Exception設定為null后會影響到例外的“處理”,這將阻止例外在之后的管道內被MVC重新拋出,如果在結果過濾器內處理例外,需要確定此處是否適合將某些資料寫入回應中,如果Action結果在執行中途拋出例外,而header也已被更新到客戶端,那么將沒有任何可靠的機制來發送失敗代碼,
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using System; namespace MyMiddlewareAndFilter.Filter { /// <summary> /// Result結果過濾器 /// </summary> public class CustomerResultFilterAttribute : Attribute, IResultFilter, IFilterMetadata, IOrderedFilter { public int Order { get; set; } public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine($"This is {typeof(CustomerResultFilterAttribute)} OnResultExecuted"); if (context.HttpContext.Request.Query["type"].Equals("4")) { //context.Result= //只讀了 } } public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine($"This is {typeof(CustomerResultFilterAttribute)} OnResultExecuting"); if (context.HttpContext.Request.Query["type"].Equals("3")) { context.Result = new JsonResult(new { Remark = $"This is {typeof(CustomerResultFilterAttribute)} OnResultExecuting", Type = context.HttpContext.Request.Query["type"] }); } } } }
6、取消和短路
除了授權過濾器之外的所有過濾器執行順序如下(沒有例外的情況):OnResourceExecuting(資源ing)-->OnActionExecuting(行為ing)-->控制器中的action-->OnActionExecuted(行為ed)-->OnResultExecuting(結果ing)-->OnResultExecuted(結果ed)-->OnResourceExecuted(資源ed)
當action中拋出例外時,result過濾器是被短路了,不會執行:OnResourceExecuting-->OnActionExecuting-->控制器中的action-->OnActionExecuted-->OnException-->OnResourceExecuted
設定Result結果造成短路:通過設定傳入過濾器方法的背景關系引數中的 Result 屬性,可以在過濾器管道的任意一點短路管道,該方式適用所有過濾器,在控制器中短路,不影響過濾器執行,

結果短路情況如下所示:(其中0表示不執行,1表示執行)

例外短路情況如下所示:(其中0表示不執行,1表示執行)

7、同一種過濾器的執行順序(自定義)


8、過濾器中獲取路由資訊
context.ActionDescriptor.RouteValues["area"].ToString();
context.ActionDescriptor.RouteValues["controller"].ToString();
context.ActionDescriptor.RouteValues["action"].ToString();
context.RouteData.Values["controller"].ToString();
context.RouteData.Values["action"].ToString();
9、過濾器的使用方式
普通過濾器:

建構式中帶引數的過濾器:

建構式中帶引數的過濾器有2種處理方式:
方式1:TypeFilterAttribute用于處理含建構式的自定義過濾器,不需要注冊,

方式2:ServiceFilterAttribute用于處理含建構式的自定義過濾器,但需要先注冊,
首先需要先在Startup.cs中注入:

然后再標記:

當然除了以上2種方式外還可以直接進行全域注冊:

最后我們再來看下整體的運行效果:
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using MyMiddlewareAndFilter.Middlewares; using MyMiddlewareAndFilter.Filter; namespace MyMiddlewareAndFilter { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(options => { options.Filters.Add<CustomerResourceFilterAttribute>(); //注冊全域過濾器 任意控制器-Action options.Filters.Add<CustomerExceptionFilterAttribute>(); }); //services.AddScoped<CustomerExceptionFilterAttribute>(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseMiddleware<RefuseStealingMiddleware>(); //防盜鏈中間件 app.Use(next => //中間件1 { Console.WriteLine("middleware 1"); return async c => { await Task.Run(() => Console.WriteLine($"This is middleware 1 Start")); await next.Invoke(c); await Task.Run(() => Console.WriteLine($"This is middleware 1 End")); }; }); app.Use(next => //中間件2 { Console.WriteLine("middleware 2"); return async c => { await Task.Run(() => Console.WriteLine($"This is middleware 2 Start")); await next.Invoke(c); await Task.Run(() => Console.WriteLine($"This is middleware 2 End")); }; }); app.Use(next => //中間件3 { Console.WriteLine("middleware 3"); return async c => { await Task.Run(() => Console.WriteLine($"This is middleware 3 Start")); await next.Invoke(c); await Task.Run(() => Console.WriteLine($"This is middleware 3 End")); }; }); app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); }); } } }
using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System; using MyMiddlewareAndFilter.Filter; namespace MyMiddlewareAndFilter.Controllers { [CustomerAuthorizationFilter] public class HomeController : Controller { private readonly ILogger<HomeController> _logger; public HomeController(ILogger<HomeController> logger) { _logger = logger; } [CustomerAction2Filter(Order = 2)] [CustomerActionFilter(Order = 1)] [CustomerResultFilter] public IActionResult Index() { Console.WriteLine($"This is {typeof(HomeController)} Index"); return View(); } //[TypeFilter(typeof(CustomerExceptionFilterAttribute))] //[ServiceFilter(typeof(CustomerExceptionFilterAttribute))] public IActionResult Test() { Console.WriteLine($"This is {typeof(HomeController)} Test"); throw new Exception("引數錯誤"); } } }
訪問“/Home/Index”控制臺輸出如下:

訪問“/Home/Test”控制臺輸出如下:

至此本文就全部介紹完了,如果覺得對您有所啟發請記得點個贊哦!!!
Demo原始碼:
鏈接:https://pan.baidu.com/s/1OTr6zqqUv4Rpn5fYLN7Mug 提取碼:leqf
此文由博主精心撰寫轉載請保留此原文鏈接:https://www.cnblogs.com/xyh9039/p/14359665.html
著作權宣告:如有雷同純屬巧合,如有侵權請及時聯系本人修改,謝謝!!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/257621.html
標籤:.NET Core
上一篇:hangfire 實作已完成的job設定過期,防止資料無限增長
下一篇:記一次dump檔案定位鎖
