ASP.NET Core 中的筛选器

作者:Kirk LarkinRick AndersonTom DykstraSteve Smith

通过使用 ASP.NET Core 中的筛选器,可在请求处理管道中的特定阶段之前或之后运行代码。

内置筛选器处理任务,例如:

  • 授权(防止用户访问未获授权的资源)。
  • 响应缓存(对请求管道进行短路出路,以便返回缓存的响应)。

可以创建自定义筛选器,用于处理横切关注点。 横切关注点的示例包括错误处理、缓存、配置、授权和日志记录。 筛选器可以避免复制代码。 例如,错误处理异常筛选器可以合并错误处理。

本文档适用于 Razor Pages、API 控制器和具有视图的控制器。 筛选器无法直接与 Razor 组件一起使用。 筛选器只能在以下情况下间接影响组件:

  • 该组件嵌入在页面或视图中。
  • 页面或控制器/视图使用此筛选器。

查看或下载示例如何下载)。

筛选器的工作原理

筛选器在 ASP.NET Core 操作调用管道(有时称为筛选器管道)内运行。 筛选器管道在 ASP.NET Core 选择了要执行的操作之后运行。

请求通过其他中间件、路由中间件、操作选择和操作调用管道进行处理。

筛选器类型

每种筛选器类型都在筛选器管道中的不同阶段执行:

  • 授权筛选器最先运行,用于确定是否已针对请求为用户授权。 如果请求未获授权,授权筛选器可以让管道短路。

  • 资源筛选器

    • 授权后运行。
    • OnResourceExecuting 在筛选器管道的其余阶段之前运行代码。 例如,OnResourceExecuting 在模型绑定之前运行代码。
    • OnResourceExecuted 在管道的其余阶段完成之后运行代码。
  • 操作筛选器

    • 在调用操作方法之前和之后立即运行代码。
    • 可以更改传递到操作中的参数。
    • 可以更改从操作返回的结果。
    • 不可在 Razor Pages 中使用。
  • 异常筛选器在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。

  • 结果筛选器在执行操作结果之前和之后立即运行代码。 仅当操作方法成功执行时,它们才会运行。 对于必须围绕视图或格式化程序的执行的逻辑,它们很有用。

下图展示了筛选器类型在筛选器管道中的交互方式。

请求通过授权过滤器、资源过滤器、模型绑定、操作过滤器、操作执行和操作结果转换、异常过滤器、结果过滤器和结果执行进行处理。

实现

通过不同的接口定义,筛选器同时支持同步和异步实现。

同步筛选器在其管道阶段之前和之后运行代码。 例如,OnActionExecuting 在调用操作方法之前调用。 OnActionExecuted 在操作方法返回之后调用。

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

异步筛选器定义 On-Stage-ExecutionAsync 方法。 例如,OnActionExecutionAsync

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context,
        ActionExecutionDelegate next)
    {
        // Do something before the action executes.

        // next() calls the action method.
        var resultContext = await next();
        // resultContext.Result is set.
        // Do something after the action executes.
    }
}

在前面的代码中,SampleAsyncActionFilter 具有执行操作方法的 ActionExecutionDelegate (next)。

多个筛选器阶段

可以在单个类中实现多个筛选器阶段的接口。 例如,ActionFilterAttribute 类可实现:

筛选器接口的同步和异步版本任意实现一个,而不是同时实现。 运行时会先查看筛选器是否实现了异步接口,如果是,则调用该接口。 如果不是,则调用同步接口的方法。 如果在一个类中同时实现异步和同步接口,则仅调用异步方法。 使用抽象类(如 ActionFilterAttribute)时,将为每种筛选器类型仅重写同步方法或仅重写异步方法。

内置筛选器属性

ASP.NET Core 包含许多可子类化和自定义的基于属性的内置筛选器。 例如,以下结果筛选器会向响应添加标头:

public class AddHeaderAttribute : ResultFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public AddHeaderAttribute(string name, string value)
    {
        _name = name;
        _value = value;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add( _name, new string[] { _value });
        base.OnResultExecuting(context);
    }
}

通过使用属性,筛选器可接收参数,如前面的示例所示。 AddHeaderAttribute 添加到控制器或操作方法,并指定 HTTP 标头的名称和值:

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

使用浏览器开发人员工具等工具来检查标头。 在响应标头下,将显示 author: Rick Anderson

以下代码实现了 ActionFilterAttribute

  • 从配置系统读取标题和名称。 与前面的示例不同,以下代码不需要将筛选器参数添加到代码中。
  • 将标题和名称添加到响应标头。
public class MyActionFilterAttribute : ActionFilterAttribute
{
    private readonly PositionOptions _settings;

    public MyActionFilterAttribute(IOptions<PositionOptions> options)
    {
        _settings = options.Value;
        Order = 1;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_settings.Title, 
                                                 new string[] { _settings.Name });
        base.OnResultExecuting(context);
    }
}

使用选项模式配置系统中提供配置选项。 例如,从 appsettings.json 文件中:

{
    "Position": {
        "Title": "编辑器",
        "Name": "Joe Smith"
    },
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
        }
    },
    "AllowedHosts": "*"
}

StartUp.ConfigureServices 中:

  • PositionOptions 类已通过 "Position" 配置区域添加到服务容器。
  • MyActionFilterAttribute 已添加到服务容器。
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
             Configuration.GetSection("Position"));
    services.AddScoped<MyActionFilterAttribute>();

    services.AddControllersWithViews();
}

以下代码显示 PositionOptions 类:

public class PositionOptions
{
    public string Title { get; set; }
    public string Name { get; set; }
}

以下代码将 MyActionFilterAttribute 应用于 Index2 方法:

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

在响应标头下,author: Rick AndersonEditor: Joe Smith 在调用 Sample/Index2 终结点时显示。

以下代码将 MyActionFilterAttributeAddHeaderAttribute 应用于 Razor 页面:

[AddHeader("Author", "Rick Anderson")]
[ServiceFilter(typeof(MyActionFilterAttribute))]
public class IndexModel : PageModel
{
    public void OnGet()
    {
    }
}

无法将筛选器应用于 Razor 页面处理程序方法。 它们可以应用于 Razor 页面模型或全局应用。

多种筛选器接口具有相应属性,这些属性可用作自定义实现的基类。

筛选器属性:

筛选器作用域和执行顺序

可以将筛选器添加到管道中的三个作用域之一:

  • 在控制器操作上使用属性。 无法将筛选器属性应用于 Razor 页面处理程序方法。
  • 在控制器或 Razor 页面上使用属性。
  • 所有控制器、操作和 Razor 页面的全局筛选器,如下面的代码所示:
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

默认执行顺序

当管道的某个特定阶段有多个筛选器时,作用域可确定筛选器执行的默认顺序。 全局筛选器涵盖类筛选器,类筛选器又涵盖方法筛选器。

在筛选器嵌套模式下,筛选器的 after 代码会按照与 before 代码相反的顺序运行。 筛选器序列:

  • 全局筛选器的 before 代码。
    • 控制器筛选器和 Razor 页面筛选器的 before 代码。
      • 操作方法筛选器的 before 代码。
      • 操作方法筛选器的 after 代码。
    • 控制器筛选器和 Razor 页面筛选器的 after 代码。
  • 全局筛选器的 after 代码。

