阅读本文你的收获:
- 了解AOP是OOP思想的补充,它能实现非核心功能的重用
- 了解ASP.NET Core中的重要过滤器和特性
- 手把手教你写过滤器和特性
引言
AOP(Aspect-OrientedProgramming,面向切面编程),可以说是OOP(Object-Oriented Programing,面向对象编程)的补充和完善。OOP的三大特性:封装、继承、多态;继承的目的是为了重用;组合(依赖)也是为了代码的重用。设计原则中告诉我们,写代码要“多组合,少继承”。
一. AOP的思想及AOP的应用场景?
日常编写代码过程中,我们用面向对象的思想对核心的业务模块进行建模
,封装数据和行为,利用封装、继承、多态的特性建构出耦合度低,重用性高、扩展性强的系统。然而系统中,还有一些是非核心业务的,但对于一个系统又是必不可少的通用功能,如日志记录、登录验证、参数校验、缓存等,这些功能每个模块可能都需要,用OOP思想去实现这些通用功能的重用,反而会增加系统的复杂性。这时候可以用AOP的思想来补充OOP的补足。
AOP的应用场景有:系统日志记录、异常处理、登录验证、缓存、事务处理等;
AOP的思想就是把各个模块中一些非核心的通用功能(横切关注点)
,抽离出来,抽离出来的功能就是一个“切面”,然后利用设计模式中的“代理模式”等,将这个切面插入到要执行的具体模块前后。
二. 过滤器(Filters)
Filter-过滤器,官网也叫筛选器。Filter是延续ASP.NET MVC的产物,同样保留了五种的Filter,分别:
过滤器 | 名称 | 应用场景 | 基类或接口 | 内置特性类 |
---|---|---|---|---|
Authorization Filter | 授权过滤器 | 验证登录的时候用 | IAuthorizationFilter或IAsyncAuthorizationFilter | AuthorizeFilter |
Resource Filter | 资源过滤器 | 资源过滤器根据用户请求的地址,查找对应的缓存 | IResourceFilter (Core里面新加的) | |
Action Filter | Action过滤器 | 在控制器方法的前后做一些操作 | IActionFilter 或 IAsyncActionFilter | ActionFilterAttribute |
Exception Filter | 异常过滤器 | 控制器发生异常时由异常过滤器进行处理 | IExceptionFilter | ExceptionFilterAttribute |
Result Filter | Result过滤器 | 控制方法的结果进行一些前后做的一些操作 | IResultFilter | ResultFilterAttribute |
各个过滤器在中间件管道中的执行交互顺序如下:
三. 用过滤器实现AOP编程
以下演示用过滤器实现异常处理和方法日志记录,开发环境:
平台版本是:.NET6
开发框架:ASP.NET Core WebApi
开发工具:Visual Studio2022
3.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; //把异常标记为已处理 } }
-
注册自定义异常过滤器
//在Program.cs 里边的服务注册中添加以下代码 builder.Services.AddControllers(options => { //添加全局异常过滤器 options.Filters.Add<GlobalExceptionFilter>(); });
-
测试自定义异常过滤器
要让控制器的方法里边,报异常,才能进入异常过滤器里边。public IActionResult Test() { int i = 0; int j = 100/i; //此处除数i为0,肯定会引发异常,此时异常过滤器就会捕获到异常,并记录到日志文件中 return Ok(j) }
3.2 行为过滤器记录api接口的运行耗时
实现效果:
-
编写自定义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}毫秒"); } }
-
注册自定义Action过滤器(全局注册)
//在Program.cs 里边的服务注册中添加以下代码 builder.Services.AddControllers(options => { ... //Action过滤器 options.Filters.Add<GlobalLogActionFilter>(); });
-
测试自定义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
- ServiceFilterAttribute:通过ServiceFilter从容器中检索你的ActionFilter,并且注入到需要的地方。
//在Program.cs中必须先注册MyFilter这个过滤器到服务容器中
builder.Services.AddSigleton<MyFilter>();
//控制器方法上用ServiceFilter注入自定义过滤器
[ServiceFilter(typeof(MyFilter))]
public IActionResult Index()
{
//一些处理
...
return Ok();
}
ServiceFilter最值得注意的就是需要对定义的filter进行注册才能使用
。
- 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的另外一种实现方式——拦截器,欢迎点赞+评论+关注。