tft每日頭條

 > 生活

 > asp.netcore跨域登錄

asp.netcore跨域登錄

生活 更新时间:2024-07-26 03:53:00
Filter概覽

如果你是從ASP.NET一路走過來的,那麼你一定對過濾器(Filter)不陌生。當然,ASP.NET Core仍然繼承了過濾器機制。

過濾器運行在過濾器管道中,這是一張官方的圖,很好地解釋了過濾器管道在HTTP請求管道中的位置:

asp.netcore跨域登錄(理解ASP.NETCore系列9-過濾器)1

可以看到,隻有當路由選擇了MVC Action之後,過濾器管道才有機會執行。

過濾器不止一種,而是有多種類型。為了讓各位對各過濾器執行順序更容易理解一下,我把官方的圖魔改了一下,如下:

asp.netcore跨域登錄(理解ASP.NETCore系列9-過濾器)2

  • Authorization Filters(授權過濾器):該過濾器位于所有過濾器的頂端,首先被執行。授權過濾器用于确認請求用戶是否已授權,如未授權,則可以将管道短路,禁止請求繼續傳遞。
  • Resource Filters(資源過濾器):當授權通過後,可以在過濾器管道的其他階段(如模型綁定)之前和之後執行自定義邏輯
  • Action Filters(操作過濾器):在調用Action之前和之後執行自定義邏輯。通過操作過濾器,可以修改要傳入Action的參數,也可以設置或修改Action的返回結果。另外,其也可以首先捕獲到Action中抛出的未處理異常并進行處理。
  • Exception Filters(異常過濾器):當controller創建時、模型綁定、Action Filters和Action執行中抛出未處理的異常時,異常過濾器可以捕獲并進行處理,需要注意的是,在此之前,響應正文還未被寫入,這意味着你可以設置返回結果。
  • Result Filters(結果過濾器):僅當Action的執行未抛出異常,或Action Filter處理了異常時,才會執行結果過濾器,允許你在操作結果執行之前和之後執行自定義邏輯。

東西有點多,你要忍一下。等看過下面的詳細介紹之後,再來回顧上面,就很容易理解了。

這些過濾器,均實現了IFilterMetadata接口,該接口不包含任何行為,僅僅是用于标記這是MVC請求管道中的過濾器。

另外,如Resource Filters、Action Filters和Result Filters這種,他們擁有兩個行為,分别在管道階段的之前和之後執行,并按照習慣,将之前命名為OnXXXing,如 OnActionExecuting,将之後命名為OnXXXExecuted,如 OnActionExecuted

過濾器的作用域和注冊方式

由于過濾器的種類繁多,為了方便大家邊學習邊測試,所以先介紹一下過濾器的作用域和注冊方式。

過濾器的作用域範圍和執行順序

同樣的,在介紹過濾器之前,先給大家介紹一下過濾器的作用域範圍和執行順序。

過濾器的作用域範圍,可分為三種,從小到大是:

  • 某個Controller中的某個Action上(不支持Razor Page中的處理方法)
  • 某個Controller或Razor Page上
  • 全局,應用到所有Controller、Action和Razor Page上

不同過濾器的執行順序,我們通過上面那幅圖可以很清楚的知曉了,但是對于不同作用域的同一類型的過濾器,執行順序又是怎樣的呢?

IActionFilter舉例說明,執行順序為:

  • 全局過濾器的 OnActionExecuting
    • Controller和Razor Page過濾器的 OnActionExecuting
      • Action過濾器的 OnActionExecuting
      • Action過濾器的 OnActionExecuted
    • Controller和Razor Page過濾器的 OnActionExecuted
  • 全局過濾器的 OnActionExecuted

也就是說,對于不同作用域的同一類型的過濾器,執行順序是由作用域範圍大到小,然後再由小到大

過濾器的注冊方式

接下來,看一下如何将過濾器注冊為不同的作用域:

全局

注冊為全局比較簡單,直接配置MvcOptions.Filters即可:

csharp

public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => options.Filters.Add<MyFilter>()); // or services.AddControllers(options => options.Filters.Add<MyFilter>()); // or services.AddControllersWithViews(options => options.Filters.Add<MyFilter>()); }

