前言
AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程;
利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。
.Net Core 对于 AOP 多种支持
AuthorizeAttribute(权限验证)
Post not found: DotNetCore/05.权限验证 AuthorizeAttribute 权限验证
IResourceFilter(资源缓存)
Asp.Net Core 6 中提供的是 IResourceFilter
/ IAsyncResourceFilter
用途:缓存
执行顺序:
- MyResourceFilterAttribute.OnResourceExecuting
- 执行控制器构造函数
- 执行 Action 方法
- MyResourceFilterAttribute.OnResourceExecuted
MyResourceFilterAttribute.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class MyResourceFilterAttribute : Attribute, IResourceFilter { private static Dictionary<string, object> cacheDictionary = new Dictionary<string, object>(); public void OnResourceExecuting(ResourceExecutingContext context) { string cacheKey = context.HttpContext.Request.Path; if (cacheDictionary.ContainsKey(cacheKey)) { context.Result = cacheDictionary[cacheKey] as Microsoft.AspNetCore.Mvc.IActionResult; } Console.WriteLine("MyResourceFilterAttribute.OnResourceExecuting"); } public void OnResourceExecuted(ResourceExecutedContext context) { string cacheKey = context.HttpContext.Request.Path; cacheDictionary[cacheKey] = context.Result; Console.WriteLine("MyResourceFilterAttribute.OnResourceExecuted"); } }
|
AsyncMyResourceFilterAttribute.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class AsyncMyResourceFilterAttribute : Attribute, IAsyncResourceFilter { private static Dictionary<string, object> cacheDictionary = new Dictionary<string, object>();
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) { Console.WriteLine("AsyncMyResourceFilterAttribute.OnResourceExecutionAsync.Before"); string cacheKey = $"{context.HttpContext.Request.Path}{context.HttpContext.Request.QueryString}"; if (cacheDictionary.ContainsKey(cacheKey)) { context.Result = cacheDictionary[cacheKey] as IActionResult; } else { ResourceExecutedContext resource = await next.Invoke(); cacheDictionary[cacheKey] = resource.Result;
Console.WriteLine("AsyncMyResourceFilterAttribute.OnResourceExecutionAsync.After"); }
} }
|
IActionFilter(方法前后的记录)
Asp.Net Core 6 中提供的是 IActionFilter
/ IAsyncActionFilter
/ ActionFilterAttribute
用途:记录日志
更接近 Action
执行顺序:
- 执行控制器构造函数
- 执行 MyActionFilterAttribute.OnActionExecuting
- 执行 Action 方法
- 执行 MyActionFilterAttribute.OnActionExecuted
MyActionFilterAttribute.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class MyActionFilterAttribute : Attribute, IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("MyActionFilterAttribute.OnActionExecuting"); } public void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("MyActionFilterAttribute.OnActionExecuted"); } }
|
记录日志
MyLogActionFilterAttribute.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class MyLogActionFilterAttribute : Attribute, IActionFilter { private readonly ILogger<MyLogActionFilterAttribute> _logger; public MyLogActionFilterAttribute(ILogger<MyLogActionFilterAttribute> logger) { _logger = logger; } public void OnActionExecuting(ActionExecutingContext context) { var param = context.HttpContext.Request.QueryString.Value; var controllerName = context.HttpContext.GetRouteValue("controller"); var actionName = context.HttpContext.GetRouteValue("action"); _logger.LogInformation($"{controllerName}/{actionName} 请求参数:{param}"); } public void OnActionExecuted(ActionExecutedContext context) { var returnValue = System.Text.Json.JsonSerializer.Serialize(context.Result); var controllerName = context.HttpContext.GetRouteValue("controller"); var actionName = context.HttpContext.GetRouteValue("action"); _logger.LogInformation($"{controllerName}/{actionName} 响应参数:{returnValue}"); } }
|
使用方法:
1 2 3
| [TypeFilter(typeof(Filters.MyLogActionFilterAttribute))]
[ServiceFilter(typeof(Filters.MyLogActionFilterAttribute))]
|
AsyncMyLogActionFilterAttribute.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class AsyncMyLogActionFilterAttribute : Attribute, IAsyncActionFilter { private readonly ILogger<MyLogActionFilterAttribute> _logger; public AsyncMyLogActionFilterAttribute(ILogger<MyLogActionFilterAttribute> logger) { _logger = logger; }
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { var controllerName = context.HttpContext.GetRouteValue("controller"); var actionName = context.HttpContext.GetRouteValue("action");
var param = context.HttpContext.Request.QueryString.Value; _logger.LogInformation($"{controllerName}/{actionName} 请求参数:{param}"); ActionExecutedContext actionExecutedContext = await next.Invoke();
var returnValue = System.Text.Json.JsonSerializer.Serialize(actionExecutedContext.Result); _logger.LogInformation($"{controllerName}/{actionName} 响应参数:{returnValue}"); } }
|
IResultFilter(结果生成前后扩展)
Asp.Net Core 6 中提供的是 IActionFilter
/ IAsyncActionFilter
/ ActionFilterAttribute
用途:统一返回结果
执行顺序:
- 执行 MyResultFilterAttribute.OnResultExecuting
- 开始生成渲染视图内容
- MyResultFilterAttribute.OnResultExecuted
MyResultFilterAttribute.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class MyResultFilterAttribute : Attribute, IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("MyResultFilterAttribute.OnResultExecuting"); } public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("MyResultFilterAttribute.OnResultExecuted"); } }
|
响应统一的 Json 格式
MyJsonResultFilterAttribute.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class MyJsonResultFilterAttribute : Attribute, IResultFilter { public void OnResultExecuting(ResultExecutingContext context) { if (context.Result is JsonResult) { var result = context.Result as JsonResult; context.Result = new JsonResult(new { Success = true, Message = "Ok", Data = result.Value }); } Console.WriteLine("MyResultFilterAttribute.OnResultExecuting"); } public void OnResultExecuted(ResultExecutedContext context) { } }
|
AsyncMyJsonResultFilterAttribute.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| public class AsyncMyJsonResultFilterAttribute : Attribute, IAsyncResultFilter { public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { if (context.Result is JsonResult) { var result = context.Result as JsonResult; context.Result = new JsonResult(new { Success = true, Message = "Ok", Data = result.Value }); }
ResultExecutedContext resultExecutedContext = await next.Invoke(); } }
|
ActionResultFilter (IActionFilter+IResultFilter)
如果重写了异步版本,同步版本则不执行
MyActionResultFilterAttribute.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| public class MyActionResultFilterAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { base.OnActionExecuting(context); } public override void OnActionExecuted(ActionExecutedContext context) { base.OnActionExecuted(context); }
public override Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { return base.OnActionExecutionAsync(context, next); }
public override void OnResultExecuting(ResultExecutingContext context) { base.OnResultExecuting(context); } public override void OnResultExecuted(ResultExecutedContext context) { base.OnResultExecuted(context); }
public override Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) { return base.OnResultExecutionAsync(context, next); } }
|
IAlwaysRun(响应结果的补充)
只要给 context.Result 赋值了,就会中断往后执行,但是依然会执行 IAlwaysRun
MyAlwaysRunAttribute.cs
1 2 3 4 5 6 7 8 9 10 11 12
| public class MyAlwaysRunAttribute : Attribute, IAlwaysRunResultFilter {
public void OnResultExecuting(ResultExecutingContext context) { Console.WriteLine("MyAlwaysRunAttribute.OnResultExecuting"); } public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("MyAlwaysRunAttribute.OnResultExecuted"); } }
|
IExceptionFilter(异常处理)
Asp.Net Core 6 中提供的是 IExceptionFilter
/ IAsyncExceptionFilter
用途:处理异常
MyExceptionFilterAttribute.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| public class MyExceptionFilterAttribute : Attribute, IExceptionFilter, IAsyncExceptionFilter { private readonly IModelMetadataProvider _modelMetadataProvider; public MyExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider) { _modelMetadataProvider = modelMetadataProvider;
} public void OnException(ExceptionContext context) { throw new NotImplementedException(); }
public async Task OnExceptionAsync(ExceptionContext context) { if (context.ExceptionHandled == false) { if (IsAjaxRequest(context.HttpContext.Request)) { context.Result = new JsonResult(new { Success = false, Message = context.Exception.Message }); } else { ViewResult result = new() { ViewName = "~/Views/Shared/Error.cshtml", ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState) }; result.ViewData.Add(nameof(context.Exception), context.Exception); context.Result = result; } context.ExceptionHandled = true; }
await Task.CompletedTask; }
private bool IsAjaxRequest(HttpRequest request) { string header = request.Headers["X-Requested-With"]; return "XMLHttpRequest".Equals(header); } }
|
异常处理的场景分析
- Action 出现没有处理的异常【√】
- Action 出现已经处理的异常【×】
- Service 层出现的异常【√】
- View 绑定时出现的异常【×】
- 不存在的 Url 地址【×】
- 其他 Filter 中出现的异常
- ActionResultFilter 【√】
- ResourceFilter 【×】
- ResultFilter 【×】
- 控制器构造函数中的异常【√】
使用中间件捕捉异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| app.UseStatusCodePagesWithReExecute("/Home/Error/{0}");
app.UseExceptionHandler(errorApp => { errorApp.Run(async context => { context.Response.StatusCode = 200; context.Response.ContentType = "text/html"; await context.Response.WriteAsync("<html lang=\"zh\"><body>\r\n"); await context.Response.WriteAsync("ERROR!<br><br>\r\n"); var exceptionHandlerPathFeature = context.Features.Get<IExceptionHandlerPathFeature>();
global::System.Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&"); global::System.Console.WriteLine($"{exceptionHandlerPathFeature?.Error.Message}"); global::System.Console.WriteLine("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&");
if (exceptionHandlerPathFeature?.Error is FileNotFoundException) { await context.Response.WriteAsync("File error thrown!<br><br>\r\n"); }
await context.Response.WriteAsync("<a href=\"/\">Home</a><br>\r\n"); await context.Response.WriteAsync("</html></body>\r\n"); await context.Response.WriteAsync(new string(' ',512)); }); });
|