一. 简介

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。

代码如下:

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

特别注意:如果要获取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代码:

     /// <summary>
/// 授权过滤器2(含构造函数)
/// </summary>
public class AuthorizeFilter2 : Attribute, IAuthorizationFilter
{
private IConfiguration Configuration; public AuthorizeFilter2(IConfiguration configuration)
{
Configuration = configuration;
} public void OnAuthorization(AuthorizationFilterContext context)
{
//1. 获取区域、控制器、Action的名称
//必须在区域里的控制器上加个特性[Area("")]才能获取
var areaName = context.ActionDescriptor.RouteValues["area"] == null ? "" : context.ActionDescriptor.RouteValues["area"].ToString();
var controllerName = context.ActionDescriptor.RouteValues["controller"] == null ? "" : context.ActionDescriptor.RouteValues["controller"].ToString();
var actionName = context.ActionDescriptor.RouteValues["action"] == null ? "" : context.ActionDescriptor.RouteValues["action"].ToString(); //2. 测试构造函数注入内容的读取
var myName = Configuration["myName"];
}
}

 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) 中加载指定的类型。

代码如下:

     /// <summary>
/// 授权过滤器3(含构造函数 在属性上实现IFilterFactory)
/// </summary>
public class AuthorizeFilter3 : TypeFilterAttribute
{
public AuthorizeFilter3() : base(typeof(AuthorizeFilter3Impl))
{
} private class AuthorizeFilter3Impl : IAuthorizationFilter
{
private IConfiguration Configuration;
public AuthorizeFilter3Impl(IConfiguration configuration)
{
Configuration = configuration;
} public void OnAuthorization(AuthorizationFilterContext context)
{
//1. 获取区域、控制器、Action的名称
//必须在区域里的控制器上加个特性[Area("")]才能获取
var areaName = context.ActionDescriptor.RouteValues["area"] == null ? "" : context.ActionDescriptor.RouteValues["area"].ToString();
var controllerName = context.ActionDescriptor.RouteValues["controller"] == null ? "" : context.ActionDescriptor.RouteValues["controller"].ToString();
var actionName = context.ActionDescriptor.RouteValues["action"] == null ? "" : context.ActionDescriptor.RouteValues["action"].ToString(); //2. 测试构造函数注入内容的读取
var myName = Configuration["myName"];
}
}
}

4. 取消和设置短路

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

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

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

     /// <summary>
/// 授权过滤器
/// </summary>
public class AuthorizeFilter : Attribute,IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
//1. 获取区域、控制器、Action的名称
//必须在区域里的控制器上加个特性[Area("")]才能获取
var areaName = context.ActionDescriptor.RouteValues["area"] == null ? "" : context.ActionDescriptor.RouteValues["area"].ToString();
var controllerName = context.ActionDescriptor.RouteValues["controller"] == null ? "" : context.ActionDescriptor.RouteValues["controller"].ToString();
var actionName = context.ActionDescriptor.RouteValues["action"] == null ? "" : context.ActionDescriptor.RouteValues["action"].ToString(); //下面的方式也能获取控制器和action的名称
//var controllerName = context.RouteData.Values["controller"].ToString();
//var actionName = context.RouteData.Values["action"].ToString(); //2.判断是什么请求,进行响应的页面跳转
if (IsAjaxRequest(context.HttpContext.Request))
{
//2.1 是ajax请求
context.Result = new JsonResult(new
{
status = "error",
message = "您没有权限"
});
}
else
{
//2.2 不是ajax请求
var result = new ViewResult { ViewName = "~/Views/Shared/Error.cshtml" };
context.Result = result;
}
} /// <summary>
/// 判断该请求是否是ajax请求
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
private bool IsAjaxRequest(HttpRequest request)
{
string header = request.Headers["X-Requested-With"];
return "XMLHttpRequest".Equals(header);
}
}

二. 五大过滤器

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

 (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)]

代码如下:

   /// <summary>
/// 测试操作过滤器,自定义Order
/// (测试作用于Action)
/// </summary>
public class ActionOrderFilter : Attribute,IActionFilter, IOrderedFilter
{
public int Order { get; set; } public void OnActionExecuted(ActionExecutedContext context)
{ } public void OnActionExecuting(ActionExecutingContext context)
{ }
}
/// <summary>
/// 测试操作过滤器,自定义Order
/// (测试作用于Controller)
/// </summary>
public class ActionOrderFilterController : Attribute, IActionFilter, IOrderedFilter
{
public int Order { get; set; } public void OnActionExecuted(ActionExecutedContext context)
{ } public void OnActionExecuting(ActionExecutingContext context)
{ }
}
/// <summary>
/// 测试操作过滤器,自定义Order
/// (测试作用于全局)
/// </summary>
public class ActionOrderFilterGlobal : Attribute, IActionFilter, IOrderedFilter
{
public int Order { get; set; } public void OnActionExecuted(ActionExecutedContext context)
{ } public void OnActionExecuting(ActionExecutingContext context)
{ }
}

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

  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对应的缓存里。