下面的示例阐释了为同步操作筛选器调用筛选器方法的顺序。

序列 筛选器作用域 筛选器方法
1 Global OnActionExecuting
2 控制器或 Razor 页面 OnActionExecuting
3 方法 OnActionExecuting
4 方法 OnActionExecuted
5 控制器或 Razor 页面 OnActionExecuted
6 Global OnActionExecuted

控制器级别筛选器

继承自 Controller 基类的每个控制器包括 Controller.OnActionExecutingController.OnActionExecutionAsyncController.OnActionExecuted OnActionExecuted 方法。 这些方法:

  • 覆盖为给定操作运行的筛选器。
  • OnActionExecuting 在所有操作筛选器之前调用。
  • OnActionExecuted 在所有操作筛选器之后调用。
  • OnActionExecutionAsync 在所有操作筛选器之前调用。 next 之后的筛选器中的代码在操作方法之后运行。

例如,在下载示例中,启动时全局应用 MySampleActionFilter

TestController

  • SampleActionFilterAttribute ([SampleActionFilter]) 应用于 FilterTest2 操作。
  • 重写 OnActionExecutingOnActionExecuted
public class TestController : Controller
{
    [SampleActionFilter(Order = int.MinValue)]
    public IActionResult FilterTest2()
    {
            var m = MethodBase.GetCurrentMethod();
            MyDebug.Write(m, HttpContext.Request.Path);
            return Content(m.ReflectedType.Name + "." + m.Name);
        }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

导航到 https://localhost:5001/Test2/FilterTest2 运行以下代码:

  • TestController.OnActionExecuting
    • MySampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • TestController.FilterTest2
      • SampleActionFilterAttribute.OnActionExecuted
    • MySampleActionFilter.OnActionExecuted
  • TestController.OnActionExecuted

控制器级别筛选器将 Order 属性设置为 int.MinValue 控制器级别筛选器无法设置为在将筛选器应用于方法之后运行。 在下一节对 Order 进行了介绍。

对于 Razor Pages,请参阅通过重写筛选器方法实现 Razor 页面筛选器

重写默认顺序

可以通过实现 IOrderedFilter 来重写默认执行序列。 IOrderedFilter 公开了 Order 属性来确定执行顺序,该属性优先于作用域。 具有较低的 Order 值的筛选器:

  • 在具有较高的 Order 值的筛选器之前运行 before 代码。
  • 在具有较高的 Order 值的筛选器之后运行 after 代码。

使用构造函数参数设置了 Order 属性:

[SampleActionFilter(Order = int.MinValue)]

请考虑以下控制器中的两个操作筛选器:

[MyAction2Filter]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        var m = MethodBase.GetCurrentMethod();
        MyDebug.Write(m, HttpContext.Request.Path);
        return Content(m.ReflectedType.Name + "." + m.Name);
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

StartUp.ConfigureServices 中添加了全局筛选器:

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

3 个筛选器按下列顺序运行:

  • Test2Controller.OnActionExecuting
    • MySampleActionFilter.OnActionExecuting
      • MyAction2FilterAttribute.OnActionExecuting
        • Test2Controller.FilterTest2
      • MySampleActionFilter.OnActionExecuted
    • MyAction2FilterAttribute.OnResultExecuting
  • Test2Controller.OnActionExecuted

在确定筛选器的运行顺序时,Order 属性重写作用域。 先按顺序对筛选器排序,然后使用作用域消除并列问题。 所有内置筛选器实现 IOrderedFilter 并将默认 Order 值设为 0。 如前所述,控制器级别筛选器将 Order 属性设置为 int.MinValue。对于内置筛选器,作用域会确定顺序,除非将 Order 设为非零值。

在前面的代码中,MySampleActionFilter 具有全局作用域,因此它在具有控制器作用域的 MyAction2FilterAttribute 之前运行。 若要首先运行 MyAction2FilterAttribute,请将顺序设置为 int.MinValue

[MyAction2Filter(int.MinValue)]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        var m = MethodBase.GetCurrentMethod();
        MyDebug.Write(m, HttpContext.Request.Path);
        return Content(m.ReflectedType.Name + "." + m.Name);
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), HttpContext.Request.Path);
        base.OnActionExecuted(context);
    }
}

若要首先运行全局筛选器 MySampleActionFilter,请将 Order 设置为 int.MinValue

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter),
                            int.MinValue);
    });
}

取消和设置短路

通过设置提供给筛选器方法的 ResourceExecutingContext 参数上的 Result 属性,可以使筛选器管道短路。 例如,以下资源筛选器将阻止执行管道的其余阶段:

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult()
        {
            Content = "Resource unavailable - header not set."
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

在下面的代码中,ShortCircuitingResourceFilterAddHeader 筛选器都以 SomeResource 操作方法为目标。 ShortCircuitingResourceFilter

  • 先运行,因为它是资源筛选器且 AddHeader 是操作筛选器。
  • 对管道的其余部分进行短路处理。

这样 AddHeader 筛选器就不会为 SomeResource 操作运行。 如果这两个筛选器都应用于操作方法级别,只要 ShortCircuitingResourceFilter 先运行,此行为就不会变。 先运行 ShortCircuitingResourceFilter(考虑到它的筛选器类型),或显式使用 Order 属性。

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

依赖关系注入

可按类型或实例添加筛选器。 如果添加实例,该实例将用于每个请求。 如果添加类型,则将激活该类型。 激活类型的筛选器意味着:

  • 将为每个请求创建一个实例。
  • 依赖关系注入 (DI) 将填充所有构造函数依赖项。

如果将筛选器作为属性实现并直接添加到控制器类或操作方法中,则该筛选器不能由依赖关系注入 (DI) 提供构造函数依赖项。 无法由 DI 提供构造函数依赖项,因为:

  • 属性在应用时必须提供自己的构造函数参数。
  • 这是属性工作原理上的限制。

以下筛选器支持从 DI 提供的构造函数依赖项:

可以将前面的筛选器应用于控制器或操作方法:

可以从 DI 获取记录器。 但是,避免创建和使用筛选器仅用于日志记录。 内置框架日志记录通常提供日志记录所需的内容。 添加到筛选器的日志记录:

  • 应重点关注业务域问题或特定于筛选器的行为。
  • 不应记录操作或其他框架事件。 内置筛选器记录操作和框架事件。

ServiceFilterAttribute

ConfigureServices 中注册服务筛选器实现类型。 ServiceFilterAttribute 可从 DI 检索筛选器实例。

以下代码显示 AddHeaderResultServiceFilter

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
    }
}

在以下代码中,AddHeaderResultServiceFilter 将添加到 DI 容器中:

public void ConfigureServices(IServiceCollection services)
{
    // Add service filters.
    services.AddScoped<AddHeaderResultServiceFilter>();
    services.AddScoped<SampleActionFilterAttribute>();

    services.AddControllersWithViews(options =>
   {
       options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
           "Result filter added to MvcOptions.Filters"));         // An instance
        options.Filters.Add(typeof(MySampleActionFilter));         // By type
        options.Filters.Add(new SampleGlobalActionFilter());       // An instance
    });
}

