.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,实现IActionFilterOnActionExecutingOnActionExecuted方法。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();
        }
posted @ 2021-03-07 11:16  一纸年华  阅读(28)  评论(0编辑  收藏  举报  来源