Asp.Net Core MVC中的过滤器
起因
想起原先学习Asp.Net MVC的过滤器(Filter,也有叫筛选器).1. 过滤器运行的原理
前面一直在说中间件,Filter是在MVC中之后才执行的.具体我们看官方文档给的图.
2. 过滤器的分类
- AuthorizationFilter OnAuthorization
- ResourceFilter OnResourceExecuting
- Controller(控制器)初始化,构造函数调用
- ActionFilter OnActionExecuting
- 进入到Action
- ActionFilter OnActionExecuted
- ResultFilter OnResultExecuting
- 进入View(视图)
- ResultFilter OnResultExecuted
- ResourceFilter ResourceFilter
3. ExceptionFilter 过滤器的使用
public IActionResult Index()
{
int i = 1;
int j = 3;
int m = i + j;
int l = m - m; //4-4 = 0
int k = m / l; //4 / 0 //会有异常
Console.WriteLine("action:/home/index");
return View();
}
先实现异常处理过滤器:
/// <summary>
/// 实现异常处理过滤
/// 1. 要实现ExceptionFilterAttribute
/// </summary>
public class CustomExceptionFilterAttribute : ExceptionFilterAttribute
{
private ILogger<CustomExceptionFilterAttribute> _logger;
public CustomExceptionFilterAttribute(ILogger<CustomExceptionFilterAttribute> logger)
{
_logger = logger;
}
/// <summary>
/// 2. 重写OnException
/// </summary>
/// <param name="context"></param>
public override void OnException(ExceptionContext context)
{
//3. 异常处理
//!context.ExceptionHandled 表示异常没有处理
if (!context.ExceptionHandled)
{
//处理异常
Console.WriteLine($"info:{context.Exception.Message}");
_logger.LogError($" {context.HttpContext.Request.Path} {context.Exception.Message}");
//给一个返回结果,这里返回json.
//在.Net Core 3.0 Json引入新入的组件System.Text.Json 来代替Newtonsoft.Json ,下面的返回中文的会进行Unicode
//所以还是处理一下编码,当然如果使用Newtonsoft.Json,这里可以忽略了,在Asp.Net Core中的需要在Startup.cs全局处理,下面要说过滤器的全局注册.这里就先不说了
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 =>
{
//这里处理System.Text.Json 中文编码问题
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
});
重新编译项目,我们通过浏览器访问一下项目,看看有能不能处理异常.
在浏览器中看到结果,是我们在异常处理过滤器指定的返回值.符合我们预期的效果.然后我们查看一下控制台输出的内容.
带着好奇心,我们看看ExceptionFilterAttribute内部是有什么?
/// <summary>
/// An abstract filter that runs asynchronously after an action has thrown an <see cref="Exception"/>. Subclasses
/// must override <see cref="OnException"/> or <see cref="OnExceptionAsync"/> but not both.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public abstract class ExceptionFilterAttribute : Attribute, IAsyncExceptionFilter, IExceptionFilter, IOrderedFilter
{
/// <inheritdoc />
public int Order { get; set; }
/// <inheritdoc />
public virtual Task OnExceptionAsync(ExceptionContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
OnException(context);
return Task.CompletedTask;
}
/// <inheritdoc />
public virtual void OnException(ExceptionContext context)
{
}
}
看到上边的源码,我们发现ExceptionFilterAttribute只要重写OnException,在内部处理异常就好了.
我们发现ExceptionFilterAttribute继承Attribute和实现IAsyncExceptionFilter, IExceptionFilter, IOrderedFilter这三个接口,既然看了源码,就顺便看这几个接口的源码:
/// <summary>
/// Marker interface for filters handled in the MVC request pipeline.
/// </summary>
public interface IFilterMetadata
{
}
/// <summary>
/// A filter that runs asynchronously after an action has thrown an <see cref="System.Exception"/>.
/// </summary>
public interface IAsyncExceptionFilter : IFilterMetadata
{
/// <summary>
/// Called after an action has thrown an <see cref="System.Exception"/>.
/// </summary>
/// <param name="context">The <see cref="ExceptionContext"/>.</param>
/// <returns>A <see cref="Task"/> that on completion indicates the filter has executed.</returns>
Task OnExceptionAsync(ExceptionContext context);
}
/// <summary>
/// A filter that runs after an action has thrown an <see cref="System.Exception"/>.
/// </summary>
public interface IExceptionFilter : IFilterMetadata
{
/// <summary>
/// Called after an action has thrown an <see cref="System.Exception"/>.
/// </summary>
/// <param name="context">The <see cref="ExceptionContext"/>.</param>
void OnException(ExceptionContext context);
}
/// <summary>
/// A filter that specifies the relative order it should run.
/// </summary>
public interface IOrderedFilter : IFilterMetadata
{
/// <summary>
/// Gets the order value for determining the order of execution of filters. Filters execute in
/// ascending numeric value of the <see cref="Order"/> property.
/// </summary>
/// <remarks>
/// <para>
/// Filters are executed in an ordering determined by an ascending sort of the <see cref="Order"/> property.
/// </para>
/// <para>
/// Asynchronous filters, such as <see cref="IAsyncActionFilter"/>, surround the execution of subsequent
/// filters of the same filter kind. An asynchronous filter with a lower numeric <see cref="Order"/>
/// value will have its filter method, such as <see cref="IAsyncActionFilter.OnActionExecutionAsync"/>,
/// executed before that of a filter with a higher value of <see cref="Order"/>.
/// </para>
/// <para>
/// Synchronous filters, such as <see cref="IActionFilter"/>, have a before-method, such as
/// <see cref="IActionFilter.OnActionExecuting"/>, and an after-method, such as
/// <see cref="IActionFilter.OnActionExecuted"/>. A synchronous filter with a lower numeric <see cref="Order"/>
/// value will have its before-method executed before that of a filter with a higher value of
/// <see cref="Order"/>. During the after-stage of the filter, a synchronous filter with a lower
/// numeric <see cref="Order"/> value will have its after-method executed after that of a filter with a higher
/// value of <see cref="Order"/>.
/// </para>
/// <para>
/// If two filters have the same numeric value of <see cref="Order"/>, then their relative execution order
/// is determined by the filter scope.
/// </para>
/// </remarks>
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}");
//给一个返回结果,这里返回json.
//在.Net Core 3.0 Json引入新入的组件System.Text.Json 来代替Newtonsoft.Json ,下面的返回中文的会进行Unicode
//所以还是处理一下编码,当然如果使用Newtonsoft.Json,这里可以忽略了,在Asp.Net Core中的需要在Startup.cs全局处理,下面要说过滤器的全局注册.这里就先不说了
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] // 这里使用过滤器要实现 Attribute
public class HomeController : Controller
{
// 省略一些代码
}
最后一种作用域范围最小的
[CustomActionFilter] //在Action上使用
public IActionResult Index()
{
int i = 1;
int j = 3;
int m = i + j;
int l = m - m; //4-4 = 0
int k = m / l; //4 / 0 //会有异常
Console.WriteLine("action:/home/index");
return View();
}
5. 需要那种过滤器,就实现对应的接口