释义

过滤器是Asp.NetCore框架提供的一种面向切面编程的机制,它允许我们在管道处理过程中在特定位置执行自定义的代码,极大程度的提高了框架的可扩展性。

分类

Asp.NetCore框架中过滤器可以分为一下5种类型:

  • 授权过滤器
  • 资源过滤器
  • 操作过滤器
  • 异常过滤器
  • 结果过滤器

由于过滤器的作用域会对所有管道生效,所以对授权、资源、结果的过滤特定操一般通过编写专门的策略来实现以达到更精细的控制,所以比较常用的过滤器主要是异常过滤器和操作过滤器。

异常过滤器

示例场景:通过异常过滤器,可以拦截Asp.NetCore应用运行过程中程序抛出的所有异常,并且统一处理异常。

使用方法:过滤器分为同步和异步过滤器,一般情况下,同步过滤器的执行效率会更高效,首先我们编写一个自定义的一场过滤器

/// <summary>
/// 自定义异常过滤器
/// </summary>
public class MyExceptionFilter:IAsyncExceptionFilter
{

    /// <summary>
    /// 实现异常拦截处理方法
    /// </summary>
    /// <param name="context">拦截到的异常上下文</param>
    /// <returns></returns>
    public Task OnExceptionAsync(ExceptionContext context)
    {
        //获取异常
        Exception exception = context.Exception;
        //异常处理,返回一个IResult对象
        context.Result = new ObjectResult(new {Code = 500, Message = exception.Message});
        //将异常处理标志设置为true
        context.ExceptionHandled = true;
        //完成异步任务
        return Task.CompletedTask;
    }

所有异步过滤器都通过继承IAsyncXxxxFilter类来实现(Xxx为过滤器名称),异常过滤器继承IAsyncExceptionFilter接口并实现其接口即可,上述程序我们把拦截到的异常封装为一个Objeck对象并返回给客户端,这样既提升了客户端的使用体验,也屏蔽了异常抛出的一堆无用报错(针对用户来说是无用的,且可能会被恶意利用),我们可以把具体的报错信息可以把它写入数据库或文件,由开发人员阅读即可。

编写好自定义异常过滤器后,通过配置Asp.NetCore框架的MvcOptions对象来将该过滤器注入到框架之中。

//Configure<>()方法用于配置框架,其实质也是注册服务,不过服务的消费由框架自己决定
builder.Services.Configure<MvcOptions>(options =>
{
    options.Filters.Add<MyExceptionFilter>();
});

在控制器中抛出异常:

[HttpGet]
   public object Filtertest()
   {
      throw new Exception("异常过滤器测试。");
   }

测试效果

可以看到客户端正常接收到后端响应,状态码为200,响应信息是我们自定义的对象信息。

操作过滤器

示例场景:操作过滤器会在控制器中每个方法执行之前,执行我们自定义的过滤操作,可以利用这一特点实现一个限流过滤器。

使用方法:先来看一下限流过滤器的实现

/// <summary>
/// 限流过滤器
/// </summary>
public class RateLimitFilter : IAsyncActionFilter
{
    private readonly IMemoryCache _memoryCache;

    /// <summary>
    /// 在构造方法中注入内存缓存
    /// </summary>
    /// <param name="memoryCache"></param>
    public RateLimitFilter(IMemoryCache memoryCache)
    {
        _memoryCache = memoryCache;
    }

    /// <summary>
    /// 实现操作过滤器接口
    /// </summary>
    /// <param name="context"></param>
    /// <param name="next"></param>
    /// <returns></returns>
    public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        //读取https上下文中的客户端请求ip
        string removeIp = context.HttpContext.Connection.RemoteIpAddress.ToString();
        //生成内存缓存key
        string cacheKey = $"LastVisit_{removeIp}";
        //根据生成的key读取一个时间戳
        long? lastTick = _memoryCache.Get<long?>(cacheKey);
        //如果没读到或者时间戳与当前时间的差值大于1000ms,说明该ip的请求频率大于1s
        if (lastTick == null || Environment.TickCount64 - lastTick > 1000)
        {
            //将当前时间与ip信息存入缓存中,并设顶10s过期
            _memoryCache.Set(cacheKey, Environment.TickCount64, TimeSpan.FromSeconds(10));
            //执行next委托
            return next();
        }
        else
        {
            //如果读到值且时间戳与当前时间的差值小于1000ms,说明请求频率小于1s,直接返回一个ContentResult
            context.Result = new ContentResult() {Content = "请求过于频繁,请稍后再试!", StatusCode = 429};
            //完成异步任务
            return Task.CompletedTask;
        }
    }
}

可以看到操作过滤器接口接收两个参数,一个是到操作执行上下文,一个是操作执行委托,操作执行上下文中含有客户端请求相关信息,即HttpContext。而操作执行委托next()参数决定了程序是否往下执行,如果我们不执行next(),程序不往下执行,那么对应的控制器中的方法也就不执行了,达到了过滤的效果。

配置过滤器

//Configure<>()方法用于配置框架,其实质也是注册服务,不过服务的消费由框架自己决定
builder.Services.Configure<MvcOptions>(options =>
{
    options.Filters.Add<MyExceptionFilter>();
    options.Filters.Add<RateLimitFilter>();
});

...
    
[HttpGet]
   public object Filtertest()
   {
      return DateTime.Now.ToString();
   }

看下效果

可以看到正常访问FilterTest会返回当前时间,快速点击Execute按钮时则会返回我们自定义的请求过于频繁信息。