在以下代码中,ServiceFilter 属性将从 DI 中检索 AddHeaderResultServiceFilter 筛选器的实例:

[ServiceFilter(typeof(AddHeaderResultServiceFilter))]
public IActionResult Index()
{
    return View();
}

使用 ServiceFilterAttribute 时,ServiceFilterAttribute.IsReusable 设置:

  • 提供以下提示:筛选器实例可能在其创建的请求范围之外被重用。 ASP.NET Core 运行时不保证:

    • 将创建筛选器的单一实例。
    • 稍后不会从 DI 容器重新请求筛选器。
  • 不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。

ServiceFilterAttribute 可实现 IFilterFactory IFilterFactory 公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。 CreateInstance 从 DI 中加载指定的类型。

TypeFilterAttribute

TypeFilterAttributeServiceFilterAttribute 类似,但不会直接从 DI 容器解析其类型。 它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 对类型进行实例化。

因为不会直接从 DI 容器解析 TypeFilterAttribute 类型:

  • 使用 TypeFilterAttribute 引用的类型不需要注册在 DI 容器中。 它们具备由 DI 容器实现的依赖项。
  • TypeFilterAttribute 可以选择为类型接受构造函数参数。

使用 TypeFilterAttribute 时,TypeFilterAttribute.IsReusable 设置:

  • 提供提示:筛选器实例可能在其创建的请求范围之外被重用。 ASP.NET Core 运行时不保证将创建筛选器的单一实例。

  • 不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。

下面的示例演示如何使用 TypeFilterAttribute 将参数传递到类型:

[TypeFilter(typeof(LogConstantFilter),
    Arguments = new object[] { "Method 'Hi' called" })]
public IActionResult Hi(string name)
{
    return Content($"Hi {name}");
}

授权筛选器

授权筛选器:

  • 是筛选器管道中运行的第一个筛选器。
  • 控制对操作方法的访问。
  • 具有在它之前的执行的方法,但没有之后执行的方法。

自定义授权筛选器需要自定义授权框架。 建议配置授权策略或编写自定义授权策略,而不是编写自定义筛选器。 内置授权筛选器:

  • 调用授权系统。
  • 不授权请求。

不会在授权筛选器中引发异常:

  • 不会处理异常。
  • 异常筛选器不会处理异常。

在授权筛选器出现异常时请小心应对。

详细了解授权

资源筛选器

资源筛选器:

如果要使大部分管道短路,资源筛选器会很有用。 例如,如果缓存命中,则缓存筛选器可以绕开管道的其余阶段。

资源筛选器示例:

操作筛选器

操作筛选器不应用于 Razor Pages。 Razor Pages 支持 IPageFilterIAsyncPageFilter 有关详细信息,请参阅 Razor 页面的筛选方法

操作筛选器:

以下代码显示示例操作筛选器:

public class MySampleActionFilter : IActionFilter 
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        MyDebug.Write(MethodBase.GetCurrentMethod(), context.HttpContext.Request.Path);
    }
}

ActionExecutingContext 提供以下属性:

  • ActionArguments - 用于读取操作方法的输入。
  • Controller - 用于处理控制器实例。
  • Result - 设置 Result 会使操作方法和后续操作筛选器的执行短路。

在操作方法中引发异常:

  • 防止运行后续筛选器。
  • 与设置 Result 不同,结果被视为失败而不是成功。

ActionExecutedContext 提供 ControllerResult 以及以下属性:

  • Canceled - 如果操作执行已被另一个筛选器设置短路,则为 true。

  • Exception - 如果操作或之前运行的操作筛选器引发了异常,则为非 NULL 值。 将此属性设置为 null:

    • 有效地处理异常。
    • 执行 Result,从操作方法中将它返回。

对于 IAsyncActionFilter,一个向 ActionExecutionDelegate 的调用可以达到以下目的:

  • 执行所有后续操作筛选器和操作方法。
  • 返回 ActionExecutedContext

若要设置短路,可将 Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result 分配到某个结果实例,并且不调用 next (ActionExecutionDelegate)。

该框架提供一个可子类化的抽象 ActionFilterAttribute

OnActionExecuting 操作筛选器可用于:

  • 验证模型状态。
  • 如果状态无效,则返回错误。
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }

OnActionExecuted 方法在操作方法之后运行:

  • 可通过 Result 属性查看和处理操作结果。

  • 如果操作执行已被另一个筛选器设置短路,则 Canceled 设置为 true。

  • 如果操作或后续操作筛选器引发了异常,则 Exception 设置为非 NULL 值。 Exception 设置为 null:

    • 有效地处理异常。
    • 执行 ActionExecutedContext.Result,从操作方法中将它正常返回。
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext 
                                           context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(
                                                context.ModelState);
        }
    }


    public override void OnActionExecuted(ActionExecutedContext 
                                          context)
    {
        var result = context.Result;
        // Do something with Result.
        if (context.Canceled == true)
        {
            // Action execution was short-circuited by another filter.
        }

        if(context.Exception != null)
        {
            // Exception thrown by action or action filter.
            // Set to null to handle the exception.
            context.Exception = null;
        }
        base.OnActionExecuted(context);
    }
}

异常筛选器

异常筛选器:

下面的异常筛选器示例使用自定义错误视图,显示在开发应用时发生的异常的相关详细信息:

public class CustomExceptionFilter : IExceptionFilter
{
    private readonly IWebHostEnvironment _hostingEnvironment;
    private readonly IModelMetadataProvider _modelMetadataProvider;

    public CustomExceptionFilter(
        IWebHostEnvironment hostingEnvironment,
        IModelMetadataProvider modelMetadataProvider)
    {
        _hostingEnvironment = hostingEnvironment;
        _modelMetadataProvider = modelMetadataProvider;
    }

    public void OnException(ExceptionContext context)
    {
        if (!_hostingEnvironment.IsDevelopment())
        {
            return;
        }
        var result = new ViewResult {ViewName = "CustomError"};
        result.ViewData = new ViewDataDictionary(_modelMetadataProvider,
                                                    context.ModelState);
        result.ViewData.Add("Exception", context.Exception);
        // TODO: Pass additional detailed data via ViewData
        context.Result = result;
    }
}

以下代码测试异常筛选器:

[TypeFilter(typeof(CustomExceptionFilter))]
public class FailingController : Controller
{
    [AddHeader("Failing Controller", 
               "Won't appear when exception is handled")]
    public IActionResult Index()
    {
        throw new Exception("Testing custom exception filter.");
    }
}

异常筛选器:

  • 没有之前和之后的事件。
  • 实现 OnExceptionOnExceptionAsync
  • 处理 Razor 页面或控制器创建、模型绑定、操作筛选器或操作方法中发生的未经处理的异常。
  • 请不要捕获资源筛选器、结果筛选器或 MVC 结果执行中发生的异常。

若要处理异常,请将 ExceptionHandled 属性设置为 true,或编写响应。 这将停止传播异常。 异常筛选器无法将异常转变为“成功”。 只有操作筛选器才能执行该转变。

