在上一篇里,介紹了中間件的相關內容和使用方法。本篇將介紹Asp.Net Core MVC框架的過濾器的相關內容和使用方法,並簡單說明一下與中間件的區別。
第一部分、MVC框架內置過濾器
下圖展示了Asp.Net Core MVC框架默認實現的過濾器的執行順序:
Authorization Filters:身份驗證過濾器,處在整個過濾器通道的最頂層。對應的類型為: AuthorizeAttribute.cs
Resource Filters:資源過濾器。因為所有的請求和響應都將經過這個過濾器,所以在這一層可以實現類似緩存的功能。對應的接口有同步和異步兩個版本: IResourceFilter.cs 、 IAsyncResourceFilter.cs
Action Filters:方法過濾器。在控制器的Action方法執行之前和之后被調用,一個很常用的過濾器。對應的接口有同步和異步兩個版本: IActionFilter.cs 、 IAsyncActionFilter.cs
Exception Filters:異常過濾器。當Action方法執行過程中出現了未處理的異常,將會進入這個過濾器進行統一處理,也是一個很常用的過濾器。對應的接口有同步和異步兩個版本: IExceptionFilter.cs 、 IAsyncExceptionFilter.cs
Result Filters:返回值過濾器。當Action方法執行完成的結果在組裝或者序列化前后被調用。對應的接口有同步和異步兩個版本: IResultFilter.cs 、 IAsyncResultFilter.cs
下面通過代碼示例來演示上面圖示里的流程順序:
1. 在工程里分別添加如下幾個過濾器

1 using System; 2 using Microsoft.AspNetCore.Mvc.Filters; 3 using Microsoft.Extensions.Logging; 4 5 namespace WebApiFrame.Core.Filters 6 { 7 public class SimpleResourceFilterAttribute : Attribute, IResourceFilter 8 { 9 private readonly ILogger<SimpleResourceFilterAttribute> logger; 10 11 public SimpleResourceFilterAttribute(ILoggerFactory loggerFactory) 12 { 13 logger = loggerFactory.CreateLogger<SimpleResourceFilterAttribute>(); 14 } 15 16 public void OnResourceExecuted(ResourceExecutedContext context) 17 { 18 logger.LogInformation("ResourceFilter Executed!"); 19 } 20 21 public void OnResourceExecuting(ResourceExecutingContext context) 22 { 23 logger.LogInformation("ResourceFilter Executing!"); 24 } 25 } 26 }

1 using System; 2 using Microsoft.AspNetCore.Mvc.Filters; 3 using Microsoft.Extensions.Logging; 4 5 namespace WebApiFrame.Core.Filters 6 { 7 public class SimpleActionFilterAttribute : Attribute, IActionFilter 8 { 9 private readonly ILogger<SimpleActionFilterAttribute> logger; 10 11 public SimpleActionFilterAttribute(ILoggerFactory loggerFactory) 12 { 13 logger = loggerFactory.CreateLogger<SimpleActionFilterAttribute>(); 14 } 15 16 public void OnActionExecuted(ActionExecutedContext context) 17 { 18 logger.LogInformation("ActionFilter Executed!"); 19 } 20 21 public void OnActionExecuting(ActionExecutingContext context) 22 { 23 logger.LogInformation("ActionFilter Executing!"); 24 } 25 } 26 }

1 using System; 2 using Microsoft.AspNetCore.Mvc.Filters; 3 using Microsoft.Extensions.Logging; 4 5 namespace WebApiFrame.Core.Filters 6 { 7 public class SimpleExceptionFilterAttribute : Attribute, IExceptionFilter 8 { 9 private readonly ILogger<SimpleExceptionFilterAttribute> logger; 10 11 public SimpleExceptionFilterAttribute(ILoggerFactory loggerFactory) 12 { 13 logger = loggerFactory.CreateLogger<SimpleExceptionFilterAttribute>(); 14 } 15 16 public void OnException(ExceptionContext context) 17 { 18 logger.LogError("Exception Execute! Message:" + context.Exception.Message); 19 context.ExceptionHandled = true; 20 } 21 } 22 }

