起因
想起原先学习Asp.Net MVC的过滤器(Filter,也有叫筛选器).
1. 过滤器运行的原理
前面一直在说中间件,Filter是在MVC中之后才执行的.具体我们看官方文档给的图.
写一个中间件,来验证请求是否先到中间件中还是先到MVC中.
2. 过滤器的分类
看完上图之后,我们先看看过滤器的执行顺序,这里先刨除异常过滤器.因为异常过滤器是在异常之后才会触发的.
根据上图执行的结果.我们在捋捋MVC中的过滤器的执行顺序:
- AuthorizationFilter OnAuthorization
- ResourceFilter OnResourceExecuting
- Controller(控制器)初始化,构造函数调用
- ActionFilter OnActionExecuting
- 进入到Action
- ActionFilter OnActionExecuted
- ResultFilter OnResultExecuting
- 进入View(视图)
- ResultFilter OnResultExecuted
- ResourceFilter ResourceFilter
3. ExceptionFilter 过滤器的使用
后面可以上代码,正式进入过滤器的学习阶段.我们先看ExceptionFilter(异常处理过滤器):
异常处理过滤器,顾名思义,一定要有异常才能处理呀.于是我们在Index Action中要手动造一个异常.
public IActionResult Index()
{
int i = 1;
int j = 3;
int m = i + j;
int l = m - m;
int k = m / l;
Console.WriteLine("action:/home/index");
return View();
}
先实现异常处理过滤器:
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private ILogger<CustomExceptionFilterAttribute> _logger;
public CustomExceptionFilterAttribute(ILogger<CustomExceptionFilterAttribute> logger)
{
_logger = logger;
}
public override void OnException(ExceptionContext context)
{
if (!context.ExceptionHandled)
{
Console.WriteLine($"info:{context.Exception.Message}");
_logger.LogError($" {context.HttpContext.Request.Path} {context.Exception.Message}");
context.Result = new JsonResult(new { Msg = "暂时无法处理", time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") });
context.ExceptionHandled = true;
}
}
}
写完异常处理过滤器,这时候异常过滤器并没有生效.要注册之后,才能生效.先看一下全局注册.
在Startup.cs文件中,在ConfigureServices中AddControllersWithViews,通过MvcOptions.Filters添加全局过滤器.
services.AddControllersWithViews(options =>
{
options.Filters.Add(typeof(CustomAuthorizationFilterAttribute));
options.Filters.Add(typeof(CustomExceptionFilterAttribute));
}).AddJsonOptions(options =>
{
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
重新编译项目,我们通过浏览器访问一下项目,看看有能不能处理异常.
在浏览器中看到结果,是我们在异常处理过滤器指定的返回值.符合我们预期的效果.然后我们查看一下控制台输出的内容.
带着好奇心,我们看看ExceptionFilterAttribute内部是有什么?
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class ExceptionFilterAttribute : Attribute, IAsyncExceptionFilter, IExceptionFilter, IOrderedFilter
{
public int Order { get; set; }
public virtual Task OnExceptionAsync(ExceptionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
OnException(context);
return Task.CompletedTask;
}
public virtual void OnException(ExceptionContext context)
{
}
}
看到上边的源码,我们发现ExceptionFilterAttribute只要重写OnException,在内部处理异常就好了.
我们发现ExceptionFilterAttribute继承Attribute和实现IAsyncExceptionFilter, IExceptionFilter, IOrderedFilter这三个接口,既然看了源码,就顺便看这几个接口的源码:
public interface IFilterMetadata
{
}
public interface IAsyncExceptionFilter : IFilterMetadata
{
Task OnExceptionAsync(ExceptionContext context);
}
public interface IExceptionFilter : IFilterMetadata
{
void OnException(ExceptionContext context);
}
public interface IOrderedFilter : IFilterMetadata
{
int Order { get; }
}
接口源码比较简单,如果不实现ExceptionFilterAttribute,我们自己也很好实现异常过滤器.如果不需要异步处理,只需要实现IExceptionFilter和IOrderedFilter
public class CustomExceptionFilter1 : IExceptionFilter, IOrderedFilter
{
public int Order => 0;
public void OnException(ExceptionContext context)
{
if (!context.ExceptionHandled)
{
Console.WriteLine($"info {typeof(CustomExceptionFilter1)} :: {context.Exception.Message}");
context.Result = new JsonResult(new { Msg = "暂时无法处理", time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff") });
context.ExceptionHandled = true;
}
}
}
options.Filters.Add(typeof(CustomExceptionFilter1));
看看效果如何:
我们在实现IExceptionFilter的同时,也实现IOrderedFilter接口,IOrderedFilter接口内部只有一个Order属性,主要是重写过滤器的默认执行顺序.
4. 过滤器的使用范围
上面在ExceptionFilter的时候,已经使用过全局注册了,过滤器的注册有3种.
- 全局注册,作用域在整个项目中使用
- 在单个控制器上注册,作用域在单个控制器中
- 在Action上注册,作用域在单个Action中
第一种全局注册,在上面有代码.我们看在控制器上的注册.
[CustomResourceFilter]
public class HomeController : Controller
{
}
最后一种作用域范围最小的
[CustomActionFilter] //在Action上使用
public IActionResult Index()
{
int i = 1;
int j = 3;
int m = i + j;
int l = m - m;
int k = m / l;
Console.WriteLine("action:/home/index");
return View();
}
5. 需要那种过滤器,就实现对应的接口
使用那种过滤器就实现对应的接口,是用异步接口还是同步接口,都可以按自己需要去做