异常筛选器:

  • 非常适合捕获发生在操作中的异常。
  • 并不像错误处理中间件那么灵活。

建议使用中间件处理异常。 基于所调用的操作方法,仅当错误处理不同时,才使用异常筛选器。 例如,应用可能具有用于 API 终结点和视图/HTML 的操作方法。 API 终结点可能返回 JSON 形式的错误信息,而基于视图的操作可能返回 HTML 形式的错误页。

结果筛选器

结果筛选器:

IResultFilter 和 IAsyncResultFilter

以下代码显示一个添加 HTTP 标头的结果筛选器:

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
        _logger.LogInformation("AddHeaderResultServiceFilter.OnResultExecuted");
    }
}

要执行的结果类型取决于所执行的操作。 返回视图的操作会将所有 Razor 处理作为要执行的 ViewResult 的一部分。 API 方法可能会将某些序列化操作作为结果执行的一部分。 详细了解操作结果

仅当操作或操作筛选器生成操作结果时,才会执行结果筛选器。 不会在以下情况下执行结果筛选器:

  • 授权筛选器或资源筛选器使管道短路。
  • 异常筛选器通过生成操作结果来处理异常。

Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting 方法可以将 Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel 设置为 true,使操作结果和后续结果筛选器的执行短路。 设置短路时写入响应对象,以免生成空响应。 如果在 IResultFilter.OnResultExecuting 中引发异常,则会导致:

  • 阻止操作结果和后续筛选器的执行。
  • 结果被视为失败而不是成功。

Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted 方法运行时,响应可能已发送到客户端。 如果响应已发送到客户端,则无法更改。

如果操作结果执行已被另一个筛选器设置短路,则 ResultExecutedContext.Canceled 设置为 true

如果操作结果或后续结果筛选器引发了异常,则 ResultExecutedContext.Exception 设置为非 NULL 值。 Exception 设置为 NULL 可有效地处理异常,并防止在管道的后续阶段引发该异常。 处理结果筛选器中出现的异常时,没有可靠的方法来将数据写入响应。 如果在操作结果引发异常时标头已刷新到客户端,则没有任何可靠的机制可用于发送失败代码。

对于 IAsyncResultFilter,通过调用 ResultExecutionDelegate 上的 await next 可执行所有后续结果筛选器和操作结果。 若要设置短路,请将 ResultExecutingContext.Cancel 设置为 true,并且不调用 ResultExecutionDelegate

public class MyAsyncResponseFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(ResultExecutingContext context,
                                             ResultExecutionDelegate next)
    {
        if (!(context.Result is EmptyResult))
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }

    }
}

该框架提供一个可子类化的抽象 ResultFilterAttribute 前面所示的 AddHeaderAttribute 类是一种结果筛选器属性。

IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter

IAlwaysRunResultFilterIAsyncAlwaysRunResultFilter 接口声明了一个针对所有操作结果运行的 IResultFilter 实现。 这包括由以下对象生成的操作结果:

  • 设置短路的授权筛选器和资源筛选器。
  • 异常筛选器。

例如,以下筛选器始终运行并在内容协商失败时设置具有“422 无法处理的实体”状态代码的操作结果 (ObjectResult):

public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult &&
            statusCodeResult.StatusCode == 415)
        {
            context.Result = new ObjectResult("Can't process this!")
            {
                StatusCode = 422,
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

IFilterFactory

IFilterFactory 可实现 IFilterMetadata 因此,IFilterFactory 实例可在筛选器管道中的任意位置用作 IFilterMetadata 实例。 当运行时准备调用筛选器时,它会尝试将其转换为 IFilterFactory 如果转换成功,则调用 CreateInstance 方法来创建将调用的 IFilterMetadata 实例。 这提供了一种很灵活的设计,因为无需在应用启动时显式设置精确的筛选器管道。

可以使用自定义属性实现来实现 IFilterFactory 作为另一种创建筛选器的方法:

public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
{
    // Implement IFilterFactory
    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return new InternalAddHeaderFilter();
    }

    private class InternalAddHeaderFilter : IResultFilter
    {
        public void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(
                "Internal", new string[] { "My header" });
        }

        public void OnResultExecuted(ResultExecutedContext context)
        {
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

在以下代码中应用了筛选器:

[AddHeader("Author", "Rick Anderson")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ServiceFilter(typeof(MyActionFilterAttribute))]
    public IActionResult Index2()
    {
        return Content("Header values by configuration.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }

    [AddHeaderWithFactory]
    public IActionResult HeaderWithFactory()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }
}

通过运行下载示例来测试前面的代码:

  • 调用 F12 开发人员工具。
  • 导航到 https://localhost:5001/Sample/HeaderWithFactory

F12 开发人员工具显示示例代码添加的以下响应标头:

  • author: Rick Anderson
  • globaladdheader: Result filter added to MvcOptions.Filters
  • internal: My header

前面的代码创建 internal: My header 响应标头。

在属性上实现 IFilterFactory

实现 IFilterFactory 的筛选器可用于以下筛选器:

  • 不需要传递参数。
  • 具备需要由 DI 填充的构造函数依赖项。

TypeFilterAttribute 可实现 IFilterFactory IFilterFactory 公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。 CreateInstance 从服务容器 (DI) 中加载指定的类型。

public class SampleActionFilterAttribute : TypeFilterAttribute
{
    public SampleActionFilterAttribute()
                         :base(typeof(SampleActionFilterImpl))
    { 
    }

    private class SampleActionFilterImpl : IActionFilter
    {
        private readonly ILogger _logger;
        public SampleActionFilterImpl(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
           _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuting");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation("SampleActionFilterAttribute.OnActionExecuted");
        }
    }
}

以下代码显示应用 [SampleActionFilter] 的三种方法:

[SampleActionFilter]
public IActionResult FilterTest()
{
    return Content("From FilterTest");
}

[TypeFilter(typeof(SampleActionFilterAttribute))]
public IActionResult TypeFilterTest()
{
    return Content("From TypeFilterTest");
}

// ServiceFilter must be registered in ConfigureServices or
// System.InvalidOperationException: No service for type '<filter>'
// has been registered. Is thrown.
[ServiceFilter(typeof(SampleActionFilterAttribute))]
public IActionResult ServiceFilterTest()
{
    return Content("From ServiceFilterTest");
}

在前面的代码中,使用 [SampleActionFilter] 修饰方法是应用 SampleActionFilter 的首选方法。

在筛选器管道中使用中间件

资源筛选器的工作方式与中间件类似,即涵盖管道中的所有后续执行。 但筛选器又不同于中间件,它们是运行时的一部分,这意味着它们有权访问上下文和构造。

若要将中间件用作筛选器,可创建一个具有 Configure 方法的类型,该方法可指定要注入到筛选器管道的中间件。 下面的示例使用本地化中间件为请求建立当前区域性:

public class LocalizationPipeline
{
    public void Configure(IApplicationBuilder applicationBuilder)
    {
        var supportedCultures = new[]
        {
            new CultureInfo("en-US"),
            new CultureInfo("fr")
        };

        var options = new RequestLocalizationOptions
        {
            DefaultRequestCulture = new RequestCulture(
                                       culture: "en-US", 
                                       uiCulture: "en-US"),
            SupportedCultures = supportedCultures,
            SupportedUICultures = supportedCultures
        };
        options.RequestCultureProviders = new[] 
            { new RouteDataRequestCultureProvider() {
                Options = options } };

        applicationBuilder.UseRequestLocalization(options);
    }
}

使用 MiddlewareFilterAttribute 运行中间件:

[Route("{culture}/[controller]/[action]")]
[MiddlewareFilter(typeof(LocalizationPipeline))]
public IActionResult CultureFromRouteData()
{
    return Content(
          $"CurrentCulture:{CultureInfo.CurrentCulture.Name},"
        + $"CurrentUICulture:{CultureInfo.CurrentUICulture.Name}");
}

中间件筛选器与资源筛选器在筛选器管道的相同阶段运行,即,在模型绑定之前以及管道的其余阶段之后。

后续操作

作者:Kirk LarkinRick AndersonTom DykstraSteve Smith

通过使用 ASP.NET Core 中的筛选器,可在请求处理管道中的特定阶段之前或之后运行代码。

内置筛选器处理任务,例如:

  • 授权(防止用户访问未获授权的资源)。
  • 响应缓存(对请求管道进行短路出路,以便返回缓存的响应)。

可以创建自定义筛选器,用于处理横切关注点。 横切关注点的示例包括错误处理、缓存、配置、授权和日志记录。 筛选器可以避免复制代码。 例如,错误处理异常筛选器可以合并错误处理。

本文档适用于 Razor Pages、API 控制器和具有视图的控制器。

查看或下载示例如何下载)。

筛选器的工作原理

筛选器在 ASP.NET Core 操作调用管道(有时称为筛选器管道)内运行。 筛选器管道在 ASP.NET Core 选择了要执行的操作之后运行。

请求通过其他中间件、路由中间件、操作选择和 ASP.NET Core 操作调用管道进行处理。

筛选器类型

每种筛选器类型都在筛选器管道中的不同阶段执行:

  • 授权筛选器最先运行,用于确定是否已针对请求为用户授权。 如果请求未获授权,授权筛选器可以让管道短路。

  • 资源筛选器

    • 授权后运行。
    • OnResourceExecuting 可以在筛选器管道的其余阶段之前运行代码。 例如,OnResourceExecuting 可以在模型绑定之前运行代码。
    • OnResourceExecuted 可以在管道的其余阶段完成之后运行代码。
  • 操作筛选器可以在调用单个操作方法之前和之后立即运行代码。 它们可用于处理传入某个操作的参数以及从该操作返回的结果。 不可在 Razor Pages 中使用操作筛选器。

  • 异常筛选器用于在向响应正文写入任何内容之前,对未经处理的异常应用全局策略。

  • 结果筛选器可以在执行单个操作结果之前和之后立即运行代码。 仅当操作方法成功执行时,它们才会运行。 对于必须围绕视图或格式化程序的执行的逻辑,它们很有用。

下图展示了筛选器类型在筛选器管道中的交互方式。

请求通过授权过滤器、资源过滤器、模型绑定、操作过滤器、操作执行和操作结果转换、异常过滤器、结果过滤器和结果执行进行处理。

实现

通过不同的接口定义,筛选器同时支持同步和异步实现。

同步筛选器可以在其管道阶段之前 (On-Stage-Executing) 和之后 (On-Stage-Executed) 运行代码。 例如,OnActionExecuting 在调用操作方法之前调用。 OnActionExecuted 在操作方法返回之后调用。

public class MySampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

异步筛选器定义 On-Stage-ExecutionAsync 方法:

public class SampleAsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context,
        ActionExecutionDelegate next)
    {
        // Do something before the action executes.

        // next() calls the action method.
        var resultContext = await next();
        // resultContext.Result is set.
        // Do something after the action executes.
    }
}