代码分享:

     /// <summary>
/// 利用资源过滤器做静态页面缓存
/// 简单版本实现,仅为了说明原理,并没有做缓存过期等一系列操作
/// </summary>
public class MyPageCacheFilter : Attribute, IResourceFilter
{
private static readonly Dictionary<string, object> myCache = new Dictionary<string, object>();
private string _cacheKey; /// <summary>
/// 在创建控制器之前执行
/// </summary>
/// <param name="context"></param>
public void OnResourceExecuting(ResourceExecutingContext context)
{
_cacheKey = context.HttpContext.Request.Path.ToString();
if (myCache.ContainsKey(_cacheKey))
{
var cachedValue = myCache[_cacheKey] as ViewResult;
if (cachedValue != null)
{
context.Result = cachedValue;// 截断请求
}
}
}
/// <summary>
/// 肯定在页面渲染以后才执行了
/// </summary>
/// <param name="context"></param>
public void OnResourceExecuted(ResourceExecutedContext context)
{
if (!String.IsNullOrEmpty(_cacheKey) && !myCache.ContainsKey(_cacheKey))
{
var result = context.Result as ViewResult;
if (result != null)
{
myCache.Add(_cacheKey, result);
}
}
}
}

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

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

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

         /// <summary>
/// 测试利用资源过滤器做页面缓存
/// </summary>
/// <returns></returns>
[MyPageCacheFilter]
public IActionResult Index4()
{
ViewBag.Time = DateTime.Now.ToString();
return View();
}

测试结果:

2. 做权限校验

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

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

下面是代码分享:

 特性的声明

    /// <summary>
/// 表示不需要校验
/// </summary>
public class SkipAttribute:Attribute
{
}

校验登录的过滤器

     /// <summary>
/// 校验是否登录
/// </summary>
public class CheckLogin : Attribute, IAuthorizationFilter
{
private readonly IHttpContextAccessor _httpContextAccessor;
private ISession _session => _httpContextAccessor.HttpContext.Session; public CheckLogin(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
} public void OnAuthorization(AuthorizationFilterContext context)
{
//也可以这样获取Session,就不需要注入了。
var testData = context.HttpContext.Session.GetString("userName"); bool isHasAttr = false;
//所有目标对象上所有特性
var data = context.ActionDescriptor.EndpointMetadata.ToList();
string attrName = typeof(SkipAttribute).ToString();
//循环比对是否含有skip特性
for (int i = ; i < data.Count; i++)
{
if (data[i].ToString().Equals(attrName))
{
isHasAttr = true;
}
} //1. 校验是否标记跨过登录验证
if (isHasAttr)
{
//表示该方法或控制器跨过登录验证
//继续走控制器中的业务即可
}
else
{
//这里只是简单的做一下校验
var userName = _session.GetString("userName");
if (string.IsNullOrEmpty(userName))
{
//表示没有值,校验没有通过
_session.SetString("userName", "ypf");
//截断请求
context.Result=new RedirectResult("~/Views/Shared/Error.cshtml");
}
}
}
}

作用的action

         /// <summary>
/// 权限校验
/// </summary>
/// <returns></returns>
//[Skip]
[TypeFilter(typeof(CheckLogin))]
public IActionResult Index5()
{
return View();
}

补充一下Session注册代码

