在上一篇裏,介紹了中間件的相關內容和使用方法。本篇將介紹Asp.Net Core MVC框架的過濾器的相關內容和使用方法,並簡單說明一下與中間件的區別。html
下圖展現了Asp.Net Core MVC框架默認實現的過濾器的執行順序:api
Authorization Filters:身份驗證過濾器,處在整個過濾器通道的最頂層。對應的類型爲: AuthorizeAttribute.cs 緩存
Resource Filters:資源過濾器。由於全部的請求和響應都將通過這個過濾器,因此在這一層能夠實現相似緩存的功能。對應的接口有同步和異步兩個版本: IResourceFilter.cs 、 IAsyncResourceFilter.cs app
Action Filters:方法過濾器。在控制器的Action方法執行以前和以後被調用,一個很經常使用的過濾器。對應的接口有同步和異步兩個版本: IActionFilter.cs 、 IAsyncActionFilter.cs 框架
Exception Filters:異常過濾器。當Action方法執行過程當中出現了未處理的異常,將會進入這個過濾器進行統一處理,也是一個很經常使用的過濾器。對應的接口有同步和異步兩個版本: IExceptionFilter.cs 、 IAsyncExceptionFilter.cs dom
Result Filters:返回值過濾器。當Action方法執行完成的結果在組裝或者序列化先後被調用。對應的接口有同步和異步兩個版本: IResultFilter.cs 、 IAsyncResultFilter.cs 異步
下面經過代碼示例來演示上面圖示裏的流程順序:ide
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框架內ui
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 }
經過在控制器或者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 }
再次修改 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執行、異常處理、返回值處理等方面都能進行控制和處理。而中間件只能粗略的過濾請求和響應。