.net6 过滤器、管道模型
1、[中间件](https://learn.microsoft.com/zh-cn/aspnet/core/fundamentals/middleware/?view=aspnetcore-7.0)
-
可以在典型应用中了解现有中间件的顺序,以及在哪里添加自定义中间件。 你可以完全控制如何重新排列现有中间件,或根据场景需要注入新的自定义中间件。
-
ExceptionHandler
异常捕获中间件: 如果接下来的流程中出现了异常, 可以使用他捕获, -
不建议改变
CORS
之前的顺序, 容易出错
-
-
program.cs中
app.Use
的就是中间件
2、Filter过滤器
-
Filter过滤器就是AOP. 面向切面编程
-
AOP 面向切面编程, 就是把无关业务的逻辑, 作为业务前置、业务中、业务后置执行, 比如权限验证、记录日志等操作
-
上述中间件执行完成之后进入执行Filter过滤器
-
在
.net core 6
中有多重过滤器流程 称作管道模型, 使用中间件链接-
AuthorizationFilters
权限验证过滤器 -
ResourceFilters
资源过滤器-
擅长缓存
-
-
Model Binding 模型绑定
-
接口中传入的模型参数, 与模型进行绑定
-
-
ActionFilters
行为过滤器-
擅长模型验证
-
日志记录
-
-
ExceptionFilters
异常过滤器-
线程捕获异常错误
-
-
ResultFilters
结果过滤器-
擅长处理结果
-
-
(0)五大过滤器
过滤器 | 名称 | 功能 | |
---|---|---|---|
AuthorizationFilter |
授权过滤器 | 1 | 权限验证 |
ResourceFilters |
资源管理过滤 | 2 | 缓存 |
ActionFilters |
行为过滤器 | 3 | 模型验证、日志记录 |
ExceptionFilters |
异常过滤器 | 4 | 异常处理 |
ResultFilters |
结果过滤器 | 5 | 结果处理 |
(1)自定义过滤器
-
context
是当前应用程序上下文, 包括如下内容
namespace SecondDemo.Filters { //自定义方法过滤器 -》 这样就获得了特性[CtmActionFilterAttribute] public class CtmActionFilterAttribute : Attribute, IActionFilter { //方法执行后 public void OnActionExecuted(ActionExecutedContext context) { //throw new NotImplementedException(); Console.WriteLine("方法执行后"); } //方法执行中 、 前 public void OnActionExecuting(ActionExecutingContext context) { //throw new NotImplementedException(); Console.WriteLine("方法执行中"); } } }
(2)注册方式
-
方法注册: 注册到方法上边
-
类注册: 注册到控制器上边
-
全局注册: 注册到
Program.cs
上
1.方法注册
-
直接把过滤器注册到方法上边
[HttpGet(Name ="测试自定义方法过滤器")] [CtmActionFilter] public void Get() { Console.WriteLine("大家好 执行了"); }
2.类注册
-
直接把过滤器注册到类上边
-
类下边所有方法, 全部共享
-
相同类型的过滤器, 类注册的要先于方法注册的执行
-
不同类型的过滤器, 只按照管道模型的顺序执行
[CtmClassFilterClass] [ApiController] [Route("/api/[controller]/[action]")] public class TestFilterController:ControllerBase { [HttpGet(Name ="测试自定义方法过滤器")] public void Get() { Console.WriteLine("大家好 执行了"); } }
3.全局注册
-
所有的控制器方法在执行之前都会进入该过滤器
-
相同类型的过滤器, 全局注册的要先于其他两个注册的执行
//全局注册过滤器 builder.Services.AddControllers(opt => { opt.Filters.Add(typeof(CtmActionFilterEnvAttribute)) }); builder.Services.AddControllers(opt => { opt.Filters.Add<CtmActionFilterEnvAttribute>() 544454});
0.注册执行顺序
-
全局注册, 数字越小越靠前执行
builder.Services.AddControllers(opt => { opt.Filters.Add<CtmActionFilterEnv1Attribute>(1)); opt.Filters.Add<CtmActionFilterEnv2Attribute>(2)); }
(4)filter的IOC
用过滤器, 建议使用过滤器构造函数注入, 作为方法特性, 当有属性需要作为构造器参数的时候, 如下例子
public class CtmAuthorizationFilterAttribute : Attribute, IAuthorizationFilter { private IActionsManager ActionsManager; public CtmAuthorizationFilterAttribute(IActionsManager actionsManager) { ActionsManager = actionsManager; } }
-
使用
TypeFilter
过滤器构造器注册, 先去把参数加入IOC容器, 再到相应的方法上注册为方法特性builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer<ContainerBuilder>(builder => { builder.RegisterType<ActionsManager>().As<IActionsManager>(); }); [HttpGet] [TypeFilter(typeof(CtmAuthorizationFilterAttribute))] public string TestAuthorizationActionFilter(int userId, string userName) { //... } -
配合IOC容器注册使用
ServiceFilter
-
使用
ServiceFilter
, 无论是filter本身还是filter需要注入的参数, 都需要在构造IOC中注册
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); builder.Host.ConfigureContainer<ContainerBuilder>(builder => { builder.RegisterType<ActionsManager>().As<IActionsManager>(); builder.RegisterType<CtmActionFilterAttribute>(); }); [HttpGet] [ServiceFilter(typeof(CtmAuthorizationFilterAttribute))] public string TestAuthorizationActionFilter(int userId, string userName) { //... } -
3、AuthorizationFilter
授权过滤器
-
是过滤器管道中第一个过滤器
-
第一个过滤器, 还没有进行到类型绑定阶段
Model Binding
-
无法直接通过
context
上下文获取接口参数
-
-
控制对方法的访问
-
只能拦截执行前的, 不能拦截执行后的
例
namespace SecondDemo.Models { //方法的信息 public class Action { public int Id { get; set; } public string ControllerName { get; set; } public string ActionName { get; set; } /// <summary> /// 返回用户拥有的方法列表 /// </summary> /// <param name="UserId"></param> /// <returns></returns> public static List<Models.Action> GetActionByUserId(int UserId) { if (UserId == 6) { return new List<Action> { new Action { ActionName = "Get", ControllerName = "TestAuthorizationController", Id = 1 } }; } return default; } } }
namespace SecondDemo.Models { //用户的信息 public class User { public int Id { get; set; } public string UserName { get; set; } } }
namespace SecondDemo.Controllers { [ApiController] [Route("/api/[controller]/[action]")] public class TestAuthorizationController : ControllerBase { [HttpGet] [CtmActionFilterClassAttribute] public string Get(int userId, string userName) { return userId.ToString(); } } }
namespace SecondDemo.Filters { /// <summary> /// 鉴定用户权限 /// </summary> public class CmtAuthorizationFilterAttribute : Attribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { //获取请求参数列表 string paramValue = context.HttpContext.Request.QueryString.Value; //获取字典<参数主键, 参数值> Dictionary<string, object> paramDic = paramValue.GetParam(); //判断是否具有如下的ID if (paramDic.TryGetValue(AuthorizationConst.USER_ID, out object value)) { //获取用户ID、用户的权限列表 int userId = Convert.ToInt32(paramDic[AuthorizationConst.USER_ID]); List<Models.Action> actionsList = Models.Action.GetActionByUserId(userId); //获取当前请求的 接口/控制器名 string actionName = context.RouteData.Values["action"].ToString(); string controllerName = context.RouteData.Values["controller"].ToString(); //对比当前接口/控制器名字是否相同 if (!actionsList.Any(action => action.ActionName == actionName && action.ControllerName == controllerName)) { throw new Exception("用户无权限"); } /*var sameAction = from action in actionsList where action.ActionName == actionName where action.ControllerName == controllerName select action; if (sameAction == null) { throw new Exception("用户无权限"); }*/ } throw new Exception("用户没有分配权限"); } } }
namespace SecondDemo.Extensions { public static class AuthorizationParameterExtensions { /// <summary> /// 增强string, 获取请求参数 /// </summary> /// <param name="paramValue"></param> /// <returns></returns> public static Dictionary<string, object> GetParam(this string paramValue) { paramValue.Replace("?", "").Trim(); //获取参数 string[] paramValues = paramValue.Split("%"); Dictionary<string, object> paraDic = new Dictionary<string, object>(); //遍历切割方法请求路径, 获取到请求参数的key和值 foreach (var param in paramValues) { paraDic.Add(param.Split("=")[0].Trim(), param.Split("=")[1].Trim()); } return paraDic; } }
4、ResourceFilter
资源过滤器
(1)短路器
-
请求经过短路器直接返回给前端了
-
不会继续在短路器后继续执行
-
在
ResouceExecutedcontext
中,Result
可以直接作为短路器使用-
只要给这个属性赋值, 直接短路
-
类型是
ActionResult
, 常常给他一个JsonResult()
的值
-
context.Result = new JsonResult();
(2)利用中间件缓存实现短路
-
IOC注入缓存中间件, 以单例模式注入, 选用瞬态则导致缓存失效
//开启autofac builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); //使用autofac进行ioc注入 builder.Host.ConfigureContainer<ContainerBuilder>(builder => { //注入缓存中间件, 以单例模式注入, 选用瞬态则导致缓存失效 builder.RegisterType<MemoryCache>().As<IMemoryCache>().SingleInstance(); });
-
controller
[HttpGet] [TypeFilter(typeof(CtmResourceFilterAttribute))] public string TestResourceFilterAttribute() { //... }
-
Filter
namespace FilterDemo.Filters { public class CtmResourceFilterAttribute : Attribute, IResourceFilter { //缓存 private IMemoryCache memoryCache; public CtmResourceFilterAttribute(IMemoryCache memoryCache) { this.memoryCache = memoryCache; } public void OnResourceExecuted(ResourceExecutedContext context) { //拿到结束上次访问时的地址, 获取上一次访问的Api[Action] string path = context.HttpContext.Request.Path; //以上次访问的地址作为key, 将返回的数据存储到缓存中 memoryCache.Set(CacheConst.REQUEST_PATH_KEY, path); } public void OnResourceExecuting(ResourceExecutingContext context) { //获取当前的访问地址 string path = context.HttpContext.Request.Path; //当前缓存中是否包含当前访问的地址 if (memoryCache.TryGetValue(CacheConst.REQUEST_PATH_KEY, out object pathValue)) { //如果包含, 则使用短路器, 接下来的步骤不执行了, 直接响应回去返回缓存的数据 context.Result = pathValue as ObjectResult; } } } }
-
公众常量
public class CacheConst { public const string REQUEST_PATH_KEY = "request:path"; }
5、ActionFilter
行为过滤器
-
适合搞模型验证, 日志记录
(1)操作日志例子
//开启autofac builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory()); //使用autofac进行ioc注入 builder.Host.ConfigureContainer<ContainerBuilder>(builder => { builder.RegisterType<User>(); }
namespace FilterDemo.Filters { public class CtmActionFilterAttribute : Attribute, IActionFilter { //日志组件 private readonly ILogger<CtmActionFilterAttribute> _logger; //用户 private readonly User user; //构造函数注入日志组件 public CtmActionFilterAttribute(ILogger<CtmActionFilterAttribute> logger, User user) { _logger = logger; this.user = user; } public void OnActionExecuted(ActionExecutedContext context) { throw new NotImplementedException(); } public void OnActionExecuting(ActionExecutingContext context) { //获取请求参数 IDictionary<string, object?> arguments = context.ActionArguments; //获取请求路径 string path = context.HttpContext.Request.Path; if (arguments.ContainsKey("user")) { User user = arguments["user"] as User; _logger.LogInformation($"{user.userName} is visting {path} at {DateTime.Now}"); } else { _logger.LogInformation($"{user.userName} is visting {path} at {DateTime.Now}"); } } } }
namespace FilterDemo.Controllers { [ApiController] [Route("/api/[controller]/[action]")] public class TestController { [HttpGet] //使用过滤器构造器注入 [TypeFilter(typeof(CtmActionFilterAttribute))] //配合IOC容器注册才能使用ServiceF [ServiceFilter(typeof(CtmActionFilterAttribute))] public string TestActionTypeFilter() { return "ok"; } } }
6、ExceptionFilter
异常过滤器
-
捕获错误信息
-
作为接口方法特性, 可以在过滤器中捕获
-
在过滤器中记录错误日志, 并且使用短路器响应回去
例子
public class CtmExceptionFilterAttribute : Attribute, IExceptionFilter { //日志 private readonly ILogger<CtmExceptionFilterAttribute> logger; public CtmExceptionFilterAttribute(Logger<CtmExceptionFilterAttribute> logger) { this.logger = logger; } public void OnException(ExceptionContext context) { //获取异常信息 string exceptionMessage = context.Exception.Message; //记录错误日志 logger.LogError(exceptionMessage); //短路器返回, 错误信息 context.Result = new ContentResult { Content = context.Exception.Message }; } }
[HttpGet] [TypeFilter(typeof(CtmExceptionFilterAttribute))] public string TestExceptionFiltterAttribute() { throw new Exception("test"); return "ok"; }
(1)捕获范围
-
只能捕获到
ActionFilter
执行前, 执行中, 执行后 -
非常适合捕获发生在操作中的异常
-
不像错误处理中间件那样灵活, 建议使用中间件处理异常
7、ResultFilter
和IAsyncResultFilter
结果过滤器
-
仅当操作或操作过滤器生成操作结果时, 才会执行结果过滤器, 不会在以下情况下执行结果过滤器
-
授权*过滤器或资源过滤器使管道短路
-
异常过滤器通过生成操作结果来处理异常
-
-
如果在
IResultFilter.OnResultExecuting
中引发异常, 则会导致-
阻止操作结果和后续过滤器的执行
-
结果被视为失败而不是成功
-
namespace FilterDemo.Filters { public class CtmResultFilterAttribute : Attribute, IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { //... } public void OnResultExecuting(ResultExecutingContext context) { //... } } }
(1)IAlwaysRunResultFilter
和IAsyncAlwaysRunResultFilter
-
这两个接口声明了一个针对所有操作结果运行的
IResultFilter
实现, 不管发生什么异常都会正常运行-
不管资源过滤器、授权过滤器设置短路
-
不挂异常过滤器执行
-
-
应用: 如果在接口执行中出现异常, 控制器会返回状态码, 通过AOP根据不同状态码返回给前端
public class CtmAlwaysRunResultFilterAttribute : Attribute, IAlwaysRunResultFilter { public void OnResultExecuting(ResultExecutingContext context) { //如果在接口执行中出现异常, 控制器会返回状态码 if (context.Result is StatusCodeResult statusCodeResult && statusCodeResult.StatusCode == StatusCodes.Status404NotFound) { //根据不同状态码返回给前端 context.Result = new ObjectResult("找不到资源") { StatusCode = StatusCodes.Status404NotFound }; } } }
8、过滤器执行顺序
(1)未指定执行顺序
过滤器执行顺序如下全局注册 => 类注册 => 方法注册 =>
(2)program.cs
中使用全局注册指定执行顺序
-
数字越小, 执行顺序越靠前
builder.Services.AddControllers(opt => { opt.Filters.Add<CtmActionFilterEnv1Attribute>(1)); opt.Filters.Add<CtmActionFilterEnv2Attribute>(2)); }
(3)Controller
中使用特性的属性指定执行顺序
[TypeFilter(构造器, 参数, 可重复用, 执行顺序)] public class Controller { [TypeFilter(构造器, 参数, 可重复用, 执行顺序)] public string Test() { //... } }
[HttpGet] [TypeFilter(typeof(CtmExceptionFilterAttribute), Order = 1)] public string TestExceptionFiltterAttribute() { throw new Exception("test"); return "ok"; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律