如何在 ASP.NET Core Web API 方法执行前后 “偷偷“ 作一些 “坏“ 事?初识 ActionFilterAttribute

如何在 ASP.NET Core Web API 方法执行前后 “偷偷“ 作一些 “坏“ 事?初识 ActionFilterAttribute ActionFilterAttribute Attribute 方法执行前后 Action Filter ASP. Core 如何在 作一些

image

前言:什么是 ActionFilterAttribute?

ActionFilterAttribute 是一种作用于控制器 Action 方法的特性(Attribute),通过它,你可以在操作执行前后、异常处理时等不同的阶段插入自定义逻辑。

比如在执行操作方法之前修改请求参数、记录日志、进行权限验证等操作,在执行操作方法之后发送邮件、同步数据等等。

本文主要通过一些例子来说明什么是 ActionFilterAttribute 及如何应用。

Step By Step 步骤:

  1. 创建一个 asp.net core webapi 的项目

  2. 直接继承 ActionFilterAttribute 抽象类创建自定义的 Test1ActionFilterAttribute 类并注入 ILogger

    using Microsoft.AspNetCore.Mvc.Filters;
    
    namespace AttributeSample
    {
    	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
    	public class Test1ActionFilterAttribute: ActionFilterAttribute
    	{
    		private ILogger<Test1ActionFilterAttribute> _logger;
    		
    		// 在构造方法里注入 ILogger 
    		public Test1ActionFilterAttribute(ILogger<Test1ActionFilterAttribute> logger)
    		{
    			_logger = logger;
    		}
    		
    		/// <summary>
    		/// 在控制器执行之前调用
    		/// </summary>
    		/// <param name="context"></param>
    		public override void OnActionExecuting(ActionExecutingContext context)
    		{
    			_logger.LogInformation("在控制器执行之前调用...");
    			base.OnActionExecuting(context);
    		}
    
    		/// <summary>
    		/// 在控制器执行之后调用
    		/// </summary>
    		/// <param name="context"></param>
    		public override void OnActionExecuted(ActionExecutedContext context)
    		{
    			_logger.LogInformation("在控制器执行之后调用...");
    			base.OnActionExecuted(context);
    		}
    	}
    }
    
  3. 通过实现 IActionFilter 接口创建自定义的 Test2ActionFilterAttribute 类并注入 ILogger(推荐方式)

    using Microsoft.AspNetCore.Mvc.Filters;
    
    namespace AttributeSample
    {
    	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
    	public class Test2ActionFilterAttribute: Attribute, IActionFilter
    	{
    		private ILogger<Test2ActionFilterAttribute> _logger;
    		
    		// 在构造方法里注入 ILogger 
    		public Test2ActionFilterAttribute(ILogger<Test2ActionFilterAttribute> logger)
    		{
    			_logger = logger;
    		}
    		
    		/// <summary>
    		/// 在控制器执行之前调用
    		/// </summary>
    		/// <param name="context"></param>
    		public void OnActionExecuting(ActionExecutingContext context)
    		{
    			_logger.LogInformation("在控制器执行之前调用...");
    		}
    
    		/// <summary>
    		/// 在控制器执行之后调用
    		/// </summary>
    		/// <param name="context"></param>
    		public void OnActionExecuted(ActionExecutedContext context)
    		{
    			_logger.LogInformation("在控制器执行之后调用...");
    		}
    	}
    }
    
  4. 直接继承 ActionFilterAttribute 抽象类创建自定义的 Test3ActionFilterAttribute 类,不注入其他依赖

    using Microsoft.AspNetCore.Mvc.Filters;
    
    namespace AttributeSample
    {
    	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false)]
    	public class Test3ActionFilterAttribute: Attribute, IActionFilter
    	{
    		private string _myName;
    		public Test3ActionFilterAttribute(string myName)
    		{
    			_myName = myName;
    		}
    		/// <summary>
    		/// 在控制器执行之前调用
    		/// </summary>
    		/// <param name="context"></param>
    		public void OnActionExecuting(ActionExecutingContext context)
    		{
    			_myName += " before";
    		}
    
    		/// <summary>
    		/// 在控制器执行之后调用
    		/// </summary>
    		/// <param name="context"></param>
    		public void OnActionExecuted(ActionExecutedContext context)
    		{
    			_myName += " after";
    		}
    	}
    }
    
  5. 在控制器中应用自定义的 ActionFilterAttribute

    using Microsoft.AspNetCore.Mvc;
    using AttributeSample;
    using System.Reflection;
    
    namespace AttributeSample.Controllers
    {
    	[ApiController]
    	[Route("[controller]")]
    	public class WeatherForecastController : ControllerBase
    	{
    		private static readonly string[] Summaries = new[]
    		{
    			"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    		};
    
    		private readonly ILogger<WeatherForecastController> _logger;
    
    		/// <summary>
    		/// 
    		/// </summary>
    		/// <param name="logger"></param>
    		public WeatherForecastController(ILogger<WeatherForecastController> logger)
    		{
    			_logger = logger;
    		}
    
    		[HttpGet(Name = "GetWeatherForecast")]
    		[TypeFilter(typeof(Test1ActionFilterAttribute))]
    		[TypeFilter(typeof(Test2ActionFilterAttribute))]
    		[Test3ActionFilter("Jacky")]
    		public IEnumerable<WeatherForecast> Get()
    		{
    			var list = Enumerable.Range(1, 5).Select(index => new WeatherForecast
    			{
    				Date = DateTime.Now.AddDays(index),
    				TemperatureC = Random.Shared.Next(-20, 55),
    				Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    			})
    			.ToArray();
    			_logger.LogInformation("执行方法...");
    			return list;
    		}
    	}
    }
    
  6. Swaager 测试,可以看到其执行顺序如下:

    AttributeSample.Test1ActionFilterAttribute: Information: 在控制器执行之前调用...
    AttributeSample.Test2ActionFilterAttribute: Information: 在控制器执行之前调用...
    AttributeSample.Test3ActionFilterAttribute...
    AttributeSample.Controllers.WeatherForecastController: Information: 执行方法...
    AttributeSample.Test3ActionFilterAttribute...
    AttributeSample.Test2ActionFilterAttribute: Information: 在控制器执行之后调用...
    AttributeSample.Test1ActionFilterAttribute: Information: 在控制器执行之后调用...
    

总结:

  1. Asp.net core webapi 使用 ActionFilterAttribute,引用的是 Microsoft.AspNetCore.Mvc.Filters 而不是 System.Web.Http.Filters
    • System.Web.Http.Filters 是属于 .Net FrameWork 的命名空间
  2. ActionFilterAttribute 如果需要在构造方法中注入某些依赖,比如注入 ILogger,有几个使用方法:
    • TypeFilter,无需在IOC中注册,有自实现,本文例子即是使用这种方式
    • ServiceFilter,需要在 Program.cs 中针对该过滤器注册服务才能使用
    • 自定义 CustomIOCFilterFactoryAttribute 实现,依然需要对过滤器进行服务注册
    • 方法2和3比较复杂,以后有时间再针对这两种方式写一些例子
  3. 没有注入其他依赖的 ActionFilterAttribute 如一般 Attribute 使用即可,比如本文的第 3 个 ActionFilterAttribute
    • [Test3ActionFilter("Jacky")]

我是老杨,一个执着于编程乐趣、至今奋斗在一线的 10年+ 资深研发老鸟,是软件项目管理师,也是快乐的程序猿,持续免费分享全栈实用编程技巧、项目管理经验和职场成长心得!欢迎关注老杨的公众号(名称:代码掌控者),和你共同探索代码世界的奥秘!

评论