原文地址:https://www.cnblogs.com/jingjiangtao/p/14711003.htmlhtml
爲了演示自定義過濾器,須要新建一個 ASP.NET Core Web API 項目,項目配置能夠按照本身的習慣來,也能夠參考下面的配置,總之能讓項目跑起來就能夠。git
Startup類:github
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.AddControllers(); } // 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(); } app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } }
launchSettings.jsonjson
{ "$schema": "http://json.schemastore.org/launchsettings.json", "profiles": { "CustomizeActionFilter": { "commandName": "Project", "dotnetRunMessages": "true", "launchBrowser": true, "launchUrl": "", "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } }
在Controllers目錄下新建SampleController控制器類。這個類只是用來演示,沒有業務上的意義:app
public class SampleController : ControllerBase { [HttpGet("NeedVersionFilter")] public IActionResult NeedVersionFilter() { return Ok("OK: Need Version"); } [HttpGet("NoNeedVersionFilter")] public IActionResult NoNeedVersionFilter() { return Ok("OK: No Need Version"); } }
接下來開始編寫自定義Action Filter。在項目根目錄下新建ActionFilters目錄,在此目錄下新建類VersionCheckAttribute,該類繼承自Attribute並實現了IActionFilter接口。繼承Attribute能夠讓自定義過濾器以特性的方式使用,也就是用方括號括起來的形式,也有人叫標籤;實現IActionFilter接口能夠在請求的不一樣階段添加處理邏輯。post
[Serializable, AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class VersionCheckAttribute : Attribute, IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { } public void OnActionExecuting(ActionExecutingContext context) { } }
其中OnActionExecuting方法在請求進入控制器Action以前執行,攔截代碼也在這個方法中實現。測試
爲作演示,假設須要實現這樣一種攔截器:每一個http請求的header中都應該帶有自定義的參數version,若是version的值正確,則請求正常進入控制器執行,若是不正確,則直接返回,再也不進入控制器。代碼以下:ui
[Serializable, AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class VersionCheckAttribute : Attribute, IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { } public void OnActionExecuting(ActionExecutingContext context) { if (!context.HttpContext.Request.Headers.ContainsKey("version")) { context.Result = new BadRequestObjectResult("Error: incorrect version"); return; } string headVersionStr = context.HttpContext.Request.Headers["version"].FirstOrDefault(); if (headVersionStr != "1.0.0") { context.Result = new BadRequestObjectResult("Error: incorrect version"); return; } } }
首先判斷header中有沒有version參數,沒有的話直接返回錯誤。若是有version參數,則判斷值是否正確,若是不正確也直接返回錯誤,若是正確,則繼續向下執行,請求會進入控制器的Action。給context.Result賦值能夠將請求短路,不會再進入控制器的Action中執行,而是直接返回。this
接下來在SampleController上應用VersionCheck過濾器,讓它過濾控制器中的全部請求:編碼
[Route("[controller]")] [ApiController] [VersionCheck] // 自定義過濾器 public class SampleController : ControllerBase { [HttpGet("NeedVersionFilter")] public IActionResult NeedVersionFilter() { return Ok("OK: Need Version"); } [HttpGet("NoNeedVersionFilter")] public IActionResult NoNeedVersionFilter() { return Ok("OK: No Need Version"); } }
運行項目,用postman請求看看結果:
沒有添加自定義header,返回 Error: incorrect version
添加了自定義header,返回正常:
至此,一個簡單的Action Filter已經完成了。
上面實現的控制器有兩個Action,假設有這樣一種需求:NeedVersionFilter接口須要過濾版本號,NoNeedVersionFilter接口不須要過濾版本號。這樣的話,[VersionCheck]放到SampleController類上就不行了,能夠刪掉控制器上的[VersionCheck]特性,轉而放到NeedVersionFilter方法上,這樣就實現了這個需求。
[Route("[controller]")] [ApiController] public class SampleController : ControllerBase { [VersionCheck] // 將過濾器放到方法上 [HttpGet("NeedVersionFilter")] public IActionResult NeedVersionFilter() { return Ok("OK: Need Version"); } [HttpGet("NoNeedVersionFilter")] public IActionResult NoNeedVersionFilter() { return Ok("OK: No Need Version"); } }
可是,若是Controller中的Action很是多,而大部分Action都須要版本過濾器,只有少數幾個不須要,用這種形式就要在每一個方法上應用[VersionCheck]特性,有點麻煩,還可能漏加。這時候若是把[VersionCheck]應用到Controller上,同時能夠排除幾個不須要過濾器的方法,寫起來會更簡潔。這是能夠作到的,經過給不須要過濾器的方法作標記,就能夠在過濾器中跳過有標記的方法了。
在ActionFilters目錄下新建類IgnoreVersionCheckAttribute,繼承自Attribute類和IFilterMetadata接口。IFilterMetadata接口沒有須要實現的方法,僅做爲標記:
[Serializable, AttributeUsage(AttributeTargets.Method)] public class IgnoreVersionCheckAttribute : Attribute, IFilterMetadata { }
修改VersioncheckAttribute類的代碼,讓過濾器跳過標記爲IgnoreVersionCheck的方法:
[Serializable, AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class VersionCheckAttribute : Attribute, IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { } public void OnActionExecuting(ActionExecutingContext context) { if (HasIgnoreVersionCheck(context)) { return; } if (!context.HttpContext.Request.Headers.ContainsKey("version")) { context.Result = new BadRequestObjectResult("Error: incorrect version"); return; } string headVersionStr = context.HttpContext.Request.Headers["version"].FirstOrDefault(); if (headVersionStr != "1.0.0") { context.Result = new BadRequestObjectResult("Error: incorrect version"); return; } } private bool HasIgnoreVersionCheck(ActionExecutingContext context) { IList<IFilterMetadata> filters = context.Filters; foreach (IFilterMetadata filter in filters) { if (filter is IgnoreVersionCheckAttribute) { return true; } } return false; } }
能夠看到,在剛進入Action時就判斷是否有IgnoreVersionCheck,若是有,則直接退出過濾器,繼續執行Controller中的代碼,若是沒有則繼續執行過濾器。HasIgnoreVersionCheck方法從ActionExecutingContext中拿到當前Action上的全部filter,遍歷查找有沒有IgnoreVersionCheckAttribute,有則返回true,沒有則返回false。
修改SampleController的代碼,把[VersionCheck]放到控制器上,在NoNeedVersionFilter方法上添加[IgnoreVersionCheck]
[Route("[controller]")] [ApiController] [VersionCheck] public class SampleController : ControllerBase { [HttpGet("NeedVersionFilter")] public IActionResult NeedVersionFilter() { return Ok("OK: Need Version"); } [IgnoreVersionCheck] [HttpGet("NoNeedVersionFilter")] public IActionResult NoNeedVersionFilter() { return Ok("OK: No Need Version"); } }
測試一下是否生效。
NeedVersionFilter接口不添加version頭:
NeedVersionFilter接口添加version頭:
NoNeedVersionFilter不添加version頭:
能夠看到確實生效了。至此,帶排除項的過濾器就完成了。
上面的過濾器代碼爲了方便起見,判斷版本號是否正確時直接用了 "1.0.0" 這種硬編碼的字符串,實際項目中這個字符串多是會變化的,最好寫在配置文件中。在appsetting.Development.json中添加字段 "VersionFilter" 並賦值:
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } }, "VersionFilter": "1.0.0" }
修改VersionCheckAttribute的代碼,經過ActionExecutedContext中的屬性獲取IConfiguration服務,再從IConfiguration實例中獲取字符串:
[Serializable, AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)] public class VersionCheckAttribute : Attribute, IActionFilter { public void OnActionExecuted(ActionExecutedContext context) { } public void OnActionExecuting(ActionExecutingContext context) { if (HasIgnoreVersionCheck(context)) { return; } if (!context.HttpContext.Request.Headers.ContainsKey("version")) { context.Result = new BadRequestObjectResult("Error: incorrect version"); return; } string headVersionStr = context.HttpContext.Request.Headers["version"].FirstOrDefault();
// 獲取配置服務 var configuration = context.HttpContext.RequestServices.GetRequiredService<IConfiguration>(); string confVersionStr = configuration.GetValue<string>("VersionFilter"); if (headVersionStr != confVersionStr) { context.Result = new BadRequestObjectResult("Error: incorrect version"); return; } } private bool HasIgnoreVersionCheck(ActionExecutingContext context) { IList<IFilterMetadata> filters = context.Filters; foreach (IFilterMetadata filter in filters) { if (filter is IgnoreVersionCheckAttribute) { return true; } } return false; } }
這樣,一個比較靈活的自定義ActionFilter就完成了。
完整代碼:https://github.com/jingjiangtao/PracticeCollection/tree/master/CustomizeActionFilter