在前面的代码中,SampleAsyncActionFilter 具有执行操作方法的 ActionExecutionDelegate (next)。 每个 On-Stage-ExecutionAsync 方法采用执行筛选器的管道阶段的 FilterType-ExecutionDelegate

多个筛选器阶段

可以在单个类中实现多个筛选器阶段的接口。 例如,ActionFilterAttribute 类实现 IActionFilterIResultFilter 及其异步等效接口。

筛选器接口的同步和异步版本任意实现一个,而不是同时实现。 运行时会先查看筛选器是否实现了异步接口,如果是,则调用该接口。 如果不是,则调用同步接口的方法。 如果在一个类中同时实现异步和同步接口,则仅调用异步方法。 使用抽象类时(如 ActionFilterAttribute),将为每种筛选器类型仅重写同步方法或仅重写异步方法。

内置筛选器属性

ASP.NET Core 包含许多可子类化和自定义的基于属性的内置筛选器。 例如,以下结果筛选器会向响应添加标头:

public class AddHeaderAttribute : ResultFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public AddHeaderAttribute(string name, string value)
    {
        _name = name;
        _value = value;
    }

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add( _name, new string[] { _value });
        base.OnResultExecuting(context);
    }
}

通过使用属性,筛选器可接收参数,如前面的示例所示。 AddHeaderAttribute 添加到控制器或操作方法,并指定 HTTP 标头的名称和值:

[AddHeader("Author", "Joe Smith")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }

多种筛选器接口具有相应属性,这些属性可用作自定义实现的基类。

筛选器属性:

筛选器作用域和执行顺序

可以将筛选器添加到管道中的三个作用域之一:

  • 在操作上使用属性。
  • 在控制器上使用属性。
  • 所有控制器和操作的全局筛选器,如下面的代码所示:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
            "Result filter added to MvcOptions.Filters"));         // An instance
        options.Filters.Add(typeof(MySampleActionFilter));         // By type
        options.Filters.Add(new SampleGlobalActionFilter());       // An instance
    }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

前面的代码使用 MvcOptions.Filters 集合全局添加三个筛选器。

默认执行顺序

当有同一类型的多个筛选器时,作用域可确定筛选器执行的默认顺序。 全局筛选器涵盖类筛选器。 类筛选器涵盖方法筛选器。

在筛选器嵌套模式下,筛选器的 after 代码会按照与 before 代码相反的顺序运行。 筛选器序列:

  • 全局筛选器的 before 代码。
    • 控制器筛选器的 before 代码。
      • 操作方法筛选器的 before 代码。
      • 操作方法筛选器的 after 代码。
    • 控制器筛选器的 after 代码。
  • 全局筛选器的 after 代码。

下面的示例阐释了为同步操作筛选器调用筛选器方法的顺序。

序列 筛选器作用域 筛选器方法
1 Global OnActionExecuting
2 控制器 OnActionExecuting
3 方法 OnActionExecuting
4 方法 OnActionExecuted
5 控制器 OnActionExecuted
6 Global OnActionExecuted

此序列显示:

  • 方法筛选器已嵌套在控制器筛选器中。
  • 控制器筛选器已嵌套在全局筛选器中。

控制器和 Razor 页面级筛选器

