ASP.NET Core 异常Filter
目录
什么是Filter?
Exception Filter
实现
注意
ActionFilter
注意
案例:自动启用事务的筛选器
事务的使用
TransactionScopeFilter的使用
什么是Filter?
- 切面编程机制,在ASP.NET Core特定的位置执行我们自定义的代码。
- ASP.NET Core中的Filter的五种类型:Authorization filter、Resource filter、Action filter、Exception filter、Result filter。本书中重点讲解Exception filter和Action filter。
- 所有筛选器一般有同步和异步两个版本,比如IActionFilter、IAsyncActionFilter接口。
Exception Filter
当系统中出现未经处理的异常的时候,异常筛选器就会执行。
实现
- 当系统中出现未处理异常的时候,我们需要统一给客户端返回如下格式的响应报文:{“code”:”500”,”message”:”异常信息”}。
对于开发环境中message是异常堆栈,对于其他环境message用一个general的报错信息。 - 实现IAsyncExceptionFilter接口。注入IHostEnvironment得知运行环境。
public class LogExceptionFilter : IAsyncExceptionFilter
{
public Task OnExceptionAsync(ExceptionContext context)
{
return File.AppendAllTextAsync("F:/error.log", context.Exception.ToString());
}
}
public class MyExceptionFilter : IAsyncExceptionFilter
{
private readonly IWebHostEnvironment hostEnv;
public MyExceptionFilter(IWebHostEnvironment hostEnv)
{
this.hostEnv = hostEnv;
}
public Task OnExceptionAsync(ExceptionContext context)
{
//context.Exception代表异常信息对象
//context.ExceptionHandled为true时,表示异常已经被处理,其他ExceptionFilter将不会再处理
//context.Result的值会返回给客户端
string msg;
if (hostEnv.IsDevelopment())
{
msg = context.Exception.Message;
}
else
{
msg = "服务器发生了未处理异常";
}
ObjectResult objresult = new ObjectResult(new { code = 500, message = msg });
context.Result = objresult;
context.ExceptionHandled = true;
return Task.CompletedTask;
}
}
注意
ExceptionFilter执行顺序与注册顺序有关,后注册的先执行
builder.Services.Configure<MvcOptions>(opt =>
{
opt.Filters.Add<MyExceptionFilter>();
opt.Filters.Add<LogExceptionFilter>();
});
ActionFilter
IAsyncActionFilter接口
多个Action Filter的链式执行。
注意
ActionFilter执行顺序与注册顺序有关,先注册的先执行
public class MyActionFilter1 : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
Console.WriteLine("MyActionFilter1前代码");
ActionExecutedContext result = await next();
if (result.Exception != null)
{
Console.WriteLine("MyActionFilter1捕获到异常");
}
else
{
Console.WriteLine("MyActionFilter1后代码");
}
}
}
builder.Services.Configure<MvcOptions>(opt =>
{
opt.Filters.Add<MyActionFilter1>();
opt.Filters.Add<MyActionFilter2>();
});
案例:自动启用事务的筛选器
- 数据库事务:要么全部成功、要么全部失败。
- 自动化:启动、提交以及回滚事务。
- 当一段使用EF Core进行数据库操作的代码放到TransactionScope声明的范围中的时候,这段代码就会自动被标记为“支持事务”。
- TransactionScope实现了IDisposable接口,如果一个TransactionScope的对象没有调用Complete()就执行了Dispose()方法,则事务会被回滚,否则事务就会被提交。
- TransactionScope还支持嵌套式事务。
- .NET Core中的TransactionScope不像.NET FX一样有MSDTC分布式事务提升的问题。请使用最终一致性事务。
事务的使用
[HttpPost]
public async Task<string> Test2()
{
using (TransactionScope tx = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
dbctx.Books.Add(new Book { Title = "计算机网络", Price = 12.6 });
await dbctx.SaveChangesAsync();
dbctx.People.Add(new Person { Name = "张三", Age = 20 });
await dbctx.SaveChangesAsync();
tx.Complete();
return "OK";
}
}
[HttpPost]
public string Test1()
{
using (TransactionScope tx = new TransactionScope())
{
dbctx.Books.Add(new Book { Title = "计算机网络", Price = 12.6 });
dbctx.SaveChangesAsync();
dbctx.People.Add(new Person { Name = "张三", Age = 20 });
dbctx.SaveChangesAsync();
tx.Complete();
return "OK";
}
}
TransactionScopeFilter的使用
对于强制不进行事务控制的Action方法,请标注NotTransactionalAttribute。
开发筛选器TransactionScopeFilter;把TransactionScopeFilter注册到Program.cs中。
NotTransationAttribute.cs:
[AttributeUsage(AttributeTargets.Method)]
public class NotTransationAttribute:Attribute
{
}
TransactionScopeFilter.cs:
public class TransactionScopeFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
//context.ActionDescriptor中是当前被执行的Action的描述信息
//context.ActionArguments中是当前被执行的Action的参数信息
ControllerActionDescriptor controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
//if (controllerActionDescriptor == null)//不是一个MVC的Action
bool isTX = false;//是否进行事务控制
if (controllerActionDescriptor != null)
{
//获取当前Action是否有NotTransationAttribute特性
bool hasNotTransationAttribute = controllerActionDescriptor.MethodInfo.GetCustomAttributes(typeof(NotTransationAttribute), false).Any();
//如果没有NotTransationAttribute特性,则进行事务控制
isTX = !hasNotTransationAttribute;
}
if (isTX)
{
//创建一个异步的事务范围
using (TransactionScope tx=new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
var r= await next();
if (r.Exception==null)
{
tx.Complete();
}
}
}
else
{
await next();
}
}
}
Program.cs:
builder.Services.Configure<MvcOptions>(opt =>
{
opt.Filters.Add<TransactionScopeFilter>();
});
案例:开发请求限流器
需求
- Action Filter可以在满足条件的时候终止操作方法的执行。
- 在Action Filter中,如果我们不调用await next(),就可以终止Action方法的执行了。
- 为了避免恶意客户端频繁发送大量请求消耗服务器资源,我们要实现“一秒钟内只允许最多有一个来自同一个IP地址的请求”。
public class ratelimitActionFilter : IAsyncActionFilter
{
private readonly IMemoryCache memcache;
public ratelimitActionFilter(IMemoryCache memcache)
{
this.memcache = memcache;
}
public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
//从HTTP请求的上下文中获取客户端的远程IP地址
string removeIP = context.HttpContext.Connection.RemoteIpAddress.ToString();
//context.ActionDescriptor中是当前被执行的Action的描述信息,转换为ControllerActionDescriptor类型
ControllerActionDescriptor controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
//构建缓存的Key
string cacheKey = $"LastVisitTick_{controllerActionDescriptor.ControllerName}_{removeIP}";
//从缓存中获取上次访问的时间戳
long? lastTick = memcache.Get<long?>(cacheKey);
//如果上次访问的时间戳不存在或者距离当前时间已经超过1秒
if (lastTick == null || Environment.TickCount64 - lastTick > 1000)
{
//设置当前的时间戳到缓存中
memcache.Set(cacheKey, Environment.TickCount64, TimeSpan.FromSeconds(10));//避免长期不访问的用户一直占用缓存
return next();
}
else
{
context.Result = new ContentResult
{
StatusCode = 429,
Content = "访问太频繁,请稍后再试!"
};
return Task.CompletedTask;
}
}
}