.NET5中AOP的5个过滤器
AOP(Aspect Oriented Programming),即面向切面编程。可以在不修改之前的代码为基础,动态增加新功能。.NET5中提供了5种AOP的Filter,分别是
- ActionFilter(方法)
- ResourceFilter(资源)
- ExceptionFilter(异常)
- ResultFilter(结果)
- AuthorizationFilter(鉴权授权)

1. ActionFilter
ActionFilter在方法的执行前后可执行相应操作。
1.1 ActionFilter基本使用
(1) 自定义一个CustomActionFilterAttribute,并且继承Attribute
,实现IActionFilter
的OnActionExecuting
和 OnActionExecuted
方法。OnActionExecuting在方法执行前执行,OnActionExecuted在方法执行后执行。
public class CustomActionFilterAttribute : Attribute, IActionFilter { public void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("执行OnActionExecuting"); } public void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("执行OnActionExecuted"); } }
(2) 在Controller的方法上添加特性标记。
public class HomeController : Controller { private readonly ILogger<HomeController> _logger; public HomeController(ILogger<HomeController> logger) { _logger = logger; } [CustomActionFilter] public IActionResult Index() { Console.WriteLine("执行控制器中的Index"); return View(); } // ... } }
打印的结果为:
执行OnActionExecuting 执行控制器中的Index 执行OnActionExecuted
1.2 ActionFilter的多种使用
除上述来使用ActionFilter,也可通过继承ActionFilterAttribute
(系统提供的实现),根据自己的需要,覆写不同的方法,达到自己的诉求。
public class CustomActionFilterSystemAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { Console.WriteLine("执行OnActionExecuting"); } public override void OnActionExecuted(ActionExecutedContext context) { Console.WriteLine("执行OnActionExecuted"); } }
异步版本的实现,通过实现IAsyncActionFilter
接口来实现。
public class CustomActionFilterAsyncAttribute : Attribute, IAsyncActionFilter { public Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { return Task.Run(() => { Console.WriteLine("执行OnActionExecutionAsync"); }); } }
1.3 ActionFilter的应用
可以用来记录日志,通过log4net将日志写入文件,ILogger依赖注入。当构造函数有了参数后控制器方法上的[CustomActionFilter]
特性就不能这么写了,应该写为[TypeFilter(typeof(CustomActionFilterAttribute))]
.
public class CustomActionFilterAttribute : Attribute, IActionFilter { private ILogger<CustomActionFilterAttribute> _logger = null; public CustomActionFilterAttribute(ILogger<CustomActionFilterAttribute> logger) { _logger = logger; } public void OnActionExecuting(ActionExecutingContext context) { _logger.LogInformation(JsonConvert.SerializeObject(context.HttpContext.Request.Query)); _logger.LogInformation("执行CustomActionFilterAttribute.OnActionExecuting"); } public void OnActionExecuted(ActionExecutedContext context) { _logger.LogInformation(JsonConvert.SerializeObject(context.Result)); _logger.LogInformation("执行CustomActionFilterAttribute.OnActionExecuted"); } }
1.4 ActionFilter的多种注册
(1)[CustomActionFilter]
---Fitler必须有无参数构造函数。
(2)[TypeFilter(typeof(CustomActionFilterAttribute))]
,可以没有无参数构造函数,可以支持依赖注入。
(3)[ServiceFilter(typeof(CustomActionFilterAttribute))]
,可以没有无参数构造函数,可以支持依赖注入,但是必须要注册服务。
1.5 FilterFactory扩展定制
为什么写上TypeFilter
这样的特性就可以支持依赖注入呢?它是由IOC容器来完成的。
(1)现在我们自定义一个特性类CustomFilterFactory,继承Attribute
,实现接口IFilterFactory
,并实现接口中的方法。
public class CustomFilterFactory : Attribute, IFilterFactory { private readonly Type _type = null; public bool IsReusable => true; public CustomFilterFactory(Type type) { _type = type; } public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) { object oInstance = serviceProvider.GetService(_type); return (IFilterMetadata)oInstance; } }
(2)在Startup类的ConfigureServices中注册。
public void ConfigureServices(IServiceCollection services) { services.AddControllersWithViews(); services.AddTransient<CustomActionFilterAttribute>(); }
(3)最后再将 [CustomFilterFactory(typeof(CustomActionFilterAttribute))]
标记到控制器的Action方法上就行了,和[TypeFilter(typeof(CustomActionFilterAttribute))]
效果一样。
1.6 Filter的生效范围
- 标记在Action上,就只对当前Action生效。
- 标记在Controller上,就对Controller上中的所有Action生效。
- 全局注册,对于当前整个项目中的Action都生效,在ConfigureServices中增加以下代码即可
services.AddMvc(option => { option.Filters.Add<CustomActionFilterAttribute>(); //全局注册: });
1.7 Filter的执行顺序
如果定义三个actionFilter,分别注册全局,控制器、Action;执行顺序如何呢?
<1> 控制器实例化 <2> 全局注册的Filter - OnActionExecuting <3> 控制器注册的Filter - OnActionExecuting <4> Actioin注册的Filter - OnActionExecuting <5> 执行Action内部的逻辑 <6> Action注册的Filter - OnActionExecuted <7> 控制器注册的Filter - OnActionExecuted <8> 全局注册的Filter - OnActionExecuted
如果想要改变执行顺序,需要在注册Filter的时候,指定Order
的值,执行顺序会按照值从小到大执行。例如在方法上添加[CustomActionActionFilterAtrribute(Order = -1)]
,不添加则Order值为0。
1.8 Filter匿名
如果全局注册,Filter生效于所有的Acion,如果有部分Action我希望不生效怎么办呢?这就需要匿名,可以避开Filter的检查。
下面自定义Filter匿名。
(1)自定义一个特性类CustomAllowAnonymousAttributet
,将[CustomAllowAnonymous]
特性添加到需要避开的方法上。
(2)在需要匿名的Filter内部,检查是否需要匿名(检查是否标记的有匿名特性),如果有就直接避开。
public void OnActionExecuting(ActionExecutingContext context) { if(context.ActionDescriptor.EndpointMetadata.Any(item=>item.GetType() == typeof(CustomAllowAnonymousAttribute))) { return; } // ...... }
2. ResourceFilter
ResourceFilter
就是为了缓存而存在的。
public class CustomResourceFilterAttribute : Attribute, IResourceFilter { private static Dictionary<string, object> CacheDictionary = new Dictionary<string, object>(); public void OnResourceExecuting(ResourceExecutingContext context) { //在这里就判断是否有缓存,只要是key 不变,缓存就不变 string key = context.HttpContext.Request.Path; if (CacheDictionary.Any(item => item.Key == key)) { //断路器,只要是对Result赋值,就不继续往后走了; context.Result = CacheDictionary[key] as IActionResult; } Console.WriteLine("执行OnResourceExecuting"); } public void OnResourceExecuted(ResourceExecutedContext context) { string key = context.HttpContext.Request.Path; CacheDictionary[key] = context.Result; Console.WriteLine("执行OnResourceExecuted"); } }
[CustomResourceFilter] public IActionResult IndexResource() { ViewBag.Date = DateTime.Now; return View(); }
3. ExceptionFilter
ExceptionFilter用来处理异常。
(1)自定义一个CustomExceptionFilterAttribute
,实现IExceptionFilter
接口。
(2)实现方法,先判断,异常是否被处理过,如果没有被处理过,就处理。如果是ajax请求,就返回JosnResult,如果不是Ajax请求,就返回错误页面。
(3)全局注册使用(和之前的ActionFilter全局注册一样)。
public class CustomExceptionFilterAttribute : Attribute, IExceptionFilter { private IModelMetadataProvider _modelMetadataProvider = null; public CustomExceptionFilterAttribute(IModelMetadataProvider modelMetadataProvider) { _modelMetadataProvider = modelMetadataProvider; } public void OnException(ExceptionContext context) { if (!context.ExceptionHandled) //异常是否被处理过 { //在这里处理 如果是Ajax请求===返回Json if (this.IsAjaxRequest(context.HttpContext.Request))//header看看是不是XMLHttpRequest { context.Result = new JsonResult(new { Result = false, Msg = context.Exception.Message });//中断式---请求到这里结束了,不再继续Action } else { //跳转到异常页面 var result = new ViewResult { ViewName = "~/Views/Shared/Error.cshtml" }; result.ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState); result.ViewData.Add("Exception", context.Exception); context.Result = result; //断路器---只要对Result赋值--就不继续往后了; } context.ExceptionHandled = true; } } private bool IsAjaxRequest(HttpRequest request) { string header = request.Headers["X-Requested-With"]; return "XMLHttpRequest".Equals(header); } }
ExceptionFilter能捕捉到哪些异常?
- 控制器实例化异常 [True]
- 异常发生在Try-cache中 [False]
- 在视图中发生异常 [False]
- Service层发生异常 [True]
- 在Action中发生异常 [True]
- 请求错误路径异常 [True] 可以使用中间件来支持,只要不是200的状态,就都可以处理。
4. ResultFilter
ResultFilter在return 返回时调用。
它的应用,比如双语言系统,其实就需要两个视图根据语言的不同,来选择不同的视图来渲染。因为在渲染视图之前,会进入到OnResultExecuting
方法,就可以在这个方法中确定究竟使用哪一个视图文件。
(1)自定义一个类,继承Attribute,实现IResultFilter接口,实现方法
(2)标记在Action方法头上
(3)执行顺序:视图执行前,渲染视图,视图执行后
public class CustomResultFilterAttribute : Attribute, IResultFilter { private IModelMetadataProvider _modelMetadataProvider = null; public CustomResultFilterAttribute(IModelMetadataProvider modelMetadataProvider) { _modelMetadataProvider = modelMetadataProvider; } /// <summary> /// 渲染视图之前执行 /// </summary> /// <param name="context"></param> public void OnResultExecuting(ResultExecutingContext context) { //在这里就可以有一个判断,符合某个情况,就使用哪一个视图; Console.WriteLine("渲染视图之前执行"); string view = context.HttpContext.Request.Query["View"];//也可以是配置文件 if (view == "1") //中文 { var result = new ViewResult { ViewName = "~/Views/Seventh/IndexOne.cshtml" }; result.ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState); context.Result = result; } else { var result = new ViewResult { ViewName = "~/Views/Seventh/IndexOne-2.cshtml" }; result.ViewData = new ViewDataDictionary(_modelMetadataProvider, context.ModelState); context.Result = result; } } /// <summary> /// 渲染视图之后执行 /// </summary> /// <param name="context"></param> public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("渲染视图之后执行"); } }
5. AuthorizationFilter
AuthorizationFilter用来做鉴权授权。通过中间件来支持。
5.1 鉴权授权
(1)在Startup中方法Configure的app.UseRouting()之后,在app.UseEndpoints()之前,增加鉴权授权。 鉴权app.UseAuthentication()
,授权app.UseAuthorization()
。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory) { ...... // 鉴权 监测用户是否登录 app.UseAuthentication(); // 授权 监测有没有权限访问后续页面 app.UseAuthorization(); ...... }
(2)在Startup中方法ConfigureServices内添加注册。
public void ConfigureServices(IServiceCollection services) { ...... services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = new PathString("/Home/Login"); //如果授权失败就跳转到登录 }); ...... }
(3)在指定Action上加上[Authorize]
特性做鉴权授权。也可以全局标记。
[Authorize] public IActionResult Index() { return View(); } [AllowAnonymous] //避开权限检查 public IActionResult Login() { return View(); } [HttpPost] [AllowAnonymous] public IActionResult Login(string name, string password, string verify) { string verifyCode = base.HttpContext.Session.GetString("CheckCode"); //if (verifyCode != null && verifyCode.Equals(verify, StringComparison.CurrentCultureIgnoreCase)) //{ #region 鉴权:鉴权,检测有没有登录,登录的是谁,赋值给User //rolelist 是登录成功后用户的角色---是来自于数据库的查询;不同的用户会查询出不同的角色; var rolelist = new List<string>() { "Admin", "Teacher", "Student" }; //ClaimTypes.Role就是做权限认证的标识; var claims = new List<Claim>()//鉴别你是谁,相关信息 { new Claim(ClaimTypes.Role,"Admin"), new Claim(ClaimTypes.Name,name), new Claim("password",password),//可以写入任意数据 new Claim("Account","admin"), new Claim("role","admin"), new Claim("admin","admin"), new Claim("User","admin") }; foreach (var role in rolelist) { claims.Add(new Claim(ClaimTypes.Role, role)); } ClaimsPrincipal userPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer")); HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, userPrincipal, new AuthenticationProperties { ExpiresUtc = DateTime.UtcNow.AddMinutes(30),//过期时间:30分钟 }).Wait(); #endregion var user = HttpContext.User; return base.Redirect("/Home/Index"); } else { base.ViewBag.Msg = "账号密码错误"; } #endregion //} //else //{ // base.ViewBag.Msg = "验证码错误"; //} return View(); }
5.1 鉴权授权-角色授权
即用户角色不同,在访问页面时需要做不同的拦截。
(1)一个特性标记到方法上,通过逗号分隔不同角色,只要是有一个角色符合就能够访问,角色之间是或者的关系。
[Authorize(Roles = "Admin,Teacher,Student")]
(2)多个特性标记,多个角色之前是且的关系,必须要包含所有的角色,才能够访问。
[Authorize(Roles = "Admin")] [Authorize(Roles = "Teacher")] [Authorize(Roles = "Student")]
5.2 鉴权授权-策略授权
上面的角色授权是在代码中写死了角色,但我们更希望能够用处理逻辑来完成校验,就需要策略授权。
(1)添加CustomAuthorizationRequirement
类继承自IAuthorizationRequirement
,用来传递策略名称。
public class CustomAuthorizationRequirement : IAuthorizationRequirement { public string Name { get; set; } public CustomAuthorizationRequirement(string policyname) { this.Name = policyname; } }
(2)添加CustomAuthorizationHandler
类专用做检验逻辑, 继承自泛型抽象类AuthorizationHandler<CustomAuthorizationRequirement>
。
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthorizationRequirement requirement) { if (requirement.Name == "Policy01") { ///策略1的逻辑 } if (requirement.Name == "Policy02") { ///策略1的逻辑 } //其实这里可以去数据库里面去做一些查询,然后根据用户的信息,做计算;如果符合就context.Succeed(requirement); //否则就Task.CompletedTask; //context.User 鉴权成功(登录成功以后),用户的信息; var role = context.User.FindFirst(c => c.Value.Contains("admin")); if (role != null) { context.Succeed(requirement); //验证通过了 } return Task.CompletedTask; //验证不通过 }
(3)添加注册,并支持多种策略。
services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>(); services.AddAuthorization(option => { option.AddPolicy("customPolicy01", policy => { policy.AddRequirements(new CustomAuthorizationRequirement("Policy01")); }); }); services.AddAuthorization(option => { option.AddPolicy("customPolicy02", policy => { policy.AddRequirements(new CustomAuthorizationRequirement("Policy02")); }); });
(4)标记到使用的方法上。
[Authorize(policy: "customPolicy01")] public IActionResult IndexPolicy() { return View(); }
本文来自博客园,作者:一纸年华,转载请注明原文链接:https://www.cnblogs.com/nullcodeworld/p/18210626
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)