继承自 Controller 基类的每个控制器包括 Controller.OnActionExecutingController.OnActionExecutionAsyncController.OnActionExecuted OnActionExecuted 方法。 这些方法:

  • 覆盖为给定操作运行的筛选器。
  • OnActionExecuting 在所有操作筛选器之前调用。
  • OnActionExecuted 在所有操作筛选器之后调用。
  • OnActionExecutionAsync 在所有操作筛选器之前调用。 next 之后的筛选器中的代码在操作方法之后运行。

例如,在下载示例中,启动时全局应用 MySampleActionFilter

TestController

  • SampleActionFilterAttribute ([SampleActionFilter]) 应用于 FilterTest2 操作。
  • 重写 OnActionExecutingOnActionExecuted
public class TestController : Controller
{
    [SampleActionFilter]
    public IActionResult FilterTest2()
    {
        return Content($"From FilterTest2");
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
        base.OnActionExecuted(context);
    }
}

导航到 https://localhost:5001/Test/FilterTest2 运行以下代码:

  • TestController.OnActionExecuting
    • MySampleActionFilter.OnActionExecuting
      • SampleActionFilterAttribute.OnActionExecuting
        • TestController.FilterTest2
      • SampleActionFilterAttribute.OnActionExecuted
    • MySampleActionFilter.OnActionExecuted
  • TestController.OnActionExecuted

对于 Razor Pages,请参阅通过重写筛选器方法实现 Razor 页面筛选器

重写默认顺序

可以通过实现 IOrderedFilter 来重写默认执行序列。 IOrderedFilter 公开了 Order 属性来确定执行顺序,该属性优先于作用域。 具有较低的 Order 值的筛选器:

  • 在具有较高的 Order 值的筛选器之前运行 before 代码。
  • 在具有较高的 Order 值的筛选器之后运行 after 代码。

可以使用构造函数参数设置 Order 属性:

[MyFilter(Name = "Controller Level Attribute", Order=1)]

请考虑前面示例中所示的 3 个相同操作筛选器。 如果控制器和全局筛选器的 Order 属性分别设置为 1 和 2,则会反转执行顺序。

序列 筛选器作用域 Order 属性 筛选器方法
1 方法 0 OnActionExecuting
2 控制器 1 OnActionExecuting
3 Global 2 OnActionExecuting
4 Global 2 OnActionExecuted
5 控制器 1 OnActionExecuted
6 方法 0 OnActionExecuted

在确定筛选器的运行顺序时,Order 属性重写作用域。 先按顺序对筛选器排序,然后使用作用域消除并列问题。 所有内置筛选器实现 IOrderedFilter 并将默认 Order 值设为 0。 对于内置筛选器,作用域会确定顺序,除非将 Order 设为非零值。

取消和设置短路

通过设置提供给筛选器方法的 ResourceExecutingContext 参数上的 Result 属性,可以使筛选器管道短路。 例如,以下资源筛选器将阻止执行管道的其余阶段:

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult()
        {
            Content = "Resource unavailable - header not set."
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }
}

在下面的代码中,ShortCircuitingResourceFilterAddHeader 筛选器都以 SomeResource 操作方法为目标。 ShortCircuitingResourceFilter

  • 先运行,因为它是资源筛选器且 AddHeader 是操作筛选器。
  • 对管道的其余部分进行短路处理。

这样 AddHeader 筛选器就不会为 SomeResource 操作运行。 如果这两个筛选器都应用于操作方法级别,只要 ShortCircuitingResourceFilter 先运行,此行为就不会变。 先运行 ShortCircuitingResourceFilter(考虑到它的筛选器类型),或显式使用 Order 属性。

[AddHeader("Author", "Joe Smith")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

    [ShortCircuitingResourceFilter]
    public IActionResult SomeResource()
    {
        return Content("Successful access to resource - header is set.");
    }

依赖关系注入

可按类型或实例添加筛选器。 如果添加实例,该实例将用于每个请求。 如果添加类型,则将激活该类型。 激活类型的筛选器意味着:

  • 将为每个请求创建一个实例。
  • 依赖关系注入 (DI) 将填充所有构造函数依赖项。

如果将筛选器作为属性实现并直接添加到控制器类或操作方法中,则该筛选器不能由依赖关系注入 (DI) 提供构造函数依赖项。 无法由 DI 提供构造函数依赖项,因为:

  • 属性在应用时必须提供自己的构造函数参数。
  • 这是属性工作原理上的限制。

以下筛选器支持从 DI 提供的构造函数依赖项:

可以将前面的筛选器应用于控制器或操作方法:

可以从 DI 获取记录器。 但是,避免创建和使用筛选器仅用于日志记录。 内置框架日志记录通常提供日志记录所需的内容。 添加到筛选器的日志记录:

  • 应重点关注业务域问题或特定于筛选器的行为。
  • 不应记录操作或其他框架事件。 内置筛选器记录操作和框架事件。

ServiceFilterAttribute

ConfigureServices 中注册服务筛选器实现类型。 ServiceFilterAttribute 可从 DI 检索筛选器实例。

以下代码显示 AddHeaderResultServiceFilter

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
    }
}

在以下代码中,AddHeaderResultServiceFilter 将添加到 DI 容器中:

public void ConfigureServices(IServiceCollection services)
{
    // Add service filters.
    services.AddScoped<AddHeaderResultServiceFilter>();
    services.AddScoped<SampleActionFilterAttribute>();

    services.AddMvc(options =>
    {
        options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
            "Result filter added to MvcOptions.Filters"));         // An instance
        options.Filters.Add(typeof(MySampleActionFilter));         // By type
        options.Filters.Add(new SampleGlobalActionFilter());       // An instance
    }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

在以下代码中,ServiceFilter 属性将从 DI 中检索 AddHeaderResultServiceFilter 筛选器的实例:

[ServiceFilter(typeof(AddHeaderResultServiceFilter))]
public IActionResult Index()
{
    return View();
}

使用 ServiceFilterAttribute 时,ServiceFilterAttribute.IsReusable 设置:

  • 提供以下提示:筛选器实例可能在其创建的请求范围之外被重用。 ASP.NET Core 运行时不保证:

    • 将创建筛选器的单一实例。
    • 稍后不会从 DI 容器重新请求筛选器。
  • 不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。

ServiceFilterAttribute 可实现 IFilterFactory IFilterFactory 公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。 CreateInstance 从 DI 中加载指定的类型。

TypeFilterAttribute

TypeFilterAttributeServiceFilterAttribute 类似,但不会直接从 DI 容器解析其类型。 它使用 Microsoft.Extensions.DependencyInjection.ObjectFactory 对类型进行实例化。

因为不会直接从 DI 容器解析 TypeFilterAttribute 类型:

  • 使用 TypeFilterAttribute 引用的类型不需要注册在 DI 容器中。 它们具备由 DI 容器实现的依赖项。
  • TypeFilterAttribute 可以选择为类型接受构造函数参数。

使用 TypeFilterAttribute 时,TypeFilterAttribute.IsReusable 设置:

  • 提供提示:筛选器实例可能在其创建的请求范围之外被重用。 ASP.NET Core 运行时不保证将创建筛选器的单一实例。

  • 不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。

下面的示例演示如何使用 TypeFilterAttribute 将参数传递到类型:

[TypeFilter(typeof(LogConstantFilter),
    Arguments = new object[] { "Method 'Hi' called" })]
