过滤器是指在netcore的请求处理管道中运行指定的代码逻辑。例如指定身份验证,异常处理、盗链验证等等。过滤器其实是实现了AOP,我们可以通过过滤器实现额外的附加操作而不会影响实际的业务逻辑实现。
.netcore中的过滤器主要有以下几种
- Authorization Filter ----授权认证过滤器
- Resource Filter ----资源过滤器
- Action Filter ----Action过滤器
- Exception Filter ----异常过滤器
- Result Filter ----结果过滤器
Filter的执行顺序
各类Filter的实现示例
1、Authorization Filter:一般用于鉴权认证。如果认证不通过则直接返回(不过现在很多情况下都是使用鉴权中心进行验证的(例如Id4实现的用户中心),很少再需要实现Authorization Filter了,当然也有特例,这要根据实际业务而定)。
示例
/// <summary>
/// 自定义鉴权认证过滤器
/// </summary>
public class CustomerAuthorizationFilterAttribute : Attribute,
IAsyncAuthorizationFilter,IOrderedFilter
{
public int Order { get; set; } = 0;
/// <summary>
///
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
var authFlag = false;
var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (descriptor != null)
{
authFlag = descriptor.ControllerTypeInfo.GetCustomAttributes(true).OfType<DescriptionAttribute>().Any()
|| descriptor.MethodInfo.GetCustomAttributes(true).OfType<DescriptionAttribute>().Any();
}
if (authFlag)
{
string userId = context.HttpContext.User.Claims.FirstOrDefault(p => p.Type == "Subject")?.Value;
ISimpleUserService simpleUserService = context.HttpContext.RequestServices.GetService<ISimpleUserService>();
var checkAuthResult = await CheckAuth(userId, simpleUserService);
if (checkAuthResult == false)
{
var responseModel = new ApiResponseModel<object>()
{
Code = "E000999", //自定义错误编码
Message = "NO PERMISSIONS",
};
context.Result = new OkObjectResult(responseModel);
//根据实际业务定义返回结果,可以是OkObjectResult,也可以是其他(例如UnauthorizedObjectResult)
//context.Result = new UnauthorizedObjectResult(responseModel);
return;
}
}
}
/// <summary>
/// 鉴权
/// </summary>
/// <param name="userId"></param>
/// <param name="userService"></param>
/// <returns></returns>
private async Task<bool> CheckAuth(string userId, ISimpleUserService userService)
{
//TODO Check authorization
//var user= await userService.GetUserBydIdAsync(userId);
await Task.Delay(0);
return true;
}
}
2、ResourceFilter:一般用于对资源型信息进行过滤。常用于防盗链/资源缓存(根据判断缓存中是否存在当前资源,存在则直接返回)。
它分为执行前,执行后的实现。
执行前(OnResourceExecuting):在Authorization Filter执行完成后。
执行后(OnResourceExecuted):在Result Execution之后
示例:
1 /// <summary>
2 /// 自定义Resource Filter
3 /// </summary>
4 public class CustomerResourceFilter : Attribute, IResourceFilter
5 {
6 /// <summary>
7 /// 执行后
8 /// </summary>
9 /// <param name="context"></param>
10 public void OnResourceExecuted(ResourceExecutedContext context)
11 {
12 //无13 }
14 /// <summary>
15 /// 执行前
16 /// </summary>
17 /// <param name="context"></param>
18 public void OnResourceExecuting(ResourceExecutingContext context)
19 {
20 #region 防盗链 --特别是一些文件,图片信息时
21 string urlReferrer = context.HttpContext.Request.Headers["Referer"];
22 if (string.IsNullOrWhiteSpace(urlReferrer))//直接访问
23 {
24 context.Result = new ForbidResult();
25 }
26 else if (!urlReferrer.Contains("localhost"))//非当前域名
27 {
28 context.Result = new ForbidResult();
29 }
30 #endregion
31 #region 缓存信息
32 //从缓存读取信息
33 IMemoryCache cache = context.HttpContext.RequestServices.GetService<IMemoryCache>();
34 //请求路径作为缓存的Key
35 string path = context.HttpContext.Request.Path.ToString();
36 object value = null;
37 if (cache.TryGetValue(path, out value))
38 {
39 string result = value.ToString();
40 //如果有Result,则不会往执行后面的过滤器
41 context.Result = new ContentResult() { Content = result };
42 }
43 #endregion
44 }
45 }
3、Action Filter :执行过滤器,一般是在Action执行前后进行过滤。可以通过这个Filter进行一些参数验证逻辑,也可以通过这个filter记录操作日志信息。
它分为执行前,执行后的实现。
执行前(OnActionExecuting):在Action执行之前。
执行后(OnActionExecuted):在Action执行完成之后。
需要注意的是:ActionFilter are not supported in Razor Pages.(Action Filter 不支持Razor Pages)
示例:
/// <summary>
/// 自定义Action Filter
/// </summary>
public class CustomerActionFilter : Attribute, IActionFilter
{
private readonly ILogger _logger;
public CustomerActionFilter(ILogger<CustomerActionFilter> logger)
{
this._logger = logger;
}
/// <summary>
/// 执行后
/// </summary>
/// <param name="context"></param>
public void OnActionExecuted(ActionExecutedContext context)
{
string userId = context.HttpContext.User.Claims.FirstOrDefault(p => p.Type == "Subject")?.Value;
var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (descriptor != null)
{
string message = $"{descriptor.ControllerName}--{descriptor.ActionName} excecuted by {userId} at {DateTime.UtcNow.ToLongDateString()}";
_logger.LogInformation(message);
}
}
/// <summary>
/// 执行前
/// </summary>
/// <param name="context"></param>
public void OnActionExecuting(ActionExecutingContext context)
{
string userId = context.HttpContext.User.Claims.FirstOrDefault(p => p.Type == "Subject")?.Value;
var descriptor = context.ActionDescriptor as ControllerActionDescriptor;
if (descriptor != null)
{
string message = $"{descriptor.ControllerName}--{descriptor.ActionName} excecuting by {userId} at {DateTime.UtcNow.ToLongDateString()}";
_logger.LogInformation(message);
}
}
}
4、Exception Filter :异常过滤器,用于捕获未处理的异常信息。
示例:
/// <summary>
/// global exception filter
/// </summary>
public class GlobalExceptionFilter : Attribute, IAsyncExceptionFilter
{
private readonly ILogger _logger;
/// <summary>
///
/// </summary>
/// <param name="logger"></param>
public GlobalExceptionFilter(ILogger<GlobalExceptionFilter> logger)
{
this._logger = logger;
}
/// <summary>
///
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task OnExceptionAsync(ExceptionContext context)
{
this._logger.LogError($"在响应 {context.HttpContext.Request.Path} 时出现异常,信息:{context.Exception.Message}");
if (!context.ExceptionHandled) //异常未被处理
{
//if (this.IsAjaxRequest(context.HttpContext.Request))//ajax请求
//{
var result= new ApiResponseModel<object>()
{
Code = "E9999999",
Message = "系统发生错误,请联系管理员",
};
context.Result = new BadRequestObjectResult(result);
//中断---请求到这里结束了,不在往下执行Action
//}
//else
//{
// var result = new ViewResult { ViewName = "~/Views/Shared/Error.cshtml" };
// result.ViewData = new ViewDataDictionary(_modelMetadataProvider,
// context.ModelState);
// result.ViewData.Add("Exception", context.Exception.Message);
// context.Result = result;
//}
context.ExceptionHandled = true;//异常已被处理
}
}
private bool IsAjaxRequest(HttpRequest request)
{
string header = request.Headers["X-Requested-With"];
return "XMLHttpRequest".Equals(header);
}
}
5、Result Filter: 结果过滤器,例如可以对结果进行格式化、大小写转换,附件特定返回信息等操作。(格式化其实可以通过在startup中指定,ResultFilter较少使用)
示例
/// <summary>
/// 自定义ResultFilter
/// </summary>
public class CustomerResultFilter : Attribute, IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
var headerName = "NextToken";
context.HttpContext.Response.Headers.Add(
headerName, new string[] { "xxxxxxx" });
//TODO如果访问过于频繁返回Too Many Request
}
public void OnResultExecuted(ResultExecutedContext context)
{
//无
}
}
Filter的注册
1、特性标识
1-1、指定对应的Filter特性(可以是Controller上标注也可以是指定的Action)
例如
[Route("api/[controller]")]
[ApiController]
[CustomerAuthorizationFilter]
public class ProductController : ControllerBase
{
.....
}
1-2、ServiceFilter和TypeFilter
为什么会有ServcieFilter和TypeFilter来指定呢?因为Filter可能存在有参的构造函数(它的参数可能是由netcore的DI框架注入的或者其它的,此时就不能像1-1中通过Filter属性直接标识了,只能通过ServiceFilter或者TypeFilter进行标注)。它们之间的区别主要是:
- ServiceFilter检索来自DI过滤器的一个实例。使用ServiceFilter而不注册过滤器类型会导致异常(即必须在startup中添加对应Filter的服务)。
- TypeFilterAttribute非常类似于ServiceFilterAttribute(和还实现IFilterFactory),但其类型不从DI容器直接解决。它使用的是Microsoft.Extensions.DependencyInjection.ObjectFactory实例化类型(对应的Filter无需注册到DI中)。
2、全局注册
在startup--ConfigureServices(IServiceCollection services)中注册
1 services.AddMvc(options =>
2 {
3 options.Filters.Add<CustomerActionFilter>();
4 options.Filters.Add<CustomerAuthorizationFilterAttribute>();
5 });