.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";
}