前言

AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程;

利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

主要的功能是:日志记录,性能统计,安全控制,事务处理,异常处理等等。

.Net Core 对于 AOP 多种支持

AuthorizeAttribute(权限验证)

Post not found: DotNetCore/05.权限验证 AuthorizeAttribute 权限验证

IResourceFilter(资源缓存)

Asp.Net Core 6 中提供的是 IResourceFilter / IAsyncResourceFilter

用途:缓存

执行顺序:

  1. MyResourceFilterAttribute.OnResourceExecuting
  2. 执行控制器构造函数
  3. 执行Action方法
  4. 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>();
/// <summary>
/// 在XX资源前
/// </summary>
/// <param name="context"></param>
/// <exception cref="NotImplementedException"></exception>
public void OnResourceExecuting(ResourceExecutingContext context)
{
string cacheKey = context.HttpContext.Request.Path;
if (cacheDictionary.ContainsKey(cacheKey))
{
//只要给context.Result赋值了,就会中断往后执行,直接返回结果
context.Result = cacheDictionary[cacheKey] as Microsoft.AspNetCore.Mvc.IActionResult;
}
Console.WriteLine("MyResourceFilterAttribute.OnResourceExecuting");
}
/// <summary>
/// 在XX资源后
/// </summary>
/// <param name="context"></param>
/// <exception cref="NotImplementedException"></exception>
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>();

/// <summary>
/// 在XX资源执行的时候
/// </summary>
/// <param name="context"></param>
/// <param name="next"></param>
/// <returns></returns>
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赋值了,就会中断往后执行,直接返回结果
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

执行顺序:

  1. 执行控制器构造函数
  2. 执行MyActionFilterAttribute.OnActionExecuting
  3. 执行Action方法
  4. 执行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
{
/// <summary>
/// 在XXAction执行前
/// </summary>
/// <param name="context"></param>
public void OnActionExecuting(ActionExecutingContext context)
{
Console.WriteLine("MyActionFilterAttribute.OnActionExecuting");
}
/// <summary>
/// 在XXAction执行后
/// </summary>
/// <param name="context"></param>
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;
}
/// <summary>
/// 在XXAction执行前
/// </summary>
/// <param name="context"></param>
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}");
}
/// <summary>
/// 在XXAction执行后
/// </summary>
/// <param name="context"></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

用途:统一返回结果

执行顺序:

  1. 执行MyResultFilterAttribute.OnResultExecuting
  2. 开始生成渲染视图内容
  3. 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
{
/// <summary>
/// 在XX结果前
/// </summary>
/// <param name="context"></param>
/// <exception cref="NotImplementedException"></exception>
public void OnResultExecuting(ResultExecutingContext context)
{
Console.WriteLine("MyResultFilterAttribute.OnResultExecuting");
}
/// <summary>
/// 在XX结果后
/// </summary>
/// <param name="context"></param>
/// <exception cref="NotImplementedException"></exception>
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
{
/// <summary>
/// 在XX结果前
/// </summary>
/// <param name="context"></param>
/// <exception cref="NotImplementedException"></exception>
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");
}
/// <summary>
/// 在XX结果后
/// </summary>
/// <param name="context"></param>
/// <exception cref="NotImplementedException"></exception>
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);
}
}

异常处理的场景分析

  1. Action 出现没有处理的异常【√】
  2. Action 出现已经处理的异常【×】
  3. Service 层出现的异常【√】
  4. View 绑定时出现的异常【×】
  5. 不存在的Url地址【×】
  6. 其他 Filter 中出现的异常
    1. ActionResultFilter 【√】
    2. ResourceFilter 【×】
    3. ResultFilter 【×】
  7. 控制器构造函数中的异常【√】

使用中间件捕捉异常

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
//如果Http请求中的Response中的状态不是200,就会进入/Home/Error/{0}中
app.UseStatusCodePagesWithReExecute("/Home/Error/{0}");
//自己拼一个Response 输出
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));//IE padding
});
});