FIlter
一般存放在 Web API 层的 Filters
目录下,以 Filter
为后缀命名。
异常处理 Filter
使用异常 Filter 后,不必在业务代码中增加繁多的 try-catch
块,统一在全局的异常处理 Filter 内处理异常。
ApiResponse
namespace Model.DTO
{
public class ApiResponse
{
public object? Data { get; set; }
public string? Message { get; set; }
}
}
ExceptionHandlingFilter
using Model.DTO;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Net;
namespace CampusServicePlatform.WebAPI.Filters
{
public class ExceptionHandlingFilter : IAsyncExceptionFilter
{
private readonly ILogger<ExceptionHandlingFilter> _logger;
private readonly IHostEnvironment _hostEnvironment;
public ExceptionHandlingFilter(ILogger<ExceptionHandlingFilter> logger, IHostEnvironment hostEnvironment)
{
_logger = logger;
_hostEnvironment = hostEnvironment;
}
public Task OnExceptionAsync(ExceptionContext context)
{
Exception exception = context.Exception;
_logger.LogError(exception, exception.Message);
ApiResponse response = new ApiResponse();
if (_hostEnvironment.IsDevelopment())
{
response.Message = exception.Message;
}
else
{
response.Message = "服务器异常,请稍后重试";
}
ObjectResult result = new ObjectResult(response);
int statusCode = -1;
switch (exception)
{
case ApplicationException:
{
if (exception.Message.Contains("Invalid token"))
{
statusCode = (int)HttpStatusCode.Forbidden;
break;
}
statusCode = (int)HttpStatusCode.BadRequest;
break;
}
case KeyNotFoundException:
{
statusCode = (int)HttpStatusCode.NotFound;
break;
}
default:
{
statusCode = (int)HttpStatusCode.InternalServerError;
break;
}
}
result.StatusCode = statusCode;
context.Result = result;
context.ExceptionHandled = true;
return Task.CompletedTask;
}
}
}
最终一致性事务 Filter
可以使用操作 Filter 实现事务的最终一致性提交,最常应用在一个业务方法内,其中一个表的数据可以正常提交,另一个与之关联的表因为某些原因,没有按照预期提交两个相关联的表到数据库的情况,例如:
_context.Users.Add(new User());
throw new Exception();
_context.UserInfos.Add(new UserInfo());
TransactionalAttribute
一般来说,不是所有 API 方法都需要用到数据库操作,这里定义一个 Attribute
来标记需要使用到事务的 API 方法。
namespace Model.Attributes
{
[AttributeUsage(AttributeTargets.Method)]
public class TransactionalAttribute : Attribute
{
}
}
TransactionScopeFilter
using Model.Attributes;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Transactions;
namespace WebAPI.Filters
{
public class TransactionScopeFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
bool hasTransactionalAttribute = false;
if (context.ActionDescriptor is ControllerActionDescriptor)
{
var actionDescriptor = (ControllerActionDescriptor)context.ActionDescriptor;
hasTransactionalAttribute = actionDescriptor.MethodInfo.IsDefined(typeof(TransactionalAttribute), false);
}
if (!hasTransactionalAttribute)
{
await next();
return;
}
using (TransactionScope transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
var actionExecutedContext = await next();
if(actionExecutedContext.Exception != null)
{
return;
}
transaction.Complete();
}
}
}
}
注册 Filter
需要注意的是,Filter 的注册顺序是会影响运行结果的。
例如,在操作 Filter 中,API 方法执行前,会先进入注册在前的 Filter;API 方法执行后,会先进入注册在后的 Filter。
var builder = WebApplication.CreateBuilder(args);
// ...
builder.Services.AddControllers(options =>
{
options.Filters.Add<TransactionScopeFilter>();
options.Filters.Add<ExceptionHandlingFilter>();
});
// ...
var app = builder.Build();
参考资料
[1] 杨中科. ASP.NET Core技术内幕与项目实战:基于DDD与前后端分离[M]. 北京: 人民邮电出版社, 2022.
[2] 杨中科. .NET 6教程,.Net Core 2022视频教程,杨中科主讲[Z/OL]. https://www.bilibili.com/video/BV1pK41137He?p=134. 2020.
[3] 董川民. .NET6中全局异常处理[EB/OL]. https://www.dongchuanmin.com/net/1600.html. 2022.