Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 692|回复: 0

第十五节:Asp.Net Core中的各种过滤器(授权、资源、操作、结果、异常)

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-7-21 14:32:10 | 显示全部楼层 |阅读模式

    一. 简介

    1. 说明

      提到过滤器,通常是指请求处理管道中特定阶段之前或之后的代码,可以处理:授权、响应缓存(对请求管道进行短路,以便返回缓存的响应)、 防盗链、本地化国际化等,过滤器用于横向处理业务,符合Aop思想,它也可以有效的避免代码的重复复制。

      在Asp.Net Core中,有5种过滤器,分别是授权、资源、操作、结果、异常五大过滤器,与之前的Asp.Net 相比,多了一个资源过滤器,剩下的4个授权、 操作、结果、异常过滤器则没有什么太大的区别。

    PS: 传统Asp.Net 中的4种过滤器参考  https://www.cnblogs.com/yaopengfei/p/7910763.html

    2. 获取区域、控制器、Action的名称

    (1). 方法1

      context.ActionDescriptor.RouteValues["area"].ToString();

      context.ActionDescriptor.RouteValues["controller"].ToString();

      context.ActionDescriptor.RouteValues["action"].ToString();

    (2). 方法2:

      context.RouteData.Values["controller"].ToString();

      context.RouteData.Values["action"].ToString();

    测试案例详见“AuthorizeFilter”类,以特性的形式作用于Areas下的TestController下的Index。

    代码如下:

     1     public class AuthorizeFilter : Attribute,IAuthorizationFilter
     2     {
     3         public void OnAuthorization(AuthorizationFilterContext context)
     4         {
     5             //1. 获取区域、控制器、Action的名称
     6             //必须在区域里的控制器上加个特性[Area("")]才能获取
     7             var areaName = context.ActionDescriptor.RouteValues["area"] == null ? "" : context.ActionDescriptor.RouteValues["area"].ToString();
     8             var controllerName = context.ActionDescriptor.RouteValues["controller"] == null ? "" : context.ActionDescriptor.RouteValues["controller"].ToString();
     9             var actionName = context.ActionDescriptor.RouteValues["action"] == null ? "" : context.ActionDescriptor.RouteValues["action"].ToString();
    10 
    11             //下面的方式也能获取控制器和action的名称
    12             //var controllerName = context.RouteData.Values["controller"].ToString();
    13             //var actionName = context.RouteData.Values["action"].ToString();
    14 
    15         }
    16 
    17     }

    特别注意:如果要获取Areas名称,必须在区域里的控制器上加个特性[Area("")]才能获取,这一点和以前的Asp.Net不同。

    3. 作用域:同传统Asp.Net相同,可以作用于全局、控制器、Action。

    (1).情况一: 过滤器中没有构造函数,如:AuthorizeFilter类

     A.作用于全局:在ConfigureService中的AddMvc方法中进行注入,有两种写法,如:o.Filters.Add(typeof(AuthorizeFilter)); 或 o.Filters.Add(new AuthorizeFilter());

     B.作用于Controller或Action: 直接以特性的形式作用于Controller或Action即可,与Asp.Net中相同。

    (2).情况二: 过滤器中有构造函数,且构造函数中注入了其他类型,如:AuthorizeFilter2 类

    分享AuthorizeFilter2代码:

     1     /// <summary>
     2     /// 授权过滤器2(含构造函数)
     3     /// </summary>
     4     public class AuthorizeFilter2 : Attribute, IAuthorizationFilter
     5     {
     6         private IConfiguration Configuration;
     7 
     8         public AuthorizeFilter2(IConfiguration configuration)
     9         {
    10             Configuration = configuration;
    11         }
    12 
    13         public void OnAuthorization(AuthorizationFilterContext context)
    14         {
    15             //1. 获取区域、控制器、Action的名称
    16             //必须在区域里的控制器上加个特性[Area("")]才能获取
    17             var areaName = context.ActionDescriptor.RouteValues["area"] == null ? "" : context.ActionDescriptor.RouteValues["area"].ToString();
    18             var controllerName = context.ActionDescriptor.RouteValues["controller"] == null ? "" : context.ActionDescriptor.RouteValues["controller"].ToString();
    19             var actionName = context.ActionDescriptor.RouteValues["action"] == null ? "" : context.ActionDescriptor.RouteValues["action"].ToString();
    20 
    21             //2. 测试构造函数注入内容的读取
    22             var myName = Configuration["myName"];
    23         }
    24     }
    View Code

     A.作用于全局:与上面情况一用法一样,直接在AddMvc方法中进行注入,即可以使用过滤器中的构造函数中注入的对象,不需要特殊处理

     B.作用于Controller或Action:发现如果直接以特性的形式进行作用,会报错缺少参数,这个时候正式引入两个特别的内置类,来处理这个问题:

      ① ServiceFilterAttribute:首先在控制器或action上这样用 [ServiceFilter(typeof(AuthorizeFilter2))], 然后在 ConfigureService中对该类进行注册一下, 如: services.AddScoped<AuthorizeFilter2>();

      ② TypeFilterAttribute: 在控制器或action上这样用 [TypeFilter(typeof(AuthorizeFilter2))] 即可,如下面的Index,不需要再在ConfigureService中进行注册了, 相比上面的ServiceFilterAttribute更方便。

    代码见上面

      ③ 在属性上实现 IFilterFactory:通过继承TypeFilterAttribute来实现,TypeFilterAttribute 可实现 IFilterFactory。 IFilterFactory 公开用于创建 IFilterMetadata 实例的 CreateInstance 方法,CreateInstance 从服务容器 (DI) 中加载指定的类型。

    代码如下:

     1     /// <summary>
     2     /// 授权过滤器3(含构造函数 在属性上实现IFilterFactory)
     3     /// </summary>
     4     public class AuthorizeFilter3 : TypeFilterAttribute
     5     {
     6         public AuthorizeFilter3() : base(typeof(AuthorizeFilter3Impl))
     7         {
     8         }
     9 
    10         private class AuthorizeFilter3Impl : IAuthorizationFilter
    11         {
    12             private IConfiguration Configuration;
    13             public AuthorizeFilter3Impl(IConfiguration configuration)
    14             {
    15                 Configuration = configuration;
    16             }
    17 
    18             public void OnAuthorization(AuthorizationFilterContext context)
    19             {
    20                 //1. 获取区域、控制器、Action的名称
    21                 //必须在区域里的控制器上加个特性[Area("")]才能获取
    22                 var areaName = context.ActionDescriptor.RouteValues["area"] == null ? "" : context.ActionDescriptor.RouteValues["area"].ToString();
    23                 var controllerName = context.ActionDescriptor.RouteValues["controller"] == null ? "" : context.ActionDescriptor.RouteValues["controller"].ToString();
    24                 var actionName = context.ActionDescriptor.RouteValues["action"] == null ? "" : context.ActionDescriptor.RouteValues["action"].ToString();
    25 
    26                 //2. 测试构造函数注入内容的读取
    27                 var myName = Configuration["myName"];
    28             }
    29         }
    30     }

    4. 取消和设置短路

    (1).过滤器直接取消:通过context.Result来截断请求,使过滤器管道短路

    (2).页面的跳转:需要区分是否是ajax请求,然后通过上面的context.Result返回不同的内容。

    PS:根据request.Headers["X-Requested-With"]是否包含XMLHttpRequest来判断是不是ajax请求。

     1     /// <summary>
     2     /// 授权过滤器
     3     /// </summary>
     4     public class AuthorizeFilter : Attribute,IAuthorizationFilter
     5     {
     6         public void OnAuthorization(AuthorizationFilterContext context)
     7         {
     8             //1. 获取区域、控制器、Action的名称
     9             //必须在区域里的控制器上加个特性[Area("")]才能获取
    10             var areaName = context.ActionDescriptor.RouteValues["area"] == null ? "" : context.ActionDescriptor.RouteValues["area"].ToString();
    11             var controllerName = context.ActionDescriptor.RouteValues["controller"] == null ? "" : context.ActionDescriptor.RouteValues["controller"].ToString();
    12             var actionName = context.ActionDescriptor.RouteValues["action"] == null ? "" : context.ActionDescriptor.RouteValues["action"].ToString();
    13 
    14             //下面的方式也能获取控制器和action的名称
    15             //var controllerName = context.RouteData.Values["controller"].ToString();
    16             //var actionName = context.RouteData.Values["action"].ToString();
    17 
    18             //2.判断是什么请求,进行响应的页面跳转
    19             if (IsAjaxRequest(context.HttpContext.Request))
    20             {
    21                 //2.1 是ajax请求
    22                 context.Result = new JsonResult(new
    23                 {
    24                     status = "error",
    25                     message = "您没有权限"
    26                 });
    27             }
    28             else
    29             {
    30                 //2.2 不是ajax请求
    31                 var result = new ViewResult { ViewName = "~/Views/Shared/Error.cshtml" };
    32                 context.Result = result;
    33             }
    34         }
    35 
    36         /// <summary>
    37         /// 判断该请求是否是ajax请求
    38         /// </summary>
    39         /// <param name="request"></param>
    40         /// <returns></returns>
    41         private bool IsAjaxRequest(HttpRequest request)
    42         {
    43             string header = request.Headers["X-Requested-With"];
    44             return "XMLHttpRequest".Equals(header);
    45         }
    46     }

     

    二. 五大过滤器

    补充一下内置的过滤器(此处建个表格说明一下继承类或接口)

     (1).ActionFilterAttribute

     (2).ExceptionFilterAttribute

     (3).ResultFilterAttribute

     (4).FormatFilterAttribute

     (5).ServiceFilterAttribute:用于处理含构造函数的自定义过滤器,但需要先注册。

     (6).TypeFilterAttribute:用于处理含构造函数的自定义过滤器,不需要注册。

    1.授权过滤器

    (1) 说明:它是过滤器管道中第一个过滤器,控制对方法的访问,仅有在它之前执行的方法,没有之后;在授权过滤器中不会处理异常, 异常过滤器也捕获到其中产生的异常,因此要小心应对。

    (2) 实现:继承Attribute类,实现IAuthorizationFilter接口,重写OnAuthorization方法。

    注:继承Attribute类的目的是可以该过滤器以特性的形式作用于Controller或Action,下面过滤器都类似,不再说明。

    (3).用途:通常用来做权限校验(详见下面案例应用)。

    2. 资源过滤器

    (1) 说明:只有授权过滤器在资源过滤器之前运行,里面的OnResourceExecuting重写是在创建控制器调用的。

    (2) 实现:继承Attribute类,实现IResourceFilter接口,重写OnResourceExecuting 和 OnResourceExecuted方法。

     (异步的话实现IAsyncResourceFilter接口,重写OnResourceExecutionAsync方法)

    (3) 用途:做一些对变化要求不高的页面的缓存(详见下面案例应用)。

    3. 操作过滤器(行为过滤器)

    (1) 说明:分别在操作方法之前和之后执行

    (2) 实现:继承Attribute类,实现IActionFilter接口,重写OnActionExecuting 和 OnActionExecuted方法。 或者直接继承ActionFilterAttribute类,观察源码可知,该类继承了Attribute类,而且还实现IActionFilter,IResultFilter接口。(异步的话实现IAsyncActionFilter接口,重写OnActionExecutionAsync方法)

    4. 结果过滤器

    (1) 说明:在方法执行前后,且操作过滤器之后;结果(如:页面渲染)的前后运行。

    (2) 实现:继承Attribute类,实现IResultFilter接口,重写OnResultExecuting 和 OnResultExecuted方法。 或者直接继承ResultFilterAttribute类,(或ActionFilterAttribute类), 观察源码可知,该类继承了Attribute类,而且还实现IResultFilter接口。(异步的话实现IAsyncActionFilter接口, 重写OnActionExecutionAsync方法) 还可以实现:IAlwaysRunResultFilter 或 IAsyncAlwaysRunResultFilter 接口。

    (3).用途:可以获取action的返回结果,进行一些处理,比如:根据要求返回json数据或jsonp数据(详见cors章节)。

    5. 异常过滤器

    (1) 说明:用于实现常见的错误处理策略,没有之前和之后事件,处理 Razor 页面或控制器创建、模型绑定、操作过滤器或操作方法中发生的未经处理的异常。 但无法捕获资源过滤器、结果过滤器或 MVC 结果执行中发生的异常 。

    (2) 实现:继承Attribute类,实现IExceptionFilter接口,重写OnException方法。 或者直接继承ExceptionFilterAttribute类,观察源码可知,该类继承了Attribute类,而且还实现IExceptionFilter接口。(异步的话实现 IAsyncExceptionFilter接口,重写OnExceptionAsync方法)

    (3) 用途:全局捕获异常,进行相关处理。

     

    三. 高级

    1. 过滤器执行顺序

     异常过滤器不参与测试,测试剩余四个过滤器的执行顺序,将四个过滤器在下面Index2方法上,经断点测试执行顺序如下:

     OnAuthorization→OnResourceExecuting→创建控制器→OnActionExecuting→执行action业务→OnActionExecuted→OnResultExecuting→页面渲染加载→

    OnResultExecuted→OnResourceExecuted

     

    2. 相同类型过滤器不同作用域的执行顺序

    A. 操作过滤器

      经测试,将操作过滤器分别作用在 action、Controller、全局,通过加断点测试执行顺序如下:

    OnActionExecuting(全局)→OnActionExecuting(Controller)→OnActionExecuting(action)→OnActionExecuted(action)→OnActionExecuted(Controller)→OnActionExecuted(全局)

    那么原理是什么呢?如何修改这个顺序呢?   

      接口IOrderedFilter,有个order属性,有小到大,访问顺序是有小到大,默认是0,实现的时候需要声明一个order属性,然后以特性作用于Action或Controller的时候声明order的值,如: [ActionOrderFilterController(Order =1)] 、[ActionOrderFilter(Order =-1)]

    代码如下:

     1   /// <summary>
     2     /// 测试操作过滤器,自定义Order
     3     /// (测试作用于Action)
     4     /// </summary>
     5     public class ActionOrderFilter : Attribute,IActionFilter, IOrderedFilter
     6     {
     7         public int Order { get; set; }
     8 
     9         public void OnActionExecuted(ActionExecutedContext context)
    10         {
    11             
    12         }
    13 
    14         public void OnActionExecuting(ActionExecutingContext context)
    15         {
    16             
    17         }
    18     }
    19     /// <summary>
    20     /// 测试操作过滤器,自定义Order
    21     /// (测试作用于Controller)
    22     /// </summary>
    23     public class ActionOrderFilterController : Attribute, IActionFilter, IOrderedFilter
    24     {
    25         public int Order { get; set; }
    26 
    27         public void OnActionExecuted(ActionExecutedContext context)
    28         {
    29 
    30         }
    31 
    32         public void OnActionExecuting(ActionExecutingContext context)
    33         {
    34 
    35         }
    36     }
    37     /// <summary>
    38     /// 测试操作过滤器,自定义Order
    39     /// (测试作用于全局)
    40     /// </summary>
    41     public class ActionOrderFilterGlobal : Attribute, IActionFilter, IOrderedFilter
    42     {
    43         public int Order { get; set; }
    44 
    45         public void OnActionExecuted(ActionExecutedContext context)
    46         {
    47 
    48         }
    49 
    50         public void OnActionExecuting(ActionExecutingContext context)
    51         {
    52 
    53         }
    54     }
    View Code

    通过加断点测试,执行顺序如下:

      OnActionExecuting(action)→OnActionExecuting(全局)→OnActionExecuting(Controller)→OnActionExecuted(Controller)→OnActionExecuted(全局)→OnActionExecuted(action)

    B. 异常过滤器: action→controller→全局 (经过测试)

     

    四. 案例应用

    1. 做页面缓存

     (1).原理:资源过滤器中的OnResourceExecuting是在创建控制器之前执行的,我们可以截取地址页面的地址作为缓存的key,然后判断一下该key是否有值,有的话能否转换成ViewResult,如果能,则直接context.Result截断返回该页面即可。

        资源过滤器中的OnResourceExecuted在页面渲染后执行,这个时候判断一下上面的key是否有值,没有的话将页面ViewResult存到该key对应的缓存里。

    代码分享:

     1     /// <summary>
     2     /// 利用资源过滤器做静态页面缓存
     3     /// 简单版本实现,仅为了说明原理,并没有做缓存过期等一系列操作
     4     /// </summary>
     5     public class MyPageCacheFilter : Attribute, IResourceFilter
     6     {
     7         private static readonly Dictionary<string, object> myCache = new Dictionary<string, object>();
     8         private string _cacheKey;
     9 
    10         /// <summary>
    11         /// 在创建控制器之前执行
    12         /// </summary>
    13         /// <param name="context"></param>
    14         public void OnResourceExecuting(ResourceExecutingContext context)
    15         {
    16             _cacheKey = context.HttpContext.Request.Path.ToString();
    17             if (myCache.ContainsKey(_cacheKey))
    18             {
    19                 var cachedValue = myCache[_cacheKey] as ViewResult;
    20                 if (cachedValue != null)
    21                 {
    22                     context.Result = cachedValue;//  截断请求 
    23                 }
    24             }
    25         }
    26         /// <summary>
    27         /// 肯定在页面渲染以后才执行了
    28         /// </summary>
    29         /// <param name="context"></param>
    30         public void OnResourceExecuted(ResourceExecutedContext context)
    31         {
    32             if (!String.IsNullOrEmpty(_cacheKey) && !myCache.ContainsKey(_cacheKey))
    33             {
    34                 var result = context.Result as ViewResult;
    35                 if (result != null)
    36                 {
    37                     myCache.Add(_cacheKey, result);
    38                 }
    39             }
    40         }
    41     }

     (2).演示:过滤器代码MyPageCacheFilter,测试页面下面的Index4。

     首先我们先进入到一个其他页面,如:http://localhost:22164/Home/Index, 然后修改地址进入到 http://localhost:22164/Home/Index4 页面,记录下页面的时间,刷新页面,发现时间

     不变,说明是从缓存中拿的页面,而不是重新加载的。

     1         /// <summary>
     2         /// 测试利用资源过滤器做页面缓存
     3         /// </summary>
     4         /// <returns></returns>
     5         [MyPageCacheFilter]
     6         public IActionResult Index4()
     7         {
     8             ViewBag.Time = DateTime.Now.ToString();
     9             return View();
    10         }

    测试结果:

    2. 做权限校验

       这里做一个简单案例说明,首先声明一个Skip特性,加在哪个action上面,表明该action不需要进行登录校验;而这里的登录校验只是简单的判断Session中是否有userName值,有的话校验通过;没有的话,写入Session,并跳转到错误页面。

    PS:Session的使用详见Session章节,在过滤器中可以使用注入的方式进行Session的注入,也可以直接通过 context.httpcontext.session 来点出来。

     下面是代码分享:

     特性的声明

    1    /// <summary>
    2     /// 表示不需要校验
    3     /// </summary>
    4     public class SkipAttribute:Attribute
    5     {
    6     }

    校验登录的过滤器

     1     /// <summary>
     2     /// 校验是否登录
     3     /// </summary>
     4     public class CheckLogin : Attribute, IAuthorizationFilter
     5     {
     6         private readonly IHttpContextAccessor _httpContextAccessor;
     7         private ISession _session => _httpContextAccessor.HttpContext.Session;
     8 
     9         public CheckLogin(IHttpContextAccessor httpContextAccessor)
    10         {
    11             _httpContextAccessor = httpContextAccessor;
    12         }
    13 
    14 
    15         public void OnAuthorization(AuthorizationFilterContext context)
    16         {
    17             //也可以这样获取Session,就不需要注入了。
    18             var testData = context.HttpContext.Session.GetString("userName");
    19 
    20             bool isHasAttr = false;
    21             //所有目标对象上所有特性
    22             var data = context.ActionDescriptor.EndpointMetadata.ToList();
    23             string attrName = typeof(SkipAttribute).ToString();
    24             //循环比对是否含有skip特性
    25             for (int i = 0; i < data.Count; i++)
    26             {
    27                 if (data.ToString().Equals(attrName))
    28                 {
    29                     isHasAttr = true;
    30                 }
    31             }
    32 
    33             //1. 校验是否标记跨过登录验证
    34             if (isHasAttr)
    35             {
    36                 //表示该方法或控制器跨过登录验证
    37                 //继续走控制器中的业务即可
    38             }
    39             else
    40             {
    41                 //这里只是简单的做一下校验
    42                 var userName = _session.GetString("userName");
    43                 if (string.IsNullOrEmpty(userName))
    44                 {
    45                     //表示没有值,校验没有通过
    46                     _session.SetString("userName", "ypf");
    47                     //截断请求
    48                     context.Result=new RedirectResult("~/Views/Shared/Error.cshtml");
    49                 }
    50             }
    51         }
    52     }

    作用的action

     1         /// <summary>
     2         /// 权限校验
     3         /// </summary>
     4         /// <returns></returns>
     5         //[Skip]
     6         [TypeFilter(typeof(CheckLogin))]
     7         public IActionResult Index5()
     8         {           
     9             return View();
    10         }

    补充一下Session注册代码

     

     

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-12-22 10:58 , Processed in 0.059263 second(s), 29 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表