!

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

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

  1. asp.net Core 中AuthorizationHandler 实现自定义授权

    前言 ASP.NET Core 中 继承的是AuthorizationHandler ,而ASP.NET Framework 中继承的是AuthorizeAttribute. 它们都是用过重写里面的方 ...

  2. ASP.NET Core 学习笔记 第五篇 ASP.NET Core 中的选项

    前言 还记得上一篇文章中所说的配置吗?本篇文章算是上一篇的延续吧.在 .NET Core 中读取配置文件大多数会为配置选项绑定一个POCO(Plain Old CLR Object)对象,并通过依赖注 ...

  3. 第五节:EF Core中的三类事务(SaveChanges、DbContextTransaction、TransactionScope)

    一. 说明 EF版本的事务介绍详见: 第七节: EF的三种事务的应用场景和各自注意的问题(SaveChanges.DBContextTransaction.TransactionScope). 本节主 ...

  4. 在ASP.NET Core中使用AOP来简化缓存操作

    前言 关于缓存的使用,相信大家都是熟悉的不能再熟悉了,简单来说就是下面一句话. 优先从缓存中取数据,缓存中取不到再去数据库中取,取到了在扔进缓存中去. 然后我们就会看到项目中有类似这样的代码了. pu ...

  5. 【php增删改查实例】第二十五节 - 在main.php中显示头像

    在用户成功上传头像以后,用户登录系统,应该能够看到自己的头像,本节演示如何在这个地方: 添加用户头像. 1.用DIV做: border-radius:50% background:url(xxx.jp ...

  6. 第二十九节: Asp.Net Core零散获取总结(不断补充)

    1. IWebHostEnvironment获取常用属性 (1).获取项目的根目录 _env.ContentRootPath 等价于 Directory.GetCurrentDirectory() ( ...

  7. 如何在ASP.NET Core中编写高效的控制器

    ​通过遵循最佳实践,可以编写更好的控制器.所谓的"瘦"控制器(指代码更少.职责更少的控制器)更容易阅读和维护.而且,一旦你的控制器很瘦,可能就不需要对它们进行太多测试了.相反,你可 ...

  8. ASP.NET Core中使用自定义MVC过滤器属性的依赖注入

    除了将自己的中间件添加到ASP.NET MVC Core应用程序管道之外,您还可以使用自定义MVC过滤器属性来控制响应,并有选择地将它们应用于整个控制器或控制器操作. ASP.NET Core中常用的 ...

  9. 从零搭建一个IdentityServer——聊聊Asp.net core中的身份验证与授权

    OpenIDConnect是一个身份验证服务,而Oauth2.0是一个授权框架,在前面几篇文章里通过IdentityServer4实现了基于Oauth2.0的客户端证书(Client_Credenti ...

随机推荐

  1. DjangoDRF之视图总结

    思维导图xmind文件:https://files-cdn.cnblogs.com/files/benjieming/DRF%E6%A8%A1%E5%9D%97%E4%B9%8B%E8%A7%86%E ...

  2. English--介词省略句型与总结

    English|介词省略句型与总结 本篇文章将会介绍介词的省略与整个语法内容的总结.小板凳都带上,准备开始了! 前言 目前所有的文章思想格式都是:知识+情感. 知识:对于所有的知识点的描述.力求不含任 ...

  3. idea中Entity实体中报错:cannot resolve column/table/...解决办法。

    idea中Entity实体中报错:cannot resolve column/table/...解决办法. 若idea中Entity实体中报错: cannot resolve column.... c ...

  4. 【转载】Gradle学习 第四章:安装Gradle

    转载地址:http://ask.android-studio.org/?/article/16 4.1. Prerequisites 前提条件Gradle requires a Java JDK or ...

  5. MySQL基础-Linux从入门到精通第十天(非原创)

    文章大纲 一.关于数据库二.MySQL的安装与初始化三.MySQL的基本操作(难点)四.扩展五.学习资料下载六.参考文章   一.关于数据库 mysql的基础知识,可以参考文章:https://www ...

  6. Django中ModelViewSet的应用

    ModelViewSet源码 class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateM ...

  7. 十大排序算法总结(Python3实现)

    十大排序算法总结(Python3实现) 本文链接:https://blog.csdn.net/aiya_aiya_/article/details/79846380 目录 一.概述 二.算法简介及代码 ...

  8. 201871020225-牟星源《面向对象程序设计(java)》第四周学习总结

    201871020225-牟星源<面向对象程序设计(java)>第四周学习总结 项目 内容 这个作业属于哪个课程 https://www.cnblogs.com/nwnu-daizh/ 这 ...

  9. (HK1-0)激活与配置摄像机

    HK使用手册 网络连接 激活与配置摄像机 网络摄像机可通过 SADP 软件.客户端软件和浏览器三种方式激活, 具体激活操作方式可参见<网络摄像机操作手册>. 1. 安装随机光盘或从官网下载 ...

  10. 从rpm包提取rpm spec 的几种方法

    包含了源码包 先安装,然后在rpmbuild 目录直接可以查看文件 不用安装 ,使用rpm2cpio rpm2cpio myrpm.src.rpm | cpio -civ '*.spec' 没有源码 ...