【ASP.NET Core】MVC过滤器:运行流程

MVC 的过滤器(Filters)也翻译为"筛选器"。但是老周更喜欢翻译为"过滤器",意思上更好理解。

既然都叫过滤器了,就是在MVC的操作方法调用前后进行特殊处理的类型。比如:

a、此调用是否已授权?

b、在模型绑定之前要不要修改数据源?(可能含有儿童不宜的数据)

c、在调用MVC方法前要不要改一改输入参数?在MVC方法调用之后要不要处理一下结果(加点味精,进一步调味)

d、发生异常后怎么处理?

过滤器可解决上面一堆提问。

ASP.NET Core 的 MVC 框架中,所有过滤器都实现共同接口 IFilterMetadata。该接口空空如也,未定义任何成员。说白了,它的用处是作为一种"记号"。你怎么证明你就是过滤器,嗯,看看你实现了 IFilterMetadata 接口没?实现了就认定是过滤器。所以,该接口纯粹是个角色标签。

咱们写代码一般不会实现 IFilterMetadata 接口,毕竟里面什么卵方法都没有,怎么规范类型?因此,过滤器专属命名空间 Microsoft.AspNetCore.Mvc.Filters 下为我们公开了以下接口,方便开发者实现:

1、IAuthorizationFilter:授权过滤器,它的优先级最高,总是最先运行。看看你有没有权限调用 MVC 方法,若没权限,就 See you La La。

2、IResourceFilter:资源过滤器。它在授权过滤成功后、模型绑定前运行。可以检查一下用于绑定的数据,要不要改一下。

3、IActionFilter:操作方法过滤器,就是针对 MVC Action 的。在操作方法运行前后运行,可以用来修改输入参数值。

4、IResultFilter:结果过滤器。当 MVC 操作方法运行成功后就会运行,可以用来修改运行结果。比如加点 HTTP 消息头什么的。

5、IExceptionFilter:当 MVC 操作方法运行过程中发生异常才会运行,无异常就不会运行。

...... 其实还有的,但这里咱们先不提,免得大伙搞得头晕。

过滤器不止一个,同一类型的过滤还可能有多个,因此,它们就像中间件那样,一个个链接起来,形成下水沟,哦不,是调用管道,或叫调用栈。于是,这就出现谁先运行的问题,虽然上面的介绍有说明,不过那太抽象了。任何编程知识只要能用代码来验证和观察,就不用图表和理论。

下面,咱们实现上述几个接口,然后往控制台上打印一些文本,来看看这些过滤器是怎么运行的。

复制代码
public class CustAuthFilter : IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        Console.WriteLine("授权过滤器运行");
    }
}

public class CustResourceFilter : IResourceFilter
{
    public void OnResourceExecuted(ResourceExecutedContext context)
    {
        Console.WriteLine("资源过滤器 - " + $"{nameof(OnResourceExecuted)}方法运行");
    }

    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        Console.WriteLine("资源过滤器 - " + $"{nameof(OnResourceExecuting)}方法运行");
    }
}

public class CustActionFilter : IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine("操作过滤器 - " + $"{nameof(OnActionExecuted)}方法运行");
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine("操作过滤器 - " + $"{nameof(OnActionExecuting)}方法运行");
    }
}

public class CustResultFilter : IResultFilter
{
    public void OnResultExecuted(ResultExecutedContext context)
    {
        Console.WriteLine("结果过滤器 - " + $"{nameof(OnResultExecuted)}方法运行");
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        Console.WriteLine("结果过滤器 - " + $"{nameof(OnResultExecuting)}方法运行");
    }
}

这里我没有实现异常过滤器,只实现了授权、资源、操作方法、结果这几个有代表性的。

授权过滤器只要实现 OnAuthorization 方法即可。在实现代码中,可以通过 HttpContext 对象查找授权有关的对象,如果确认是没有访问权限的,可以设置一个自己定 Result 让 MVC 操作方法的调用终止。

资源过滤器要实现两个方法:OnResourceExecuting 方法在模型绑定前调用,这时你有机会修改数据源;OnResourceExecuted 方法是在资源过滤之后的其他过滤器运行结束才被调用,即:

复制代码
ResourceExecuting
    ........ 剩余过滤器.......
ResourceExecuted

