7.5筛选器(过滤器)

筛选器运行开发人员在ASP.NET Core特定的位置执行我们自己的代码,比如在控制器的操作方法之前执行数据检查,或者在ActionResult执行的时候向响应报文头中加入自定义数据。

异常筛选器

系统中出现未处理异常的时候,就会自动执行异常筛选器

  1. 编写筛选器
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
//异步异常筛选器要实现IAsyncExceptionFilter接口
public class MyExceptionFilter : IAsyncExceptionFilter
{
//由于筛选器中需要把异常写入日志,并且要判断运行环境,所以加入这两个服务
private readonly ILogger<MyExceptionFilter> logger;
private readonly IHostEnvironment env;
public MyExceptionFilter(ILogger<MyExceptionFilter> logger, IHostEnvironment env)
{
this.logger = logger;
this.env = env;
}
public Task OnExceptionAsync(ExceptionContext context)
{
Exception exception = context.Exception;//获取异常对象
logger.LogError(exception, "UnhandledException occured");//将异常写入日志
string message;
if (env.IsDevelopment())//如果是开发环境
{
message = exception.ToString();
}
else
{
message = "程序中出现未处理异常";
}
//响应报文头文件
ObjectResult result = new ObjectResult(new { code = 500, message = message });
result.StatusCode = 500;
context.Result = result;
//告诉ASP.NET Core不再执行默认的响应逻辑
context.ExceptionHandled = true;
return Task.CompletedTask;
}
}
  1. 设置全局筛选器,在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. 编写操作筛选器1
using Microsoft.AspNetCore.Mvc.Filters;

public class MyActionFilter1 : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
Console.WriteLine("MyActionFilter 1:开始执行");
//next之前的代码是在操作方法执行之前要执行的代码
ActionExecutedContext r = await next();//执行下一个筛选器,
//如果next出现异常,则ActionExecutedContext.Exception则为异常对像,如果没有异常
//则使用ActionExecutedContext.Result获取
//next之后的代码是在操作方法执行之后要执行的代码
if (r.Exception != null)
{
Console.WriteLine("MyActionFilter 1:执行失败");
}
else
{
Console.WriteLine("MyActionFilter 1:执行成功");
}
}
}
  1. 编写操作筛选器2
using Microsoft.AspNetCore.Mvc.Filters;

public class MyActionFilter2 : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
Console.WriteLine("MyActionFilter 2:开始执行");
ActionExecutedContext r = await next();
if (r.Exception != null)
{
Console.WriteLine("MyActionFilter 2:执行失败");
}
else
{
Console.WriteLine("MyActionFilter 2:执行成功");
}
}
}
  1. 在Program.cs注册筛选器
builder.Services.Configure<MvcOptions>(options =>
{
options.Filters.Add<MyActionFilter1>();//注册顺序及时执行顺序
options.Filters.Add<MyActionFilter2>();
});
  1. 在控制器中增加操作方法
[HttpGet]
public string GetData()
{
Console.WriteLine("执行GetData");
return "hello";
}
  1. 结果

7.5筛选器(过滤器)_服务器

案例1 自动启用事务

数据库事务保证了我们对数据的操作要么全部成功,要么全部失败,我们可以使用​​TransactionScope​​​来操作数据库事务,将使用EF Core的数据库操作放到​​TransactionScope​​声明的范围内,则这段代码自动支撑事务。

  1. 我们定义数据库操作都默认使用事务,对于不使用事务的方法,使用NotTransactionAttribute来标记
[AttributeUsage(AttributeTargets.Method)]
public class NotTransactionalAttribute : Attribute{}
  1. 实现筛选器
public class TransactionScopeFilter : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
bool hasNotTransactionalAttribute = false;
//判断是否标注了NotTransactionalAttribute
if (context.ActionDescriptor is ControllerActionDescriptor)
{
var actionDesc = (ControllerActionDescriptor)context.ActionDescriptor;
hasNotTransactionalAttribute = actionDesc.MethodInfo
.IsDefined(typeof(NotTransactionalAttribute));
}
//如果不想添加事务则直接使用next执行
if (hasNotTransactionalAttribute)
{
await next();
return;
}
//用using声明作用域,将所有数据库操作都包含进该作用域中
//因为OnActionExecutionAsync是异步,所以要使用TransactionScopeAsyncFlowOption.Enabled
//表示作用域会跨线程
using var txScope =
new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
var result = await next();
if (result.Exception == null)
{
txScope.Complete();//如果没有异常则提交事务
}
}
}
  1. 注册到Program.cs中
builder.Services.Configure<MvcOptions>(options =>
{
options.Filters.Add<TransactionScopeFilter>();
});

案例2:请求限流器

如果有客户端频繁发送请求而消耗服务器资源,则可以限制1s内只允许同一个IP的一次请求

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;

public class RateLimitFilter : IAsyncActionFilter
{
private readonly IMemoryCache memCache;//记录上一次的访问时间
public RateLimitFilter(IMemoryCache memCache)
{
this.memCache = memCache;
}
public Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
//获取用户IP
string removeIP = context.HttpContext.Connection.RemoteIpAddress.ToString();
string cacheKey = $"LastVisitTick_{removeIP}";
long? lastTick = memCache.Get<long?>(cacheKey);//从缓存中获取上次访问的时间
if (lastTick == null || Environment.TickCount64 - lastTick > 1000)//不存在或者大于1s
{
memCache.Set(cacheKey, Environment.TickCount64,TimeSpan.FromSeconds(10));//设置缓存
return next();//执行操作方法
}
else//频繁访问
{//不执行next(),即不再执行操作方法
context.Result = new ContentResult { StatusCode= 429 } ;
return Task.CompletedTask;
}
}
}

注册筛选器和内存缓存服务

builder.Services.Configure<MvcOptions>(options =>
{
options.Filters.Add<RateLimitFilter>();
});
builder.Services.AddMemoryCache();