1 using System; 2 using Microsoft.AspNetCore.Mvc.Filters; 3 using Microsoft.Extensions.Logging; 4 5 namespace WebApiFrame.Core.Filters 6 { 7 public class SimpleResultFilterAttribute : Attribute, IResultFilter 8 { 9 private readonly ILogger<SimpleResultFilterAttribute> logger; 10 11 public SimpleResultFilterAttribute(ILoggerFactory loggerFactory) 12 { 13 logger = loggerFactory.CreateLogger<SimpleResultFilterAttribute>(); 14 } 15 16 public void OnResultExecuted(ResultExecutedContext context) 17 { 18 logger.LogInformation("ResultFilter Executd!"); 19 } 20 21 public void OnResultExecuting(ResultExecutingContext context) 22 { 23 logger.LogInformation("ResultFilter Executing!"); 24 } 25 } 26 }
2. 修改 Startup.cs 內的ConfigureServices方法,作為全局過濾器添加到MVC框架內

1 using Microsoft.AspNetCore.Builder; 2 using Microsoft.Extensions.DependencyInjection; 3 using Microsoft.Extensions.Logging; 4 using WebApiFrame.Core.Filters; 5 6 namespace WebApiFrame 7 { 8 public class Startup 9 { 10 public void ConfigureServices(IServiceCollection services) 11 { 12 // 注入MVC框架 13 services.AddMvc(options => 14 { 15 options.Filters.Add(typeof(SimpleResourceFilterAttribute)); 16 options.Filters.Add(typeof(SimpleActionFilterAttribute)); 17 options.Filters.Add(typeof(SimpleExceptionFilterAttribute)); 18 options.Filters.Add(typeof(SimpleResultFilterAttribute)); 19 }); 20 } 21 22 public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) 23 { 24 // 添加日志支持 25 loggerFactory.WithFilter(new FilterLoggerSettings() 26 { 27 { "Microsoft", LogLevel.Warning } 28 }) 29 .AddConsole().AddDebug(); 30 31 // 添加NLog日志支持 32 //loggerFactory.AddNLog(); 33 34 // 添加MVC中間件 35 app.UseMvc(); 36 } 37 } 38 }
3. 控制器添加兩個方法,一個方法正常返回內容,另一個方法拋出一個未處理的異常

1 using System; 2 using Microsoft.AspNetCore.Mvc; 3 using Microsoft.Extensions.Logging; 4 using WebApiFrame.Models; 5 6 namespace WebApiFrame.Controllers 7 { 8 9 [Route("api/[controller]")] 10 public class UsersController : Controller 11 { 12 private ILogger<UsersController> _logger; 13 14 public UsersController(ILogger<UsersController> logger){ 15 _logger = logger; 16 } 17 18 [HttpGet] 19 public IActionResult GetAll(){ 20 throw new Exception("GetAll function failed!"); 21 } 22 23 [HttpGet("{id}")] 24 public IActionResult Get(int id) 25 { 26 var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" }; 27 return new ObjectResult(user); 28 } 29 30 [HttpPost] 31 public IActionResult Post([FromBody] User user){ 32 if(user == null){ 33 return BadRequest(); 34 } 35 36 // TODO:新增操作 37 user.Id = new Random().Next(1, 10); 38 return CreatedAtAction("Get", new { id = user.Id }, user); 39 } 40 41 [HttpPut("{id}")] 42 public IActionResult Put(int id, [FromBody] User user){ 43 if(user == null){ 44 return BadRequest(); 45 } 46 47 // TODO: 更新操作 48 return new NoContentResult(); 49 } 50 51 [HttpDelete("{id}")] 52 public void Delete(int id){ 53 // TODO: 刪除操作 54 55 } 56 } 57 }
4. 打開cmd窗口,使用命令行 dotnet run 啟動程序,訪問地址 http://localhost:5000/api/users/1 ,查看窗口日志,會發現日志打印順序與圖片標識順序相符。
再次訪問地址 http://localhost:5000/api/users/ ,查看窗口日志,發現異常過濾器被調用,輸出了異常日志
第二部分、過濾器的引用
先創建一個自定義的ActionFilter作為演示例子
1 using System; 2 using Microsoft.AspNetCore.Mvc.Filters; 3 4 namespace WebApiFrame.Core.Filters 5 { 6 public class MyActionFilterAttribute : Attribute, IActionFilter 7 { 8 public void OnActionExecuted(ActionExecutedContext context) 9 { 10 11 } 12 13 public void OnActionExecuting(ActionExecutingContext context) 14 { 15 context.HttpContext.Response.Headers.Add("My-Header", "WebApiFrame-Header"); 16 } 17 } 18 }
一、作為特性標識引用
標識在控制器上,則訪問這個控制器下的所有方法都將調用這個過濾器
1 using System; 2 using Microsoft.AspNetCore.Mvc; 3 using Microsoft.Extensions.Logging; 4 using WebApiFrame.Core.Filters; 5 using WebApiFrame.Models; 6 7 namespace WebApiFrame.Controllers 8 { 9 10 [Route("api/[controller]")] 11 [MyActionFilter] 12 public class UsersController : Controller 13 { 14 private ILogger<UsersController> _logger; 15 16 public UsersController(ILogger<UsersController> logger) 17 { 18 _logger = logger; 19 } 20 21 [HttpGet] 22 public IActionResult GetAll() 23 { 24 throw new Exception("GetAll function failed!"); 25 } 26 27 [HttpGet("{id}")] 28 public IActionResult Get(int id) 29 { 30 var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" }; 31 return new ObjectResult(user); 32 } 33 34 #region 其他方法 35 // ...... 36 #endregion 37 } 38 }
通過Fiddle工具訪問地址 http://localhost:5000/api/users/1 ,查看響應內容,可以發現響應頭部增加了自定義內容
也可以標識在方法上,則只有被標識的方法被調用時才會調用過濾器
1 using System; 2 using Microsoft.AspNetCore.Mvc; 3 using Microsoft.Extensions.Logging; 4 using WebApiFrame.Core.Filters; 5 using WebApiFrame.Models; 6 7 namespace WebApiFrame.Controllers 8 { 9 10 [Route("api/[controller]")] 11 public class UsersController : Controller 12 { 13 private ILogger<UsersController> _logger; 14 15 public UsersController(ILogger<UsersController> logger) 16 { 17 _logger = logger; 18 } 19 20 [HttpGet] 21 public IActionResult GetAll() 22 { 23 throw new Exception("GetAll function failed!"); 24 } 25 26 [HttpGet("{id}")] 27 [MyActionFilter] 28 public IActionResult Get(int id) 29 { 30 var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" }; 31 return new ObjectResult(user); 32 } 33 34 #region 其他方法 35 // ...... 36 #endregion 37 } 38 }
二、全局過濾器
在第一部分的示例中采用的就是全局過濾器的方式。使用了全局過濾器后,所有的控制器下的所有方法被調用時都將調用這個過濾器。
下面的代碼示例是通過生成實例的形式注冊過濾器
1 using Microsoft.AspNetCore.Builder; 2 using Microsoft.Extensions.DependencyInjection; 3 using Microsoft.Extensions.Logging; 4 using WebApiFrame.Core.Filters; 5 6 namespace WebApiFrame 7 { 8 public class Startup 9 { 10 public void ConfigureServices(IServiceCollection services) 11 { 12 // 注入MVC框架 13 services.AddMvc(options => 14 { 15 options.Filters.Add(new MyActionFilterAttribute()); 16 }); 17 } 18 19 public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) 20 { 21 // 添加日志支持 22 loggerFactory.WithFilter(new FilterLoggerSettings() 23 { 24 { "Microsoft", LogLevel.Warning } 25 }) 26 .AddConsole().AddDebug(); 27 28 // 添加NLog日志支持 29 //loggerFactory.AddNLog(); 30 31 // 添加MVC中間件 32 app.UseMvc(); 33 } 34 } 35 }
也可以通過類型進行注冊
1 using Microsoft.AspNetCore.Builder; 2 using Microsoft.Extensions.DependencyInjection; 3 using Microsoft.Extensions.Logging; 4 using WebApiFrame.Core.Filters; 5 6 namespace WebApiFrame 7 { 8 public class Startup 9 { 10 public void ConfigureServices(IServiceCollection services) 11 { 12 // 注入MVC框架 13 services.AddMvc(options => 14 { 15 // 實例注冊 16 //options.Filters.Add(new MyActionFilterAttribute()); 17 18 // 類型注冊 19 options.Filters.Add(typeof(MyActionFilterAttribute)); 20 }); 21 } 22 23 public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory) 24 { 25 // 添加日志支持 26 loggerFactory.WithFilter(new FilterLoggerSettings() 27 { 28 { "Microsoft", LogLevel.Warning } 29 }) 30 .AddConsole().AddDebug(); 31 32 // 添加NLog日志支持 33 //loggerFactory.AddNLog(); 34 35 // 添加MVC中間件 36 app.UseMvc(); 37 } 38 } 39 }
三、通過ServiceFilter引用
通過在控制器或者Action方法上使用ServiceFilter特性標識引用過濾器。通過此方法可以將通過構造方法進行注入並實例化的過濾器引入框架內。
修改一下 MyActionFilterAttribute.cs 內容,添加一個帶參數的構造方法,引入日志記錄
1 using System; 2 using Microsoft.AspNetCore.Mvc.Filters; 3 using Microsoft.Extensions.Logging; 4 5 namespace WebApiFrame.Core.Filters 6 { 7 public class MyActionFilterAttribute : Attribute, IActionFilter 8 { 9 private readonly ILogger<MyActionFilterAttribute> logger; 10 11 public MyActionFilterAttribute(ILoggerFactory loggerFactory) 12 { 13 logger = loggerFactory.CreateLogger<MyActionFilterAttribute>(); 14 } 15 16 public void OnActionExecuted(ActionExecutedContext context) 17 { 18 19 } 20 21 public void OnActionExecuting(ActionExecutingContext context) 22 { 23 context.HttpContext.Response.Headers.Add("My-Header", "WebApiFrame-Header"); 24 logger.LogInformation("MyActionFilterAttribute Executiong!"); 25 } 26 } 27 }
修改 Startup.cs 的ConfigureServices方法,將過濾器類型注入到DI(依賴注入)容器里
1 public void ConfigureServices(IServiceCollection services) 2 { 3 // 注入MVC框架 4 services.AddMvc(options => 5 { 6 // 實例注冊 7 //options.Filters.Add(new MyActionFilterAttribute()); 8 9 // 類型注冊 10 //options.Filters.Add(typeof(MyActionFilterAttribute)); 11 }); 12 13 // 將過濾器類型添加到DI容器里 14 services.AddScoped<MyActionFilterAttribute>(); 15 }
四、通過TypeFilter引入
再次修改 MyActionFilterAttribute.cs 的構造器方法,添加普通的參數
1 using System; 2 using Microsoft.AspNetCore.Mvc.Filters; 3 using Microsoft.Extensions.Logging; 4 5 namespace WebApiFrame.Core.Filters 6 { 7 public class MyActionFilterAttribute : Attribute, IActionFilter 8 { 9 private readonly string _key; 10 private readonly string _value; 11 12 public MyActionFilterAttribute(string key, string value) 13 { 14 _key = key; 15 _value = value; 16 } 17 18 public void OnActionExecuting(ActionExecutingContext context) 19 { 20 context.HttpContext.Response.Headers.Add(_key, _value); 21 } 22 23 public void OnActionExecuted(ActionExecutedContext context) 24 { 25 26 } 27 } 28 }
在 UsersController.cs 控制器的Action方法上添加特性標識,同時注釋掉 Startup.cs 的ConfigureServices的類型注入方法。因為用TypeFilter引用過濾器不需要將類型注入到DI容器
1 [HttpGet("{id}")] 2 [TypeFilter(typeof(MyActionFilterAttribute), Arguments = new object[]{ "My-Header", "WebApiFrame-Header" })] 3 public IActionResult Get(int id) 4 { 5 var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" }; 6 return new ObjectResult(user); 7 }
另外,也可以通過TypeFilter引用需要通過構造方法注入進行實例化的過濾器。將上面第三點的例子里的進行改寫
1 using Microsoft.AspNetCore.Mvc; 2 using Microsoft.AspNetCore.Mvc.Filters; 3 using Microsoft.Extensions.Logging; 4 5 namespace WebApiFrame.Core.Filters 6 { 7 public class MyActionFilterAttribute : TypeFilterAttribute 8 { 9 public MyActionFilterAttribute() : base(typeof(MyActionFilterImpl)) 10 { 11 12 } 13 14 private class MyActionFilterImpl : IActionFilter 15 { 16 private readonly ILogger<MyActionFilterImpl> logger; 17 18 public MyActionFilterImpl(ILoggerFactory loggerFactory) 19 { 20 logger = loggerFactory.CreateLogger<MyActionFilterImpl>(); 21 } 22 23 public void OnActionExecuting(ActionExecutingContext context) 24 { 25 context.HttpContext.Response.Headers.Add("My-Header", "WebApiFrame-Header"); 26 logger.LogInformation("MyActionFilterAttribute Executiong!"); 27 } 28 29 public void OnActionExecuted(ActionExecutedContext context) 30 { 31 32 } 33 } 34 } 35 }
修改 UsersController.cs 控制器的Action方法的特性標識
1 [HttpGet("{id}")] 2 [MyActionFilter] 3 public IActionResult Get(int id) 4 { 5 var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" }; 6 return new ObjectResult(user); 7 }
第三部分、自定義過濾器執行順序
以ActionFilter執行順序為例,默認執行順序如下圖
1. Controller OnActionExecuting
2. Global OnActionExecuting
3. Class OnActionExecuting
4. Method OnActionExecuting
5. Method OnActionExecuted
6. Class OnActionExecuted
7. Global OnActionExecuted
8. Controller OnActionExecuted
下面用代碼驗證這個順序
修改 MyActionFilterAttribute.cs 內容。添加實現接口 IOrderedFilter.cs ,設置默認順序為0
using System; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Logging; namespace WebApiFrame.Core.Filters { public class MyActionFilterAttribute : Attribute, IActionFilter, IOrderedFilter { private readonly int _order; private readonly string _target; private readonly ILogger<MyActionFilterAttribute> logger; public int Order { get { return _order; } } public MyActionFilterAttribute(string target, int order = 0) { _order = order; _target = target; ILoggerFactory loggerFactory = new LoggerFactory(); loggerFactory.WithFilter(new FilterLoggerSettings() { { "Microsoft", LogLevel.Warning } }) .AddConsole().AddDebug(); logger = loggerFactory.CreateLogger<MyActionFilterAttribute>(); } public void OnActionExecuted(ActionExecutedContext context) { logger.LogInformation($"{_target} Executed!"); } public void OnActionExecuting(ActionExecutingContext context) { logger.LogInformation($"{_target} Executing!"); } } }
在 UsersController.cs 重寫OnActionExecuting和OnActionExecuted。同時分別注冊全局過濾器、標識控制器過濾器和方法過濾器
1 using System; 2 using Microsoft.AspNetCore.Mvc; 3 using Microsoft.AspNetCore.Mvc.Filters; 4 using Microsoft.Extensions.Logging; 5 using WebApiFrame.Core.Filters; 6 using WebApiFrame.Models; 7 8 namespace WebApiFrame.Controllers 9 { 10 11 [Route("api/[controller]")] 12 [MyActionFilter("Class")] 13 public class UsersController : Controller 14 { 15 private ILogger<UsersController> _logger; 16 17 public UsersController(ILogger<UsersController> logger) 18 { 19 _logger = logger; 20 } 21 22 [HttpGet] 23 public IActionResult GetAll() 24 { 25 throw new Exception("GetAll function failed!"); 26 } 27 28 [HttpGet("{id}")] 29 [MyActionFilter("Method")] 30 public IActionResult Get(int id) 31 { 32 var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" }; 33 return new ObjectResult(user); 34 } 35 36 public override void OnActionExecuting(ActionExecutingContext context) 37 { 38 _logger.LogInformation("Controller Executing!"); 39 } 40 41 public override void OnActionExecuted(ActionExecutedContext context) 42 { 43 _logger.LogInformation("Controller Executd!"); 44 } 45 46 #region 其他方法 47 // ...... 48 #endregion 49 } 50 }
1 using Microsoft.AspNetCore.Builder; 2 using Microsoft.Extensions.DependencyInjection; 3 using Microsoft.Extensions.Logging; 4 using WebApiFrame.Core.Filters; 5 6 namespace WebApiFrame 7 { 8 public class Startup 9 { 10 public void ConfigureServices(IServiceCollection services) 11 { 12 // 注入MVC框架 13 services.AddMvc(options => 14 { 15 // 實例注冊 16 options.Filters.Add(new MyActionFilterAttribute("Global")); 17 }); 18 } 19 20 // ...... 21 } 22 }
啟動程序,訪問地址 http://localhost:5000/api/users/1 ,查看日志
接下來,修改一下標識方法控制器的參數
1 [HttpGet("{id}")] 2 [MyActionFilter("Method", -1)] 3 public IActionResult Get(int id) 4 { 5 var user = new User() { Id = id, Name = "Name:" + id, Sex = "Male" }; 6 return new ObjectResult(user); 7 }
再次啟動程序,訪問地址 http://localhost:5000/api/users/1 ,查看日志
當順序被設置為-1時,對應標識位置的過濾器將優先調用。但是無法先於控制器的重寫方法調用。
第四部分、過濾器與中間件
1. 過濾器是MVC框架的一部分,中間件屬於Asp.Net Core管道的一部分。
2. 過濾器在處理請求和響應時更加的精細一些,在用戶權限、資源訪問、Action執行、異常處理、返回值處理等方面都能進行控制和處理。而中間件只能粗略的過濾請求和響應。