在上一篇里,介绍了中间件的相关内容和使用方法。本篇将介绍Asp.Net Core 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. 在工程里分别添加如下几个过滤器
2. 修改 Startup.cs 内的ConfigureServices方法,作为全局过滤器添加到MVC框架内
3. 控制器添加两个方法,一个方法正常返回内容,另一个方法抛出一个未处理的异常
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 : Controller13 {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 #endregion37 }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 : Controller12 {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 #endregion37 }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 : IActionFilter15 {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 OnActionExecuting2. Global OnActionExecuting3. Class OnActionExecuting4. Method OnActionExecuting5. Method OnActionExecuted6. Class OnActionExecuted7. Global OnActionExecuted8. 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 : Controller14 {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 #endregion49 }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执行、异常处理、返回值处理等方面都能进行控制和处理。而中间件只能粗略的过滤请求和响应。
联系客服