WebApi 过滤器
Authorization Filters //权限验证
Resource Filters //资源验证 常被用于缓存
Model Binding //模型绑定
Action Filters //行为 常被用于模型验证和日志记录
Exception Filters //异常处理 常被用于错误日志
Result Filters //结果验证 用于
三种注册方式
- 方法注册 注册在方法上 Action
- 类注册 注册在控制器上 Controller
- 全局注册 注册在Program
过滤器的注解的定义
public class CtmResourceFilterAttribute : Attribute, IResourceFilter{public void OnResourceExecuted(ResourceExecutedContext context){throw new NotImplementedException();}//控制器请求前,可以有效减少服务器的压力public void OnResourceExecuting(ResourceExecutingContext context){//可以添加短路器throw new NotImplementedException();}}
public class CtmAuthriztionFuilterAttribute : Attribute, IAuthorizationFilter{//在请求完成前public void OnAuthorization(AuthorizationFilterContext context){throw new NotImplementedException();}}
public class CtmActionFilterAttribute : Attribute, IActionFilter{//请求结束后public void OnActionExecuted(ActionExecutedContext context){throw new NotImplementedException();}//请求结束前public void OnActionExecuting(ActionExecutingContext context){throw new NotImplementedException();}}
三种方法权重不一样,执行的依次顺序
progarm->类注册->方法注册
相同类型的FIlter,全局注册的优先级>类上注册优先级>方法上的优先级
不同类型的拦截器互不影响,按照图例顺序
方法注册
namespace FilterDemo.Controllers
{[ApiController][Route("[controller]/[action]")]public class WeatherForecastController : ControllerBase{private readonly ILogger<WeatherForecastController> _logger;public WeatherForecastController(ILogger<WeatherForecastController> logger){_logger = logger;}[HttpGet][CtmActionFilter]//行为拦截[CtmAuthriztionFuilter]//鉴权public string Get(){return "";}}
}
类注册
namespace FilterDemo.Controllers
{[ApiController][Route("[controller]/[action]")][CtmActionFilter]//行为拦截[CtmAuthriztionFuilter]//鉴权[CtmResourceFilter]//资源拦截public class WeatherForecastController : ControllerBase{private readonly ILogger<WeatherForecastController> _logger;public WeatherForecastController(ILogger<WeatherForecastController> logger){_logger = logger;}[HttpGet]public string Get(){return "";}}
}
Progarm注册
builder.Services.AddControllers(o=>o.Filters.Add<CtmActionFilterAttribute>());
builder.Services.AddControllers(o=> { //数字越小,越先执行o.Filters.Add<CtmActionFilterAttribute>(1);o.Filters.Add<CtmActionFilterAttribute>(2);});
过滤器的执行顺序
在方法注册上添加order比重
[HttpGet]
// [CtmActionFilter]//行为拦截
[CtmAuthriztionFuilter]//鉴权
[TypeFilter(typeof(CtmResourceFilterAttribute))]//资源拦截
[TypeFilter(typeof(CtmExceptionFilterAttriubute))]//异常处理拦截
[CtmResultFilter]
//在特性中,第一个是作为构造函数传入的,第二个是给属性传参,要把属性的名称填上
[TypeFilter(typeof(CtmActionFilterAttribute),Order =1)]
public string Get()
{return "";
}
就算是全局定义的filter,如果自行定义的order没有方法注册的比重大,那么,仍然要先执方法注册的
授权过滤器
AuthorizationFilter
过滤器管道中的第一个过滤器。
控制对操作方法的访问。
这是一个简单的模拟,目的是拿取参数中的值
短路器
前置过滤器有AuthorizationFilter和ResouceFilter,这两个过滤器是指在控制器的构造函数执行之前执行的,这两个过滤器执行后,执行构造函数,在执行Model Binding模型绑定和ActionFilter
ResourceFilter
其中Result正常来说为null,如果不等于null,正常来说,应该是短路器被触发了。
短路器被触发后,就不会去触发控制器的构造函数了,会直接通过短路器的内部逻辑返回请求。
public class CtmResourceFilterAttribute : Attribute, IResourceFilter{private static Dictionary<string, object> _dicCache = new Dictionary<string, object>();//控制器处理请求后public void OnResourceExecuted(ResourceExecutedContext context){//获取当前访问的网址PathString path = context.HttpContext.Request.Path;//将返回的数据存在数组中_dicCache[path] = context.Result as ObjectResult;}//控制器请求前,可以有效减少服务器的压力public void OnResourceExecuting(ResourceExecutingContext context){//可以添加短路器,这里就触发短路了//context.Result = new JsonResult("没有权限");//获取当前的访问地址var path = context.HttpContext.Request.Path;//字典中是否包含pathif (_dicCache.ContainsKey(path)){//如果包含,直接返回缓存的数据context.Result = _dicCache[path] as ObjectResult;}}}
ActionFilter
用于 记录日志,模型认证
以下代码讲的是,如何用ActionFilter去记录日志和特性注入
过滤器实现构造函数注入
[HttpPost]
[TypeFilter(typeof(CtmActionFilterAttribute))]
public User AddUser(User user)
{return user;
}
//一个特性,关于方法的拦截public class CtmActionFilterAttribute : Attribute, IActionFilter{private readonly ILogger logger;public CtmActionFilterAttribute(ILogger<CtmActionFilterAttribute> logger){this.logger = logger;}//请求结束后public void OnActionExecuted(ActionExecutedContext context){throw new NotImplementedException();}//请求结束前public void OnActionExecuting(ActionExecutingContext context){logger.LogInformation("....");}}
还有一种注册方式
[HttpPost][ServiceFilter(typeof(CtmActionFilterAttribute))]public User AddUser(User user){return user;}
这种方式需要将filter在Progarm中进行依赖注册
builder.Services.AddTransient<CtmActionFilterAttribute>();
//这里注册的是Transient,瞬态
如果使用了ServiceFilter,那么无论是Filter本身还是filter需要注入的参数,都需要在容器中注入。
builder.Services.AddTransient<User>();
public class CtmActionFilterAttribute : Attribute, IActionFilter
{private readonly ILogger logger;private readonly User user;public CtmActionFilterAttribute(ILogger<CtmActionFilterAttribute> logger,User user){this.logger = logger;this.user = user;}//请求结束后public void OnActionExecuted(ActionExecutedContext context){throw new NotImplementedException();}//请求结束前public void OnActionExecuting(ActionExecutingContext context){PathString path = context.HttpContext.Request.Path;var argumnet = context.ActionArguments;if (argumnet.ContainsKey("User")){var user = argumnet["user"] as User;logger.LogInformation($"{user.Name} is visiting {path}");}else{logger.LogInformation($"{user.Name} is visiting {path}");}}
利用缓存实现短路
缓存使用menory catch
首先要对menory catch进行注册
注意注册时,不能用瞬态,使用单例模式,保持缓存的存在
builder.Services.AddSingleton<IMemoryCache,MemoryCache>();
对控制器使用
[HttpGet]// [CtmActionFilter]//行为拦截[CtmAuthriztionFuilter]//鉴权[TypeFilter(typeof(CtmResourceFilterAttribute))]//资源拦截public string Get(){return "";}
这样才能在ResourceFilterAttribute中拿到相应的缓存
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Caching.Memory;namespace FilterDemo.Filters
{public class CtmResourceFilterAttribute : Attribute, IResourceFilter{private static Dictionary<string, object> _dicCache = new Dictionary<string, object>();private readonly IMemoryCache memoryCache;//控制器处理请求后public CtmResourceFilterAttribute(IMemoryCache memoryCache){this.memoryCache = memoryCache;}public void OnResourceExecuted(ResourceExecutedContext context){/* //获取当前访问的网址PathString path = context.HttpContext.Request.Path;//将返回的数据存在数组中_dicCache[path] = context.Result as ObjectResult;*///获取当前访问的网址PathString path = context.HttpContext.Request.Path;//不再用字典,用缓存进行存储返回的结果//两个参数,一个key一个valuememoryCache.Set(path, context.Result as ObjectResult); }//控制器请求前,可以有效减少服务器的压力public void OnResourceExecuting(ResourceExecutingContext context){//可以添加短路器,这里就触发短路了//context.Result = new JsonResult("没有权限");//获取当前的访问地址var path = context.HttpContext.Request.Path;//字典中是否包含path/* if (_dicCache.ContainsKey(path)){//如果包含,直接返回缓存的数据context.Result = _dicCache[path] as ObjectResult;}*///从缓存中拿到当前path对应的值//有值返回true,没有值返回falseif(memoryCache.TryGetValue(path,out object value)){context.Result = value as ObjectResult;}}}
}
关于out
if(memoryCache.TryGetValue(path,out object value)){context.Result = value as ObjectResult;}
解析版
object value;if(memoryCache.TryGetValue(path,out value)){context.Result = value as ObjectResult;}
给参数赋值后,加上out,可以将返回值,和加上out修饰的值一并带出来。
ExceptionFilters
异常处理拦截
作用是捕获异常的处理
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;namespace FilterDemo.Filters
{public class CtmExceptionFilterAttriubute : Attribute, IExceptionFilter{private readonly ILogger<CtmExceptionFilterAttriubute> logger;public CtmExceptionFilterAttriubute(ILogger<CtmExceptionFilterAttriubute> logger){this.logger = logger;}//在这里可以拿到错误信息public void OnException(ExceptionContext context){logger.LogInformation(context.Exception.Message);context.Result = new ContentResult{Content = context.Exception.Message};}}
}
[HttpGet]// [CtmActionFilter]//行为拦截[CtmAuthriztionFuilter]//鉴权[TypeFilter(typeof(CtmResourceFilterAttribute))]//资源拦截[TypeFilter(typeof(CtmExceptionFilterAttriubute))]//异常处理拦截public string Get(){return "";}
异常过滤器的捕获范围
可以用于常见的错误处理策略
非常适合捕获发生在操作中的异常
建议使用中间件的处理异常
基于所调用的操作方法,仅当错误处理不同时,才使用异常过滤器。
只能捕获跟action相关的异常,只能捕获操作相关的异常
捕获范围很小
ResultFilters
结果过滤器,围绕着操作结果执行。
只有当操作能够生成结果时,才会使用结果过滤器。
在异常过滤器通过生成操作结果时,结果过滤器不会执行。
结果过滤器中的异常不会被异常过滤器捕获
但是如果继承了IAlwaysRunResultFilter接口,那么就可以在异常过滤器捕获异常后仍然可以进入结果过滤器
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;namespace FilterDemo.Filters
{public class CtmAlwaysResultFilter : Attribute, IAlwaysRunResultFilter{public void OnResultExecuted(ResultExecutedContext context){throw new NotImplementedException();}public void OnResultExecuting(ResultExecutingContext context){if (context.Result is StatusCodeResult statusCodeResult&& statusCodeResult.StatusCode == StatusCodes.Status404NotFound){context.Result = new ObjectResult("这是404"){StatusCode = StatusCodes.Status404NotFound};}}}
}