public IActionResult Hi(string name)
{
    return Content($"Hi {name}");
}
public class LogConstantFilter : IActionFilter
{
    private readonly string _value;
    private readonly ILogger<LogConstantFilter> _logger;

    public LogConstantFilter(string value, ILogger<LogConstantFilter> logger)
    {
        _logger = logger;
        _value = value;
    }

    public void OnActionExecuting(ActionExecutingContext context)
    {
        _logger.LogInformation(_value);
    }

    public void OnActionExecuted(ActionExecutedContext context)
    { }
}

授权筛选器

授权筛选器:

  • 是筛选器管道中运行的第一个筛选器。
  • 控制对操作方法的访问。
  • 具有在它之前的执行的方法,但没有之后执行的方法。

自定义授权筛选器需要自定义授权框架。 建议配置授权策略或编写自定义授权策略,而不是编写自定义筛选器。 内置授权筛选器:

  • 调用授权系统。
  • 不授权请求。

不会在授权筛选器中引发异常:

  • 不会处理异常。
  • 异常筛选器不会处理异常。

在授权筛选器出现异常时请小心应对。

详细了解授权

资源筛选器

资源筛选器:

如果要使大部分管道短路,资源筛选器会很有用。 例如,如果缓存命中,则缓存筛选器可以绕开管道的其余阶段。

资源筛选器示例:

操作筛选器

重要

操作筛选器不应用于 Razor Pages。 Razor Pages 支持 IPageFilterIAsyncPageFilter 有关详细信息,请参阅 Razor 页面的筛选方法

操作筛选器:

以下代码显示示例操作筛选器:

public class MySampleActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

ActionExecutingContext 提供以下属性:

  • ActionArguments - 用于读取操作方法的输入。
  • Controller - 用于处理控制器实例。
  • Result - 设置 Result 会使操作方法和后续操作筛选器的执行短路。

在操作方法中引发异常:

  • 防止运行后续筛选器。
  • 与设置 Result 不同,结果被视为失败而不是成功。

ActionExecutedContext 提供 ControllerResult 以及以下属性:

  • Canceled - 如果操作执行已被另一个筛选器设置短路,则为 true。

  • Exception - 如果操作或之前运行的操作筛选器引发了异常,则为非 NULL 值。 将此属性设置为 null:

    • 有效地处理异常。
    • 执行 Result,从操作方法中将它返回。

对于 IAsyncActionFilter,一个向 ActionExecutionDelegate 的调用可以达到以下目的:

  • 执行所有后续操作筛选器和操作方法。
  • 返回 ActionExecutedContext

若要设置短路,可将 Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext.Result 分配到某个结果实例,并且不调用 next (ActionExecutionDelegate)。

该框架提供一个可子类化的抽象 ActionFilterAttribute

OnActionExecuting 操作筛选器可用于:

  • 验证模型状态。
  • 如果状态无效,则返回错误。
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }

OnActionExecuted 方法在操作方法之后运行:

  • 可通过 Result 属性查看和处理操作结果。

  • 如果操作执行已被另一个筛选器设置短路,则 Canceled 设置为 true。

  • 如果操作或后续操作筛选器引发了异常,则 Exception 设置为非 NULL 值。 Exception 设置为 null:

    • 有效地处理异常。
    • 执行 ActionExecutedContext.Result,从操作方法中将它正常返回。
public class ValidateModelAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.ModelState.IsValid)
        {
            context.Result = new BadRequestObjectResult(context.ModelState);
        }
    }


    public override void OnActionExecuted(ActionExecutedContext context)
    {
        var result = context.Result;
        // Do something with Result.
        if (context.Canceled == true)
        {
            // Action execution was short-circuited by another filter.
        }

        if(context.Exception != null)
        {
            // Exception thrown by action or action filter.
            // Set to null to handle the exception.
            context.Exception = null;
        }
        base.OnActionExecuted(context);
    }
}

异常筛选器

异常筛选器:

下面的异常筛选器示例使用自定义错误视图,显示在开发应用时发生的异常的相关详细信息:

public class CustomExceptionFilter : IExceptionFilter
{
    private readonly IHostingEnvironment _hostingEnvironment;
    private readonly IModelMetadataProvider _modelMetadataProvider;

    public CustomExceptionFilter(
        IHostingEnvironment hostingEnvironment,
        IModelMetadataProvider modelMetadataProvider)
    {
        _hostingEnvironment = hostingEnvironment;
        _modelMetadataProvider = modelMetadataProvider;
    }

    public void OnException(ExceptionContext context)
    {
        if (!_hostingEnvironment.IsDevelopment())
        {
            return;
        }
        var result = new ViewResult {ViewName = "CustomError"};
        result.ViewData = new ViewDataDictionary(_modelMetadataProvider,
                                                    context.ModelState);
        result.ViewData.Add("Exception", context.Exception);
        // TODO: Pass additional detailed data via ViewData
        context.Result = result;
    }
}

异常筛选器:

  • 没有之前和之后的事件。
  • 实现 OnExceptionOnExceptionAsync
  • 处理 Razor 页面或控制器创建、模型绑定、操作筛选器或操作方法中发生的未经处理的异常。
  • 请不要捕获资源筛选器、结果筛选器或 MVC 结果执行中发生的异常。

若要处理异常,请将 ExceptionHandled 属性设置为 true,或编写响应。 这将停止传播异常。 异常筛选器无法将异常转变为“成功”。 只有操作筛选器才能执行该转变。

异常筛选器:

  • 非常适合捕获发生在操作中的异常。
  • 并不像错误处理中间件那么灵活。

建议使用中间件处理异常。 基于所调用的操作方法,仅当错误处理不同时,才使用异常筛选器。 例如,应用可能具有用于 API 终结点和视图/HTML 的操作方法。 API 终结点可能返回 JSON 形式的错误信息,而基于视图的操作可能返回 HTML 形式的错误页。

结果筛选器

结果筛选器:

IResultFilter 和 IAsyncResultFilter

以下代码显示一个添加 HTTP 标头的结果筛选器:

public class AddHeaderResultServiceFilter : IResultFilter
{
    private ILogger _logger;
    public AddHeaderResultServiceFilter(ILoggerFactory loggerFactory)
    {
        _logger = loggerFactory.CreateLogger<AddHeaderResultServiceFilter>();
    }

    public void OnResultExecuting(ResultExecutingContext context)
    {
        var headerName = "OnResultExecuting";
        context.HttpContext.Response.Headers.Add(
            headerName, new string[] { "ResultExecutingSuccessfully" });
        _logger.LogInformation("Header added: {HeaderName}", headerName);
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Can't add to headers here because response has started.
    }
}

要执行的结果类型取决于所执行的操作。 返回视图的操作会将所有 Razor 处理作为要执行的 ViewResult 的一部分。 API 方法可能会将某些序列化操作作为结果执行的一部分。 详细了解操作结果

