Mbp應用服務層的AOP實現
實現方法:asp.net core mvc 篩選器 + 中間件
日志,事務,和接口返回結果統一格式化采用操作篩選器,而異常處理采用中間件來處理.
最開始,我是打算用autofac的高級特性的攔截器來做AOP的,但是遇到一個問題,poco controller沒辦法注入到autofac的容器里面.導致攔截器不能正常工作,所以就采用了篩選器來做.這里的場景有以下幾個:
- AOP是在應用層,而應用層是用poco controller做的,而在asp.net core web api的基架中,controller處在管線的末端.所以可以在這層進行全局攔截.
- 中間件處理異常更加靈活,可以有更多的定制化需求,而異常篩選器是響應抓捕的異常來處理,它相當於是異常發生后的一個處理程序,而中間件可以來實現類似異常篩選器.
- 中間件不短路,只是在程序運行的管線上添加了try catch,同時也解決了異步方法出現的異常無法被捕獲的問題.
實現代碼
- Mbp.AspNetCore.Filter 添加MbpLogFilter,MbpTransActionFilter,ResponseMiddleware三個操作篩選器
- Mbp.AspNetCore.Middleware 添加MbpGlobaExceptionMiddleware中間件
- 在模塊MbpAspNetCoreModule中注冊篩選器和中間件
public override IServiceCollection AddServices(IServiceCollection services)
{
services.AddMvc().AddNewtonsoftJson(options =>
{
// 忽略循環引用
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
// 不使用駝峰
options.SerializerSettings.ContractResolver = new DefaultContractResolver();
// 設置時間格式
options.SerializerSettings.DateFormatString = "yyyy-MM-dd HH:mm:ss";
}).AddMvcOptions(options =>
{
// 禁用Version的綁定
options.ModelMetadataDetailsProviders.Add(new ExcludeBindingMetadataProvider(typeof(System.Version)));
// 統一事務處理中間件
options.Filters.Add(typeof(MbpTransActionFilter));
// 統一日志處理中間件
options.Filters.Add(typeof(MbpLogFilter));
// 請求響應統一格式處理中間件
options.Filters.Add(typeof(ResponseMiddleware));
}); ;
AddAutoWebApi(services, new AutoWebApiOptions());
// 創建Cors策略
services.AddCors(options =>
{
options.AddPolicy("MbpCors",
builder =>
{
builder.WithOrigins(services.BuildServiceProvider().GetService<IConfiguration>().GetSection("AllowedHosts").Value)
.AllowAnyMethod()
.AllowAnyHeader(); ;
});
});
return base.AddServices(services);
}
public override void UseModule(IApplicationBuilder app)
{
// 啟用跨域請求中間件
app.UseCors("MbpCors");
// 啟用應用服務層全局錯誤處理中間件
app.UseMiddleware(typeof(MbpGlobaExceptionMiddleware));
base.UseModule(app);
}
異常中間件對並發沖突進行單獨處理
public async Task InvokeAsync(HttpContext context, ILogger<MbpGlobaExceptionMiddleware> logger)
{
try
{
// Call the next delegate/middleware in the pipeline
await _next(context);
}
catch (DbUpdateConcurrencyException ex)
{
// 發生沖突時候,犧牲后者.不做具體數據合並操作.提示當前用戶數據已經發生修改,需要重試.
logger.LogError("並發沖突:" + ex.Message);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(new { Code = 500, Message = "提交並發沖突", Version = "1", Data = new List<object>() }));
}
catch (Exception ex)
{
// 其他異常
logger.LogError($"請求[{context.Request.Path}]發生異常:" + ex.Message);
context.Response.ContentType = "application/json";
await context.Response.WriteAsync(JsonConvert.SerializeObject(new { Code = 500, Message = "服務器異常", Version = "1", Data = new List<object>() }));
}
}