Action 过滤器也要实现两个方法:OnActionExecuting 在操作调用前运行;OnActionExecuted 是在操作方法调用后运行。

结果过滤器需要实现两个方法:OnResultExecuting 方法在操作结果执行前调用,这里可以修改 MVC 方法返回的值;OnResultExecuted 方法是在操作结果执行之后调用,一般这里可以改改HTTP向应头、Cookie 什么的。

其实咱们刚实现的过滤器都是同步版本,这些过滤器都有配套的异步版本,接口都是以 IAsync 开头。这里咱们先不用管同步异步,避免搞得复杂了。也不要去理会过滤器是全局的还是局部的,下面咱们统一把它们注册为全局的。配置方法是通过 MVC 选项类的 Filters 集合,把要用的过滤器添加进去即可。

复制代码
 var builder = WebApplication.CreateBuilder(args);
 builder.Services.AddControllersWithViews(options =>
 {
     // 配置全局过滤器
     options.Filters.Add<CustAuthFilter>();
     options.Filters.Add<CustResourceFilter>();
     options.Filters.Add<CustActionFilter>();
     options.Filters.Add<CustResultFilter>();
 });
 var app = builder.Build();

添加一个"狗头"控制器,用于测试。

复制代码
public class GouTouController : Controller
{
    public IActionResult Index()
    {
        Console.WriteLine("Index操作运行");
        return View();
    }
}

为了防止 ASP.NET Core 应用程序输出的日志干扰咱们查看控制台内容,咱们禁用所有日志输出。打开 appsettings.json 文件,把所有日志类别的记录级别改为 None。

复制代码
{
  "Logging": {
    "LogLevel": {
      "*": "None"
    }
  },
  "AllowedHosts": "*"
}

星号 * 的意思就是代表所有类别的日志,LogLevel 为 None 就不会输出日志了(貌似有个别日志禁用不了)。

运行程序后,控制台打印出这样的内容:

这个流程现在是不是很清晰了?咱们画图表了,直接这样表达就好:

复制代码
Author
Resource Executing
    Action Executing
            Action Running
    Action Executed
    Result Executing
            Result Running
    Result Executed
Resource Executed

局部过滤器的运行过程与全局过滤器相同,如果局部和全局过滤器同时使用,那会发生什么呢?咱们试试。

接下来我们为授权过滤、资源过滤、操作过滤、结果过滤各创建两个类------用于局部和全局。实际开发中一般不需要这样搞,通常全局和局部写一个类就行,毕竟过滤器类型在全局和局部是通用的。我这里只为了演示。局部过滤器是通过特性类的方式应用到 MVC 方法上的,所以,局部过滤器除了实现过滤器接口,还要从 Attribute 类派生。

1、实现局部、全局授权过滤器。

复制代码
// 授权过滤器-局部
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class MyAuthorFilterAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        Console.WriteLine("局部:授权过滤器运行");
    }
}

// 授权过滤器-全局
public class GlobAuthorFilter : IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        Console.WriteLine("全局:授权过滤器运行");
    }
}

2、实现局部、全局资源过滤器。

复制代码
// 资源过滤器-局部
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class MyResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuted(ResourceExecutedContext context)
    {
        Console.WriteLine("局部:资源过滤器-Executed");
    }

    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        Console.WriteLine("局部:资源过滤器-Executing");
    }
}

// 资源过滤器-全局
public class GlobResourceFilter : IResourceFilter
{
    public void OnResourceExecuted(ResourceExecutedContext context)
    {
        Console.WriteLine("全局:资源过滤器-Executed");
    }

    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        Console.WriteLine("全局:资源过滤器-Executing");
    }
}

3、实现局部、全局操作过滤器。

复制代码
// 操作过滤器-局部
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class MyActionFilterAttribute : Attribute, IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine("局部:操作过滤器-Executed");
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine("局部:操作过滤器-Executing");
    }
}

// 操作过滤器-全局
public class GlobActionFilter : IActionFilter
{
    public void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine("全局:操作过滤器-Executed");
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine("全局:操作过滤器-Executing");
    }
}

4、实现局部、全局结果过滤器。