仅当操作或操作筛选器生成操作结果时,才会执行结果筛选器。 不会在以下情况下执行结果筛选器:

  • 授权筛选器或资源筛选器使管道短路。
  • 异常筛选器通过生成操作结果来处理异常。

Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting 方法可以将 Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel 设置为 true,使操作结果和后续结果筛选器的执行短路。 设置短路时写入响应对象,以免生成空响应。 如果在 IResultFilter.OnResultExecuting 中引发异常,则会导致:

  • 阻止操作结果和后续筛选器的执行。
  • 结果被视为失败而不是成功。

Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted 方法运行时,响应可能已发送到客户端。 如果响应已发送到客户端,则无法再更改。

如果操作结果执行已被另一个筛选器设置短路,则 ResultExecutedContext.Canceled 设置为 true

如果操作结果或后续结果筛选器引发了异常,则 ResultExecutedContext.Exception 设置为非 NULL 值。 Exception 设置为 NULL 可有效地处理异常,并防止 ASP.NET Core 在管道的后续阶段重新引发该异常。 处理结果筛选器中出现的异常时,没有可靠的方法来将数据写入响应。 如果在操作结果引发异常时标头已刷新到客户端,则没有任何可靠的机制可用于发送失败代码。

对于 IAsyncResultFilter,通过调用 ResultExecutionDelegate 上的 await next 可执行所有后续结果筛选器和操作结果。 若要设置短路,请将 ResultExecutingContext.Cancel 设置为 true,并且不调用 ResultExecutionDelegate

public class MyAsyncResponseFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(ResultExecutingContext context,
                                             ResultExecutionDelegate next)
    {
        if (!(context.Result is EmptyResult))
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }

    }
}

该框架提供一个可子类化的抽象 ResultFilterAttribute 前面所示的 AddHeaderAttribute 类是一种结果筛选器属性。

IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter

IAlwaysRunResultFilterIAsyncAlwaysRunResultFilter 接口声明了一个针对所有操作结果运行的 IResultFilter 实现。 这包括由以下对象生成的操作结果:

  • 设置短路的授权筛选器和资源筛选器。
  • 异常筛选器。

例如,以下筛选器始终运行并在内容协商失败时设置具有“422 无法处理的实体”状态代码的操作结果 (ObjectResult):

public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult &&
            statusCodeResult.StatusCode == 415)
        {
            context.Result = new ObjectResult("Can't process this!")
            {
                StatusCode = 422,
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

IFilterFactory

IFilterFactory 可实现 IFilterMetadata 因此,IFilterFactory 实例可在筛选器管道中的任意位置用作 IFilterMetadata 实例。 当运行时准备调用筛选器时,它会尝试将其转换为 IFilterFactory 如果转换成功,则调用 CreateInstance 方法来创建将调用的 IFilterMetadata 实例。 这提供了一种很灵活的设计,因为无需在应用启动时显式设置精确的筛选器管道。

可以使用自定义属性实现来实现 IFilterFactory 作为另一种创建筛选器的方法:

public class AddHeaderWithFactoryAttribute : Attribute, IFilterFactory
{
    // Implement IFilterFactory
    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return new InternalAddHeaderFilter();
    }

    private class InternalAddHeaderFilter : IResultFilter
    {
        public void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(
                "Internal", new string[] { "My header" });
        }

        public void OnResultExecuted(ResultExecutedContext context)
        {
        }
    }

    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

可以通过运行下载示例来测试前面的代码:

  • 调用 F12 开发人员工具。
  • 导航到 https://localhost:5001/Sample/HeaderWithFactory

F12 开发人员工具显示示例代码添加的以下响应标头:

  • author: Joe Smith
  • globaladdheader: Result filter added to MvcOptions.Filters
  • internal: My header

前面的代码创建 internal: My header 响应标头。

在属性上实现 IFilterFactory

实现 IFilterFactory 的筛选器可用于以下筛选器:

  • 不需要传递参数。
  • 具备需要由 DI 填充的构造函数依赖项。

TypeFilterAttribute 可实现 IFilterFactory IFilterFactory 公开用于创建 IFilterMetadata 实例的 CreateInstance 方法。 CreateInstance 从服务容器 (DI) 中加载指定的类型。

public class SampleActionFilterAttribute : TypeFilterAttribute
{
    public SampleActionFilterAttribute():base(typeof(SampleActionFilterImpl))
    {
    }

    private class SampleActionFilterImpl : IActionFilter
    {
        private readonly ILogger _logger;
        public SampleActionFilterImpl(ILoggerFactory loggerFactory)
        {
            _logger = loggerFactory.CreateLogger<SampleActionFilterAttribute>();
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation("Business action starting...");
            // perform some business logic work

        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            // perform some business logic work
            _logger.LogInformation("Business action completed.");
        }
    }
}

以下代码显示应用 [SampleActionFilter] 的三种方法:

[SampleActionFilter]
public IActionResult FilterTest()
{
    return Content($"From FilterTest");
}

[TypeFilter(typeof(SampleActionFilterAttribute))]
public IActionResult TypeFilterTest()
{
    return Content($"From ServiceFilterTest");
}

// ServiceFilter must be registered in ConfigureServices or
// System.InvalidOperationException: No service for type '<filter>' has been registered.
// Is thrown.
[ServiceFilter(typeof(SampleActionFilterAttribute))]
public IActionResult ServiceFilterTest()
{
    return Content($"From ServiceFilterTest");
}

在前面的代码中,使用 [SampleActionFilter] 修饰方法是应用 SampleActionFilter 的首选方法。

在筛选器管道中使用中间件

资源筛选器的工作方式与中间件类似,即涵盖管道中的所有后续执行。 但筛选器又不同于中间件,它们是 ASP.NET Core 运行时的一部分,这意味着它们有权访问 ASP.NET Core 上下文和构造。

若要将中间件用作筛选器,可创建一个具有 Configure 方法的类型,该方法可指定要注入到筛选器管道的中间件。 下面的示例使用本地化中间件为请求建立当前区域性:

public class LocalizationPipeline
{
    public void Configure(IApplicationBuilder applicationBuilder)
    {
        var supportedCultures = new[]
        {
            new CultureInfo("en-US"),
            new CultureInfo("fr")
        };

        var options = new RequestLocalizationOptions
        {

            DefaultRequestCulture = new RequestCulture(culture: "en-US", 
                                                     uiCulture: "en-US"),
            SupportedCultures = supportedCultures,
            SupportedUICultures = supportedCultures
        };
        options.RequestCultureProviders = new[] 
            { new RouteDataRequestCultureProvider() { Options = options } };

        applicationBuilder.UseRequestLocalization(options);
    }
}

使用 MiddlewareFilterAttribute 运行中间件:

[Route("{culture}/[controller]/[action]")]
[MiddlewareFilter(typeof(LocalizationPipeline))]
public IActionResult CultureFromRouteData()
{
    return Content($"CurrentCulture:{CultureInfo.CurrentCulture.Name},"
        + $"CurrentUICulture:{CultureInfo.CurrentUICulture.Name}");
}

中间件筛选器与资源筛选器在筛选器管道的相同阶段运行,即,在模型绑定之前以及管道的其余阶段之后。

后续操作

上一篇:ASP.NET Core 中的区域

下一篇:ASP.NET Core Razor SDK

关注微信小程序
程序员编程王-随时随地学编程

扫描二维码
程序员编程王

扫一扫关注最新编程教程