Controller、Razor Page 或 Action

作用域為 Controller、Razor Page 或 Action 在注冊方式上來說,實際上都是差不多的,都是以特性的方式進行标注。

最簡單的,過濾器構造函數無參數或這些參數均無需由DI來提供,此時隻需要過濾器繼承Attribute即可:

csharp

class MyFilterAttribute : Attribute, IActionFilter { } [MyFilter] public class HomeController : Controller { }

另一種,過濾器的構造函數參數均需要DI來提供,此時就需要用到ServiceFilterAttribute了:

csharp

class MyFilter :IActionFilter { public MyFilter(IWebHostEnvironment env) { } } public void ConfigureServices(IServiceCollection services) { // 将過濾器添加到 DI 容器 services.AddScoped<MyFilter>(); } [ServiceFilter(typeof(MyFilter))] public class HomeController : Controller { }

ServiceFilterAttribute是如何創建這種類型過濾器的實例的呢?看下它的結構你就明白了:

csharp

public interface IFilterFactory : IFilterMetadata { // 過濾器實例是否可跨請求重用 bool IsReusable { get; } // 通過 IServiceProvider 創建指定過濾器類型的實例 IFilterMetadata CreateInstance(IServiceProvider serviceProvider); } public class ServiceFilterAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter { // Type 就是要創建的過濾器的類型 public ServiceFilterAttribute(Type type) { ServiceType = type ?? throw new ArgumentNullException(nameof(type)); } public int Order { get; set; } // 獲取過濾器的類型,也就是構造函數中傳進來的 public Type ServiceType { get; } // 過濾器實例是否可跨請求重用,默認 false public bool IsReusable { get; set; } // 通過 IServiceProvider.GetRequiredService 創建指定過濾器類型的實例 // 所以要求該過濾器和構造函數參數要在DI容器中注冊 public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { var filter = (IFilterMetadata)serviceProvider.GetRequiredService(ServiceType); if (filter is IFilterFactory filterFactory) { // 展開 IFilterFactory filter = filterFactory.CreateInstance(serviceProvider); } return filter; } }

如果你想要使過濾器實例在其作用域之外被重用,可以通過指定IsReusable = true來達到目的,需要注意的是要保證該過濾器所依賴的服務生命周期一定是單例的。另外,這并不能保證該過濾器實例是單例,也有可能出現多個。

好了,還有最後一種最複雜的,就是過濾器的構造函數部分不需要DI來提供,部分又需要DI來提供,此時就需要用到TypeFilterAttribute了:

csharp

class MyFilter : IActionFilter { // 第一個參數 caller 不是通過DI提供的 // 第二個參數 env 是通過DI提供的 public MyFilter(string caller, IWebHostEnvironment env) { } } // ... 注意,這裡就不需要将 MyFilter 注冊到DI容器了,記得将注冊代碼删除 // Arguments 裡面存放的參數就是無需DI提供的參數 [TypeFilter(typeof(MyFilter), Arguments = new object[] { "HomeController" })] public class HomeController : Controller { }

同樣,看一下TypeFilterAttribute的結構:

csharp

public class TypeFilterAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter { private ObjectFactory _factory; // type 就是要創建的過濾器的類型 public TypeFilterAttribute(Type type) { ImplementationType = type ?? throw new ArgumentNullException(nameof(type)); } // 要傳遞給過濾器構造函數的非DI容器提供的參數 public object[] Arguments { get; set; } // 獲取過濾器的類型,也就是構造函數中傳進來的 public Type ImplementationType { get; } public int Order { get; set; } public bool IsReusable { get; set; } // 通過 ObjectFactory 創建指定過濾器類型的實例 public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { if (_factory == null) { var argumentTypes = Arguments?.Select(a => a.GetType())?.ToArray(); _factory = ActivatorUtilities.CreateFactory(ImplementationType, argumentTypes ?? Type.EmptyTypes); } var filter = (IFilterMetadata)_factory(serviceProvider, Arguments); if (filter is IFilterFactory filterFactory) { // 展開 IFilterFactory filter = filterFactory.CreateInstance(serviceProvider); } return filter; } }

過濾器上下文

過濾器中的行為,都會有一個上下文參數,這些上下文參數都繼承自抽象類FilterContext,而FilterContext又繼承自ActionContext(這也從側面說明了,過濾器就是為Action服務的):

csharp

public class ActionContext { // Action相關的信息 public ActionDescriptor ActionDescriptor { get; set; } // HTTP上下文 public HttpContext HttpContext { get; set; } // 模型綁定和驗證 public ModelStateDictionary ModelState { get; } // 路由數據 public RouteData RouteData { get; set; } } public abstract class FilterContext : ActionContext { public virtual IList<IFilterMetadata> Filters { get; } public bool IsEffectivePolicy<TMetadata>(TMetadata policy) where TMetadata : IFilterMetadata {} public TMetadata FindEffectivePolicy<TMetadata>() where TMetadata : IFilterMetadata {} }

當我們自定義一個過濾器時,免不了要和上下文進行交互,所以,了解上下文的結構,是不可或缺的。下面就挑兩個重要的參數探究一下。

我們先來看ActionDescriptor,它裡面包含了和Action相關的信息:

csharp

public class ActionDescriptor { // 标識該Action的唯一标識,其實就是一個Guid public string Id { get; } // 路由字典,包含了controller、action的名字等 public IDictionary<string, string> RouteValues { get; set; } // 特性路由的相關信息 public AttributeRouteInfo? AttributeRouteInfo { get; set; } // Action的約束列表 public IList<IActionConstraintMetadata>? ActionConstraints { get; set; } // 終結點元數據,咱們一般用不到 public IList<object> EndpointMetadata { get; set; } // 路由中的參數列表,包含參數名、參數類型、綁定信息等 public IList<ParameterDescriptor> Parameters { get; set; } public IList<ParameterDescriptor> BoundProperties { get; set; } // 過濾器管道中與當前Action有關的過濾器列表 public IList<FilterDescriptor> FilterDescriptors { get; set; } // Action的個性化名稱 public virtual string? DisplayName { get; set; } // 共享元數據 public IDictionary<object, object> Properties { get; set; } }

下面的HttpContext這個就不說了,太大了。不過你得知道,有了它,你可以針對請求和響應做自己想做的操作。

接下來就是ModelState,它是用于校驗模型綁定的,通過它,可以知道模型是否綁定成功,也可以得到綁定失敗的校驗信息。相關細節将在後續關于模型綁定的文章中進行介紹。

然後就是RouteData,很顯然,它存儲了和路由有關的信息,那就看一下它包括什麼吧:

csharp

public class RouteData { // 當前路由路徑上由路由生成的數據标記 public RouteValueDictionary DataTokens { get; } // Microsoft.AspNetCore.Routing.IRouter 的實例列表 public IList<IRouter> Routers { get; } // 路由值,包含了 ActionDescriptor.RouteValues 中的數據 public RouteValueDictionary Values { get; } }

後面,就來到了Filters,看到IFilterMetadata我相信你也已經猜到了,它表示過濾器管道中與當前Action有關的過濾器列表。

Authorization Filters

授權過濾器是過濾器管道的第一個被執行的過濾器,用于系統授權。一般不會編寫自定義的授權過濾器,而是配置授權策略或編寫自定義授權策略。詳細内容将在後續文章介紹。

Resource Filters

資源過濾器,在授權過濾器執行後執行,該過濾器包含“之前”和“之後”兩個行為,包裹了模型綁定、操作過濾器、Action執行、異常過濾器、結果過濾器以及結果執行。

通過實現IResourceFilterIAsyncResourceFilter接口:

csharp

public interface IResourceFilter : IFilterMetadata { void OnResourceExecuting(ResourceExecutingContext context); void OnResourceExecuted(ResourceExecutedContext context); } public interface IAsyncResourceFilter : IFilterMetadata { Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next); }

當攔截到請求時,你可以得到資源信息的上下文:

csharp

public class ResourceExecutingContext : FilterContext { // 獲取或設置該Action的執行結果 public virtual IActionResult? Result { get; set; } // Action參數綁定源提供器工廠,比如 Form、Route、QueryString、JQueryForm、FormFile等 public IList<IValueProviderFactory> ValueProviderFactories { get; } } public class ResourceExecutedContext : FilterContext { // 指示Action的執行是否已取消 public virtual bool Canceled { get; set; } // 如果捕獲到未處理的異常,會存放到此處 public virtual Exception? Exception { get; set; } public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; } // 指示異常是否已被處理 public virtual bool ExceptionHandled { get; set; } // 獲取或設置該Action的執行結果 public virtual IActionResult? Result { get; set; } }

類似的,一旦設置了Result,就可以使過濾器管道短路。

對于ResourceExecutedContext,有兩種方式來處理異常:

  • ExceptionExceptionDispatchInfo置為null
  • ExceptionHandled置為true

單純的僅設置Result是行不通的。所以我建議大家,在處理異常時,除了設置Result外,也将ExceptionHandled設置為true,這樣也讓讀代碼的人更容易理解代碼邏輯。

另外,ResourceExecutedContext.Canceled,用于指示Action的執行是否已取消。當在 OnResourceExecuting 中手動設置 ResourceExecutingContext.Result 時,會将 Canceled 置為 true。需要注意的是,想要測試這種情況,至少要注冊兩個資源過濾器,并且在第二個資源過濾器中設置Result,才能夠在第一個過濾器中看到效果。

Action Filters

操作過濾器,在模型綁定後執行,該過濾器同樣包含“之前”和“之後”兩個行為,包裹了Action的執行(不包含Controller的創建)。

如果Action執行過程中或後續操作過濾器中抛出異常,首先捕獲到異常的是操作過濾器的OnActionExecuted,而不是異常過濾器。

通過實現IActionFilterIAsyncActionFilter接口:

csharp

public interface IActionFilter : IFilterMetadata { void OnActionExecuting(ActionExecutingContext context); void OnActionExecuted(ActionExecutedContext context); } public interface IAsyncActionFilter : IFilterMetadata { Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next); }

同樣地,看一下上下文結構:

csharp

public class ActionExecutingContext : FilterContext { // 獲取或設置該Action的執行結果 public virtual IActionResult? Result { get; set; } // Action的參數字典,key是參數名,value是參數值 public virtual IDictionary<string, object> ActionArguments { get; } // 獲取該Action所屬的Controller public virtual object Controller { get; } } public class ActionExecutedContext : FilterContext { // 指示Action的執行是否已取消 public virtual bool Canceled { get; set; } // 獲取該Action所屬的Controller public virtual object Controller { get; } // 如果捕獲到未處理的異常,會存放到此處 public virtual Exception? Exception { get; set; } public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; } // 指示異常是否已被處理 public virtual bool ExceptionHandled { get; set; } // 獲取或設置該Action的執行結果 public virtual IActionResult Result { get; set; } }

關于ActionExecutedContext.Canceled屬性和異常處理相關的知識點,均與資源過濾器類似,這裡就不再贅述了。

由于操作過濾器常常在應用中的使用比較頻繁,所以這裡詳細介紹一下它的使用。ASP.NET Core框架提供了一個抽象類ActionFilterAttribute,該抽象類實現了多個接口,還繼承了Attribute,允許我們以特性的方式使用。所以,一般比較建議大家通過繼承該抽象類來自定義操作過濾器:

csharp

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public abstract class ActionFilterAttribute : Attribute, IActionFilter, IAsyncActionFilter, IResultFilter, IAsyncResultFilter, IOrderedFilter { public int Order { get; set; } public virtual void OnActionExecuting(ActionExecutingContext context) { } public virtual void OnActionExecuted(ActionExecutedContext context) { } public virtual async Task OnActionExecutionAsync( ActionExecutingContext context, ActionExecutionDelegate next) { // 删除了一些空校驗代碼... OnActionExecuting(context); if (context.Result == null) { OnActionExecuted(await next()); } } public virtual void OnResultExecuting(ResultExecutingContext context) { } public virtual void OnResultExecuted(ResultExecutedContext context) { } public virtual async Task OnResultExecutionAsync( ResultExecutingContext context, ResultExecutionDelegate next) { // 删除了一些空校驗代碼... OnResultExecuting(context); if (!context.Cancel) { OnResultExecuted(await next()); } } }

可以看到,ActionFilterAttribute同時實現了同步和異步接口,不過,我們在使用時,隻需要實現同步或異步接口就可以了,不要同時實現。這是因為,運行時會先檢查過濾器是否實現了異步接口,如果是,則調用該異步接口。否則,就調用同步接口。 如果在一個類中同時實現了異步和同步接口,則僅會調用異步接口。

當要全局進行驗證模型綁定狀态時,使用操作過濾器再合适不過了!

csharp

public class ModelStateValidationFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { if (!context.ModelState.IsValid) { if (context.HttpContext.Request.AcceptJson()) { var errorMsg = string.Join(Environment.NewLine, context.ModelState.Values.SelectMany(v => v.Errors.Select(e => e.ErrorMessage))); context.Result = new BadRequestObjectResult(AjaxResponse.Failed(errorMsg)); } else { context.Result = new ViewResult(); } } } } public static class HttpRequestExtensions { public static bool AcceptJson(this HttpRequest request) { if (request == null) throw new ArgumentNullException(nameof(request)); var regex = new Regex(@"^(\*|application)/(\*|json)$"); return request.Headers[HeaderNames.Accept].ToString() .Split(',') .Any(type => regex.IsMatch(type)); } }

Exception Filters

異常過濾器,可以捕獲Controller創建時(也就是隻捕獲構造函數中抛出的異常)、模型綁定、Action Filter和Action中抛出的未處理異常。

再着重說明一下:如果Action執行過程中或非首個操作過濾器中抛出異常,首先捕獲到異常的是操作過濾器的OnActionExecuted,而不是異常過濾器。但是,如果在Controller創建時抛出異常,那首先捕獲到異常的就是異常過濾器了。

我知道大家在初時異常過濾器的時候,有的人會誤認為它可以捕獲程序中的任何異常,這是不對的!

異常過濾器:

  • 通過實現接口IExceptionFilterIAsyncExceptionFilter來自定義異常過濾器
  • 可以捕獲Controller創建時(也就是隻捕獲構造函數中抛出的異常)、模型綁定、Action Filter和Action中抛出的未處理異常
  • 其他地方抛出的異常不會捕獲

先來看一下這兩個接口:

csharp

// 僅具有标記作用,标記其為 mvc 請求管道的過濾器 public interface IFilterMetadata { } public interface IExceptionFilter : IFilterMetadata { // 當抛出異常時,該方法會捕獲 void OnException(ExceptionContext context); } public interface IAsyncExceptionFilter : IFilterMetadata { // 當抛出異常時,該方法會捕獲 Task OnExceptionAsync(ExceptionContext context); }

OnExceptionOnExceptionAsync方法都包含一個類型為ExceptionContext參數,很顯然,它就是與異常有關的上下文,我們的異常處理邏輯離不開它。那接着來看一下它的結構吧:

csharp

public class ExceptionContext : FilterContext { // 捕獲到的未處理異常 public virtual Exception Exception { get; set; } public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; } // 指示異常是否已被處理 // true:表示異常已被處理,異常不會再向上抛出 // false:表示異常未被處理,異常仍會繼續向上抛出 public virtual bool ExceptionHandled { get; set; } // 設置響應的 IActionResult // 如果設置了結果,也表示異常已被處理,異常不會再向上抛出 public virtual IActionResult? Result { get; set; } }

下面,我們就來實現一個自定義的異常處理器:

csharp

public class MyExceptionFilterAttribute : ExceptionFilterAttribute { private readonly IModelMetadataProvider _modelMetadataProvider; public MyExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider) { _modelMetadataProvider = modelMetadataProvider; } public override void OnException(ExceptionContext context) { if (!context.ExceptionHandled) { // 此處僅為簡單演示 var exception = context.Exception; var result = new ViewResult() { ViewName = "Error", ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState) { // 記得給 ErrorViewModel 加上 Message 屬性 Model = new ErrorViewModel { Message = exception.ToString() } } }; context.Result = result; // 标記異常已處理 context.ExceptionHandled = true; } } }

接着,找到/Views/Shared/Error.cshtml,展示一下錯誤消息:

gherkin

@model ErrorViewModel @{ ViewData["Title"] = "Error"; } <p>@Model.Message</p>

最後,注冊一下MyExceptionFilterAttribute

csharp

public void ConfigureServices(IServiceCollection services) { services.AddScoped<MyExceptionFilterAttribute>(); services.AddControllersWithViews(); }

現在,我們将該異常處理器加在/Home/Index上,并抛個異常:

csharp

public class HomeController : Controller { [ServiceFilter(typeof(MyExceptionFilterAttribute))] public IActionResult Index() { throw new Exception("Home Index Error"); return View(); } }

當請求/Home/Index時,你會得到如下頁面:

asp.netcore跨域登錄(理解ASP.NETCore系列9-過濾器)3

Result Filters

結果過濾器,包裹了操作結果的執行。所謂操作結果的執行,可以是Razor視圖的處理操作,也可以是Json結果的序列化操作等。

通過實現IResultFilterIAsyncResultFilter接口:

csharp

public interface IResultFilter : IFilterMetadata { void OnResultExecuting(ResultExecutingContext context); void OnResultExecuted(ResultExecutedContext context); } public interface IAsyncResultFilter : IFilterMetadata { Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next); }

當實現這兩個接口其一時,則僅當Action或Action Filters生成Result時,才會執行結果過濾器。像授權、資源過濾器使管道短路或異常過濾器通過生成Result來處理異常等,都不會執行結果過濾器。

如果在 OnResultExecuting 中抛異常了,就會導緻短路,Action結果和後續的結果過濾器都不會執行,并且執行結果也被視為失敗。

同樣地,看一下上下文結構:

csharp

public class ResultExecutingContext : FilterContext { // 獲取該Action所屬的Controller public virtual object Controller { get; } // 獲取或設置該Action的結果 public virtual IActionResult Result { get; set; } // 指示結果過濾器是否應該被短路,若短路,Action結果和後續的的結果過濾器,都不會執行 public virtual bool Cancel { get; set; } } public class ResultExecutedContext : FilterContext { // 指示結果過濾器是否被短路,若短路,Action結果和後續的的結果過濾器,都不會執行 public virtual bool Canceled { get; set; } // 獲取該Action所屬的Controller public virtual object Controller { get; } // 獲取或設置結果或結果過濾器執行過程中抛出的未處理異常 public virtual Exception? Exception { get; set; } public virtual ExceptionDispatchInfo? ExceptionDispatchInfo { get; set; } // 異常是否已被處理 public virtual bool ExceptionHandled { get; set; } // 獲取或設置該Action的執行結果 public virtual IActionResult Result { get; } }

可以通過繼承抽象類ResultFilterAttribute來實現自定義結果過濾器:

csharp

class MyResultFilter : ResultFilterAttribute { private readonly ILogger<MyResultFilter> _logger; public MyResultFilter(ILogger<MyResultFilter> logger) { _logger = logger; } public override void OnResultExecuted(ResultExecutedContext context) { context.HttpContext.Response.Headers.Add("CustomHeaderName", "CustomHeaderValue"); } public override void OnResultExecuting(ResultExecutingContext context) { if (context.HttpContext.Response.HasStarted) { _logger.LogInformation("Response has started!"); } } }

上面說過,IResultFilterIAsyncResultFilter接口有一定的局限性,當授權、資源過濾器使管道短路或異常過濾器通過生成Result來處理異常等,會導緻結果過濾器不被執行。但是,如果在這種情況下,我們也想要執行結果過濾器,那該咋辦呢?别慌,ASP.NET Core已經想到這種情況了。

那就是實現IAlwaysRunResultFilterIAsyncAlwaysRunResultFilter接口,看這名字就夠直接了吧——始終運行:

csharp

public interface IAlwaysRunResultFilter : IResultFilter, IFilterMetadata { } public interface IAsyncAlwaysRunResultFilter : IAsyncResultFilter, IFilterMetadata { }

中間件過濾器

中間件過濾器,其實是在過濾器管道中加入中間件管道。中間件過濾器的執行時機與資源過濾器一樣,即模型綁定之前和管道的其餘部分執行之後執行。

要創建中間件過濾器,需要滿足一個條件,那就是該中間件必須包含一個Configure方法(一般來說還會包含一個IApplicationBuilder參數用于配置中間件管道,不過這不是強制的)。

例如:

csharp

class MyPipeline { public void Configure(IApplicationBuilder app) { System.Console.WriteLine("MyPipeline"); } } [MiddlewareFilter(typeof(MyPipeline))] public class HomeController : Controller { }

其他IOrderedFilter

針對同一類型的過濾器,我們可以有多個實現,這些實現,可以注冊到不同的作用域中,而且同一個作用域可以有多個該過濾器類型的實現。如果我們将這樣的多個實現作用于同一個Action,這些過濾器實例的執行順序就是我們所要關心的了。

默認的,如果将同一作用域的同一類型的過濾器的多個實現作用到某個Action上,則這些過濾器實例的執行順序是按照注冊的順序進行的。

例如,我們現在有兩個操作過濾器——MyActionFilter1和MyActionFilter2:

csharp

public class MyActionFilter1 : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("OnActionExecuting: MyActionFilter1"); } public override void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("OnResultExecuted: MyActionFilter1"); } } public class MyActionFilter2 : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("OnActionExecuting: MyActionFilter2"); } public override void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("OnResultExecuted: MyActionFilter2"); } }

然後将其作用到HomeController.Index方法上,并且,先注冊MyActionFilter2,再注冊MyActionFilter1:

csharp

public class HomeController : Controller { [MyActionFilter2] [MyActionFilter1] public IActionResult Index() { return View(); } }

當請求Home/Index時,控制台的輸出如下:

apache

OnActionExecuting: MyActionFilter2 OnActionExecuting: MyActionFilter1 OnResultExecuted: MyActionFilter1 OnResultExecuted: MyActionFilter2

但是,我們在開發過程中,很容易手滑将注冊順序弄錯,這時我們就需要一個手動指定執行順序的機制,這就用到了IOrderedFilter接口。

csharp

public interface IOrderedFilter : IFilterMetadata { // 執行順序 int Order { get; } }

IOrderedFilter接口很簡單,隻有一個Order屬性,表示執行順序,默認值為0。Order值越小,則過濾器的Before方法越先執行,After方法越後執行。

下面我們改造一下MyActionFilter1和MyActionFilter2,讓MyActionFilter1先執行:

csharp

public class MyActionFilter1 : ActionFilterAttribute { public MyActionFilter1() { Order = -1; } public override void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("OnActionExecuting: MyActionFilter1"); } public override void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("OnResultExecuted: MyActionFilter1"); } } public class MyActionFilter2 : ActionFilterAttribute { public MyActionFilter2() { Order = 1; } public override void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("OnActionExecuting: MyActionFilter2"); } public override void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("OnResultExecuted: MyActionFilter2"); } }

此時,再次請求Home/Index,控制台的輸出如下:

apache

OnActionExecuting: MyActionFilter1 OnActionExecuting: MyActionFilter2 OnResultExecuted: MyActionFilter2 OnResultExecuted: MyActionFilter1

現在,我們看一下不同作用域的情況下,Order是否生效。将MyActionFilter2作用域提升到控制器上。

csharp

[MyActionFilter2] public class HomeController : Controller { [MyActionFilter1] public IActionResult Index() { return View(); } }

此時,再次請求Home/Index,控制台的輸出如下:

csharp

OnActionExecuting: MyActionFilter1 OnActionExecuting: MyActionFilter2 OnResultExecuted: MyActionFilter2 OnResultExecuted: MyActionFilter1

哇,神奇的事情發生了,作用域為Action的MyActionFilter1竟然優先于作用域為Controller的MyActionFilter2執行。

實際上,Order會重寫作用域,即先按Order對過濾器進行排序,然後再通過作用域消除并列問題。

另外,若要始終首先執行全局過濾器,則請将Order設置為int.MinValue

csharp

public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(options => { options.Filters.Add<MyActionFilter2>(int.MinValue); }); }

歡迎點贊 轉發 關注!大家的支持是我分享最大的動力!!!

,

更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!

查看全部

相关生活资讯推荐

热门生活资讯推荐

网友关注

Copyright 2023-2024 - www.tftnews.com All Rights Reserved