7.5筛选器(过滤器)
筛选器运行开发人员在ASP.NET Core特定的位置执行我们自己的代码,比如在控制器的操作方法之前执行数据检查,或者在ActionResult执行的时候向响应报文头中加入自定义数据。
异常筛选器
系统中出现未处理异常的时候,就会自动执行异常筛选器
- 编写筛选器
usingMicrosoft.AspNetCore.Mvc;
usingMicrosoft.AspNetCore.Mvc.Filters;
//异步异常筛选器要实现IAsyncExceptionFilter接口
publicclassMyExceptionFilter : IAsyncExceptionFilter
{
//由于筛选器中需要把异常写入日志,并且要判断运行环境,所以加入这两个服务
privatereadonlyILogger<MyExceptionFilter>logger;
privatereadonlyIHostEnvironmentenv;
publicMyExceptionFilter(ILogger<MyExceptionFilter>logger, IHostEnvironmentenv)
{
this.logger=logger;
this.env=env;
}
publicTaskOnExceptionAsync(ExceptionContextcontext)
{
Exceptionexception=context.Exception;//获取异常对象
logger.LogError(exception, "UnhandledException occured");//将异常写入日志
stringmessage;
if (env.IsDevelopment())//如果是开发环境
{
message=exception.ToString();
}
else
{
message="程序中出现未处理异常";
}
//响应报文头文件
ObjectResultresult=newObjectResult(new { code=500, message=message });
result.StatusCode=500;
context.Result=result;
//告诉ASP.NET Core不再执行默认的响应逻辑
context.ExceptionHandled=true;
returnTask.CompletedTask;
}
}
- 设置全局筛选器,在Program.cs的builder.Build之前添加
//MvcOptions是ASP.NET Core项目的主要配置对象,这是新的用法
builder.Services.Configure<MvcOptions>(options=>
{//注册全局筛选器,这样ASP.NET Core所有未处理的异常都可以被MyMyExceptionFilte处理
options.Filters.Add<MyExceptionFilter>();
});
操作筛选器
控制器中操作方法执行的时候,操作筛选器就会被执行,通常可以在操作方法执行之前或者之后执行一些代码
操作筛选器要实现IAsyncActionFilter
接口,接口中定义了
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
方法
context:代表Action执行的上下文对象,从context中可以获取请求路径等信息
next:用来指向下一个操作器的委托,一个项目可以有多个操作筛选器,如果当前操作筛选器是最后一个筛选器则next就是要执行的操作方法
- 编写操作筛选器1
usingMicrosoft.AspNetCore.Mvc.Filters;
publicclassMyActionFilter1 : IAsyncActionFilter
{
publicasyncTaskOnActionExecutionAsync(ActionExecutingContextcontext,
ActionExecutionDelegatenext)
{
Console.WriteLine("MyActionFilter 1:开始执行");
//next之前的代码是在操作方法执行之前要执行的代码
ActionExecutedContextr=awaitnext();//执行下一个筛选器,
//如果next出现异常,则ActionExecutedContext.Exception则为异常对像,如果没有异常
//则使用ActionExecutedContext.Result获取
//next之后的代码是在操作方法执行之后要执行的代码
if (r.Exception!=null)
{
Console.WriteLine("MyActionFilter 1:执行失败");
}
else
{
Console.WriteLine("MyActionFilter 1:执行成功");
}
}
}
- 编写操作筛选器2
usingMicrosoft.AspNetCore.Mvc.Filters;
publicclassMyActionFilter2 : IAsyncActionFilter
{
publicasyncTaskOnActionExecutionAsync(ActionExecutingContextcontext,
ActionExecutionDelegatenext)
{
Console.WriteLine("MyActionFilter 2:开始执行");
ActionExecutedContextr=awaitnext();
if (r.Exception!=null)
{
Console.WriteLine("MyActionFilter 2:执行失败");
}
else
{
Console.WriteLine("MyActionFilter 2:执行成功");
}
}
}
- 在Program.cs注册筛选器
builder.Services.Configure<MvcOptions>(options=>
{
options.Filters.Add<MyActionFilter1>();//注册顺序及时执行顺序
options.Filters.Add<MyActionFilter2>();
});
- 在控制器中增加操作方法
[HttpGet]
publicstringGetData()
{
Console.WriteLine("执行GetData");
return"hello";
}
- 结果
案例1 自动启用事务
数据库事务保证了我们对数据的操作要么全部成功,要么全部失败,我们可以使用TransactionScope
来操作数据库事务,将使用EF Core的数据库操作放到TransactionScope
声明的范围内,则这段代码自动支撑事务。
- 我们定义数据库操作都默认使用事务,对于不使用事务的方法,使用NotTransactionAttribute来标记
[AttributeUsage(AttributeTargets.Method)]
publicclassNotTransactionalAttribute : Attribute{}
- 实现筛选器
publicclassTransactionScopeFilter : IAsyncActionFilter
{
publicasyncTaskOnActionExecutionAsync(ActionExecutingContextcontext, ActionExecutionDelegatenext)
{
boolhasNotTransactionalAttribute=false;
//判断是否标注了NotTransactionalAttribute
if (context.ActionDescriptorisControllerActionDescriptor)
{
varactionDesc= (ControllerActionDescriptor)context.ActionDescriptor;
hasNotTransactionalAttribute=actionDesc.MethodInfo
.IsDefined(typeof(NotTransactionalAttribute));
}
//如果不想添加事务则直接使用next执行
if (hasNotTransactionalAttribute)
{
awaitnext();
return;
}
//用using声明作用域,将所有数据库操作都包含进该作用域中
//因为OnActionExecutionAsync是异步,所以要使用TransactionScopeAsyncFlowOption.Enabled
//表示作用域会跨线程
usingvartxScope=
newTransactionScope(TransactionScopeAsyncFlowOption.Enabled);
varresult=awaitnext();
if (result.Exception==null)
{
txScope.Complete();//如果没有异常则提交事务
}
}
}
- 注册到Program.cs中
builder.Services.Configure<MvcOptions>(options=>
{
options.Filters.Add<TransactionScopeFilter>();
});
案例2:请求限流器
如果有客户端频繁发送请求而消耗服务器资源,则可以限制1s内只允许同一个IP的一次请求
usingMicrosoft.AspNetCore.Mvc;
usingMicrosoft.AspNetCore.Mvc.Filters;
usingMicrosoft.Extensions.Caching.Memory;
publicclassRateLimitFilter : IAsyncActionFilter
{
privatereadonlyIMemoryCachememCache;//记录上一次的访问时间
publicRateLimitFilter(IMemoryCachememCache)
{
this.memCache=memCache;
}
publicTaskOnActionExecutionAsync(ActionExecutingContextcontext,
ActionExecutionDelegatenext)
{
//获取用户IP
stringremoveIP=context.HttpContext.Connection.RemoteIpAddress.ToString();
stringcacheKey=$"LastVisitTick_{removeIP}";
long?lastTick=memCache.Get<long?>(cacheKey);//从缓存中获取上次访问的时间
if (lastTick==null||Environment.TickCount64-lastTick>1000)//不存在或者大于1s
{
memCache.Set(cacheKey, Environment.TickCount64,TimeSpan.FromSeconds(10));//设置缓存
returnnext();//执行操作方法
}
else//频繁访问
{//不执行next(),即不再执行操作方法
context.Result=newContentResult { StatusCode=429 } ;
returnTask.CompletedTask;
}
}
}
注册筛选器和内存缓存服务
builder.Services.Configure<MvcOptions>(options=>
{
options.Filters.Add<RateLimitFilter>();
});
builder.Services.AddMemoryCache();