复制代码
// 结果过滤器-局部
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class MyResultFilterAttribute : Attribute, IResultFilter
{
    public void OnResultExecuted(ResultExecutedContext context)
    {
        Console.WriteLine("局部:结果过滤器-Executed");
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        Console.WriteLine("局部:结果过滤器-Executing");
    }
}

// 结果过滤器-全局
public class GlobResultFilter : IResultFilter
{
    public void OnResultExecuted(ResultExecutedContext context)
    {
        Console.WriteLine("全局:结果过滤器-Executed");
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        Console.WriteLine("全局:结果过滤器-Executing");
    }
}

先注册全局过滤器。

复制代码
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers(options =>
{
    // 添加全局过滤器
    options.Filters.Add<GlobActionFilter>();
    options.Filters.Add<GlobAuthorFilter>();
    options.Filters.Add<GlobResourceFilter>();
    options.Filters.Add<GlobResultFilter>();
});
var app = builder.Build();

局部过滤器以特性方式应用于 MVC 操作方法。

复制代码
public class SpiderController : ControllerBase
{
    [MyResourceFilter]
    [MyResultFilter]
    [MyActionFilter, MyAuthorFilter]
    public IActionResult Index()
    {
        Console.WriteLine("Index操作被调用");
        return Content("大火烧了毛毛虫");
    }
}

和上一个例子一样,禁用日志输出(appsettings.json文件)。

复制代码
{
  "Logging": {
    "LogLevel": {
      "*": "None"
    }
  },
  ......
}

程序运行后,控制台打印以下内容:

过滤器按 授权->资源->操作->结果 运行的次序不变。在同种过滤器中,全局过滤器优先运行。

复制代码
全局授权过滤器
局部授权过滤器
全局资源过滤器 - 前
    局部资源过滤器 - 前
        全局操作过滤器 - 前
            局部操作过滤器 - 前
                【调用 MVC 操作方法】
            局部操作过滤器 - 后
        全局操作过滤器 - 后
        全局结果过滤器 - 前
             局部结果过滤器 - 前
                【执行操作结果】
             局部结果过滤器 - 后
        全局结果过滤器 - 后
    局部资源过滤器 - 后
全局资源过滤器 - 后

另外,有一件事要注意:如果你的控制器的基类是 Controller,那么,还有优先更高的 Action Filter。看看 Controller 类它实现了啥接口。

复制代码
public abstract class Controller : ControllerBase, IActionFilter, IFilterMetadata, IAsyncActionFilter, IDisposable

咱们把刚才的控制器代码改一下,让它继承 Controller 类,并重写 OnActionExecuting、OnActionExecuted 方法。

复制代码
public class SpiderController : Controller
{
    ......

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine("控制器实现的操作过滤器-Executing");
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine("控制器实现的操作过滤器-Executed");
        base.OnActionExecuted(context);
    }
}

然后再次运行程序,控制台将打印以下内容:

看,这个由控制器类实现的 Action 过滤器比全局的还早运行。

相关推荐
棉晗榜6 天前
.net core在linux导出excel,System.Drawing.Common is not supported on this platform
linux·excel·asp.net core·miniexcel
棉晗榜6 天前
asp.net core发布配置端口号,支持linux
asp.net core
coredx9 天前
如何优雅地让 ASP.NET Core 支持异步模型验证
asp.net core
小乖兽技术16 天前
ASP.NET Core Web 项目的部署:选择 IIS 还是 Kestrel?
后端·kestrel·iis·asp.net·asp.net core
界面开发小八哥18 天前
DevExtreme JS & ASP.NET Core v24.2新功能预览 - 全新的聊天组件
javascript·ui·asp.net core·界面控件·ui开发·devextreme·.net 9
gc_229923 天前
学习ASP.NET Core的身份认证(基于Session的身份认证3)
asp.net core·session·身份认证
gc_229924 天前
ASP.NET Core项目中使用SqlSugar连接多个数据库的方式
asp.net core·sqlsugar·多数据库
gc_229925 天前
学习ASP.NET Core的身份认证(基于Session的身份认证1)
asp.net core·session·身份认证
gc_22991 个月前
学习ASP.NET Core的身份认证(基于Cookie的身份认证1)
asp.net core·cookie·身份认证
gc_22991 个月前
学习ASP.NET Core的身份认证(基于Cookie的身份认证3)
asp.net core·cookie·身份认证