ASP.NET Core基础之AOP编程(一)-过滤器

阅读本文你的收获:

  1. 了解AOP是OOP思想的补充,它能实现非核心功能的重用
  2. 了解ASP.NET Core中的重要过滤器和特性
  3. 手把手教你写过滤器和特性

引言

AOP(Aspect-OrientedProgramming,面向切面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP的三大特性:封装、继承、多态;继承的目的是为了重用;组合(依赖)也是为了代码的重用。设计原则中告诉我们,写代码要“多组合,少继承”。

一. AOP的思想及AOP的应用场景?

日常编写代码过程中,我们用面向对象的思想对核心的业务模块进行建模,封装数据和行为,利用封装、继承、多态的特性建构出耦合度低,重用性高、扩展性强的系统。然而系统中,还有一些是非核心业务的,但对于一个系统又是必不可少的通用功能,如日志记录、登录验证、参数校验、缓存等,这些功能每个模块可能都需要,用OOP思想去实现这些通用功能的重用,反而会增加系统的复杂性。这时候可以用AOP的思想来补充OOP的补足。

AOP的应用场景有:系统日志记录、异常处理、登录验证、缓存、事务处理等;
AOP的思想就是把各个模块中一些非核心的通用功能(横切关注点),抽离出来,抽离出来的功能就是一个“切面”,然后利用设计模式中的“代理模式”等,将这个切面插入到要执行的具体模块前后。
AOP思想示意图

二. 过滤器(Filters)

Filter-过滤器,官网也叫筛选器。Filter是延续ASP.NET MVC的产物,同样保留了五种的Filter,分别:

过滤器名称应用场景基类或接口内置特性类
Authorization Filter授权过滤器验证登录的时候用IAuthorizationFilter或IAsyncAuthorizationFilterAuthorizeFilter
Resource Filter资源过滤器资源过滤器根据用户请求的地址,查找对应的缓存IResourceFilter (Core里面新加的)
Action FilterAction过滤器在控制器方法的前后做一些操作IActionFilter 或 IAsyncActionFilterActionFilterAttribute
Exception Filter异常过滤器控制器发生异常时由异常过滤器进行处理IExceptionFilterExceptionFilterAttribute
Result FilterResult过滤器控制方法的结果进行一些前后做的一些操作IResultFilterResultFilterAttribute

各个过滤器在中间件管道中的执行交互顺序如下:
过滤器执行顺序

三. 用过滤器实现AOP编程

以下演示用过滤器实现异常处理和方法日志记录,开发环境:

平台版本是:.NET6
开发框架:ASP.NET Core WebApi
开发工具:Visual Studio2022

3.1 异常过滤器记录异常日志信息

  1. 编写自定义异常过滤器

    //编写异常过滤器
    public class GlobalExceptionFilter: IExceptionFilter
    {
        private ILogger<GlobalExceptionFilter> _logger;
        //构造函数 依赖注入 ILogger<> 
        public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
        {
        	_logger = logger;
        }
        
        //实现接口方法
        public void OnException(ExceptionContext context)
        {
            //在方法中,处理异常,写入Nlog日志
            Exception ex = context.Exception;
            var message ="错误信息为"+ ex.Message;
            _logger.LogError(message); //写入日志
    			
            context.ExceptionHandled = true; //把异常标记为已处理
        }
    }
    
  2. 注册自定义异常过滤器

    //在Program.cs 里边的服务注册中添加以下代码
    builder.Services.AddControllers(options =>
    {
       //添加全局异常过滤器
       options.Filters.Add<GlobalExceptionFilter>();
    });
    
  3. 测试自定义异常过滤器
    要让控制器的方法里边,报异常,才能进入异常过滤器里边。

    public IActionResult Test()
    {
    	 int i = 0;
    	 int j = 100/i;  //此处除数i为0,肯定会引发异常,此时异常过滤器就会捕获到异常,并记录到日志文件中
    	 return Ok(j)
    }
    

3.2 行为过滤器记录api接口的运行耗时

实现效果:
运行结果

  1. 编写自定义Action过滤器

     /// <summary>
     /// 全局Action过滤器-在日志文件中输出Action处理耗时
     /// </summary>
     public class GlobalLogActionFilter : IAsyncActionFilter
     {
         private ILogger<GlobalLogActionFilter> _logger;
         public GlobalLogActionFilter(ILogger<GlobalLogActionFilter> logger)
         {
             _logger = logger;
         }
    
         /// <summary>
         /// 过滤器的处理
         /// </summary>
         /// <param name="context"></param>
         /// <param name="next"></param>
         /// <returns></returns>
         public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
         {
             //如果方法上有[NoLog]特性,则不进行记录(请看以下特性的章节)
             if(context.ActionDescriptor.EndpointMetadata.Any(m=>m.GetType()==typeof(NoLogAttribute)))
             {
                 await next(); //不进行记录,直接调用下一个方法
                 return;
             }
    
             //开启秒表计时
             var sw = new Stopwatch();
             sw.Start();
             //执行方法处理
             var result = await next(); //回调我们写的控制器的方法(Action)
             //结束秒表计时
             sw.Stop();
    
             var actionName = context.ActionDescriptor.AttributeRouteInfo.Template.ToLower();
             _logger.LogInformation($"执行了{actionName}方法,用时{sw.ElapsedMilliseconds}毫秒");
         }
     }
    
  2. 注册自定义Action过滤器(全局注册)

    //在Program.cs 里边的服务注册中添加以下代码
    builder.Services.AddControllers(options =>
    {
       ...
       //Action过滤器 
       options.Filters.Add<GlobalLogActionFilter>();
    });
    
  3. 测试自定义Action过滤器
    调用控制器的Action方法的时候,自动会去调用Action过滤器。

四. 特性(Attribute)

4.1 什么是特性(Attribute)?

特性(Attribute)是用于在运行时传递程序中各种元素(比如类、方法、结构、枚举、组件等)的行为信息的声明性标签。您可以通过使用特性向程序添加声明性信息。一个声明性标签是通过放置在它所应用的元素前面的方括号([ ])来描述的。

特性的使用方法很简单,只需要写在方法,类,枚举,字段等程序体的上面用[]括起来即可,如下:

//通过继承Attribute可以定义一个特性类
 [AttributeUsage(AttributeTargets.Class|AttributeTargets.Method,AllowMultiple =true)]
 public class NoLogAttribute : Attribute
 {
 }
//使用特性的例子:
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController:ControllerBase
{
    [HttpGet] //这是个内置特性,标注以下接口方法采用Get方式
    [Obsolete]//这是个内置特性,标注以下方法为过时的方法
    [NoLog]   //这个是自定义特性,标注以下方法不要输出日志
    public void Test()
    {
        Console.WriteLine("这是一个过时的方法");
    }
}

4.2 内置的过滤器特性

以下列举部分常见的内置过滤器特性,我们可以通过继承这些类,快速实现自己的特性类。

  • ActionFilterAttribute :行为过滤器特性
  • ExceptionFilterAttribute:异常过滤器特性
  • ResultFilterAttribute:结果过滤器特性
  • FormatFilterAttribute:响应格式化过滤器特性
  • ServiceFilterAttribute:用来局部使用过滤器的特性
  • TypeFilterAttribute:也是用来局部注入过滤器的特性

4.3 编写一个过滤器特性

使用ActionFilterAttribute这个内置过滤器特性,自定义一个过滤器特性,来实现添加任意响应头部信息

 /// <summary>
 /// 继承了一个 ActionFilterAttribute,这个抽象类继承了什么接口呢?
 /// </summary>
 //ResponseHeaderAttribute是一个自定义特性,[ResponseHeader]
 public class ResponseHeaderAttribute : ActionFilterAttribute  
 {
     private readonly string _name;
     private readonly string _value;

     /// <summary>
     /// 构造函数
     /// </summary>
     /// <param name="name"></param>
     /// <param name="value"></param>
     public ResponseHeaderAttribute(string name, string value)
     {
         (_name, _value) = (name, value); //元组赋值法
     }
         
     /// <summary>
     /// 结果过滤器里边的 结果被执行前
     /// </summary>
     /// <param name="context"></param>
     public override void OnResultExecuting(ResultExecutingContext context)
     {
         //添加响应头,键值对信息  (你对HTTP协议了解多少??)
         context.HttpContext.Response.Headers.Add(_name, _value);
         base.OnResultExecuting(context);
     }
 }

测试以上定义的自定义过滤器

//在控制器上应用
[ResponseHeader("Filter-Header", "Filter Value")]
public class UserController : ControllerBase
{
    public IActionResult Index() =>
        Content("请通过浏览器F12调试工具查看输出的响应头信息");
    // ...
	
    //也可以在方法上面应用
    [ResponseHeader("Another-Filter-Header", "Another Filter Value")]
    public IActionResult Multiple() =>
        Content("请通过浏览器F12调试工具查看输出的响应头信息");
}

4.4 ServiceFilterAttribute 与 TypeFilterAttribute

  1. ServiceFilterAttribute:通过ServiceFilter从容器中检索你的ActionFilter,并且注入到需要的地方。
	 //在Program.cs中必须先注册MyFilter这个过滤器到服务容器中
	 builder.Services.AddSigleton<MyFilter>();

    //控制器方法上用ServiceFilter注入自定义过滤器
    [ServiceFilter(typeof(MyFilter))]
    public IActionResult Index()
    {
        //一些处理 
        ...
        return Ok();
    }

ServiceFilter最值得注意的就是需要对定义的filter进行注册才能使用

  1. TypeFilterAttribute:TypeFilterAttribute 与 ServiceFilterAttribute 类似,但不会直接从 DI 容器解析其类型。 它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 对类型进行实例化。
    //控制器方法上用ServiceFilter注入自定义过滤器
    [TypeFilter(typeof(MyFilter),IsReusable = true)]
    public IActionResult Index()
    {
        //一些处理 
        ...
        return Ok();
    }

它和ServiceFilter的区别是它不从DI容器中创建实例,所以不需要在服务容器中进行注册就能使用。TypeFilter是通过ObjectFactory来创建实例的,ObjectFactory是ActivatorUtilities.CreateFactory创建出来的委托。(请自行深入调研ActivatorUtilities这个工具。)

ServiceFilter与TypeFilter总结

这两个特性都可以实现自定义过滤器的局部注入,但有不同:

  • ServiceFilter和TypeFilter都实现了IFilterFactory
  • ServiceFilter需要对自定义的Filter进行注册,TypeFilter不需要
  • ServiceFilter的Filter生命周期源自于您如何注册,而TypeFilter每次都会创建一个新的实例

本次分享就这么多,希望对你有帮助。下次分享AOP的另外一种实现方式——拦截器,欢迎点赞+评论+关注。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

采石之人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值