【.Net Core】过滤器Filter

  • Post author:
  • Post category:其他




使用背景

过滤器有什么作用,在什么场景下适合用到它?

假设一个项目进展到快结束的时候,项目leader为了保证程序的稳定性和可监控和维护性要求将所有的方法加上日志,如果项目比较庞大,方法非常多,那岂不是得费很大得劲来完成这样一件事情。不过不用担心,咋们遇到的问题,伟大的语言设计者早已帮我们想好了解决办法过滤器,过滤器是一种AOP(面向切面编程)技术的体现,AOP具有松耦合,易扩展,代码可复用的特点。

通常我们在这些场景下如 身份验证 、 日志记录 、异常获取 等会使用到过滤器



工作原理

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

在这里插入图片描述



筛选器类型

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


授权筛选器:

  • 首先运行。
  • 确定用户是否获得请求授权。
  • 如果请求未获授权,可以让管道短路。


资源筛选器:

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


操作筛选器:

  • 在调用操作方法之前和之后立即运行。
  • 可以更改传递到操作中的参数。
  • 可以更改从操作返回的结果。
  • 不可在 Razor Pages 中使用。


终结点筛选器:

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


结果筛选器:

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

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

在这里插入图片描述

Razor 页面还支持 Razor 页面筛选器,这些筛选器在页面处理程序之前和之后 Razor 运行。



实现

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

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

public class SampleActionFilter : 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 方法。 例如,OnActionExecutionAsync:

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

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



多个筛选器阶段

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

  • 同步:IActionFilter 和 IResultFilter
  • 异步:IAsyncActionFilter 和 IAsyncResultFilter
  • IOrderedFilter

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



内置筛选器属性

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

public class ResponseHeaderAttribute : ActionFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public ResponseHeaderAttribute(string name, string value) =>
        (_name, _value) = (name, value);

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_name, _value);

        base.OnResultExecuting(context);
    }
}

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

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

使用浏览器开发人员工具等工具来检查标头。 在响应标头下,将显示 filter-header: Filter Value。

以下代码将 ResponseHeaderAttribute 应用于控制器和操作:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{
    public IActionResult Index() =>
        Content("Examine the response headers using the F12 developer tools.");

    // ...

    [ResponseHeader("Another-Filter-Header", "Another Filter Value")]
    public IActionResult Multiple() =>
        Content("Examine the response headers using the F12 developer tools.");
}

Multiple 操作的响应包括以下标头:

  • filter-header: Filter Value
  • another-filter-header: Another Filter Value

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

筛选器属性:

  • ActionFilterAttribute
  • ExceptionFilterAttribute
  • ResultFilterAttribute
  • FormatFilterAttribute
  • ServiceFilterAttribute
  • TypeFilterAttribute

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



筛选器作用域和执行顺序

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

  • 在控制器或 Razor 页面上使用属性。
  • 在控制器操作上使用属性。 无法将筛选器属性应用于 Razor 页面处理程序方法。
  • 所有控制器、操作和 Razor 页面的全局筛选器,如下面的代码所示:
var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalSampleActionFilter>();
});



默认执行顺序

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

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

  • 全局筛选器的 before 代码。

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

      控制器筛选器的 after 代码。
  • 全局筛选器的 after 代码。

下面的示例阐释了为同步操作筛选器运行筛选器方法的顺序:

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



控制器级别筛选器

继承自 Controller 的每个控制器都包括 OnActionExecuting、OnActionExecutionAsync 和 OnActionExecuted 方法。 这些方法可覆盖为给定操作运行的筛选器:

  • OnActionExecuting 在所有操作筛选器之前运行。
  • OnActionExecuted 在所有操作筛选器之后运行。
  • OnActionExecutionAsync 在所有操作筛选器之前运行。 调用 next 后的代码在操作筛选器之后运行。

以下 ControllerFiltersController 类:

  • 将 SampleActionFilterAttribute ([SampleActionFilter]) 应用于控制器。
  • 重写 OnActionExecuting 和 OnActionExecuted。
[SampleActionFilter]
public class ControllerFiltersController : Controller
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuting)}");

        base.OnActionExecuting(context);
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(OnActionExecuted)}");

        base.OnActionExecuted(context);
    }

    public IActionResult Index()
    {
        Console.WriteLine(
            $"- {nameof(ControllerFiltersController)}.{nameof(Index)}");

        return Content("Check the Console.");
    }
}

导航到 https://localhost:/ControllerFilters 运行以下代码:

  • ControllerFiltersController.OnActionExecuting

    • GlobalSampleActionFilter.OnActionExecuting
    • SampleActionFilterAttribute.OnActionExecuting
    • ControllerFiltersController.Index
    • SampleActionFilterAttribute.OnActionExecuted
    • GlobalSampleActionFilter.OnActionExecuted
  • ControllerFiltersController.OnActionExecuted

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



替代默认顺序

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

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

在控制器级别筛选器示例中,GlobalSampleActionFilter 具有全局作用域,因此它在具有控制器作用域的 SampleActionFilterAttribute 之前运行。 若要首先运行 SampleActionFilterAttribute,请将其顺序设置为 int.MinValue:

[SampleActionFilter(Order = int.MinValue)]
public class ControllerFiltersController : Controller
{
    // ...
}

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

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<GlobalSampleActionFilter>(int.MinValue);
});



取消和设置短路

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

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter
{
    public void OnResourceExecuting(ResourceExecutingContext context)
    {
        context.Result = new ContentResult
        {
            Content = nameof(ShortCircuitingResourceFilterAttribute)
        };
    }

    public void OnResourceExecuted(ResourceExecutedContext context) { }
}

在下面的代码中,[ShortCircuitingResourceFilter] 和 [ResponseHeader] 筛选器都以 Index 操作方法为目标。 ShortCircuitingResourceFilterAttribute 筛选器:

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

这样 ResponseHeaderAttribute 筛选器就不会为 Index 操作运行。 如果这两个筛选器都应用于操作方法级别,只要 ShortCircuitingResourceFilterAttribute 先运行,此行为就不会变。 ShortCircuitingResourceFilterAttribute 因其筛选器类型而首先运行:

[ResponseHeader("Filter-Header", "Filter Value")]
public class ShortCircuitingController : Controller
{
    [ShortCircuitingResourceFilter]
    public IActionResult Index() =>
        Content($"- {nameof(ShortCircuitingController)}.{nameof(Index)}");
}



依赖关系注入

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

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

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

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

  • ServiceFilterAttribute
  • TypeFilterAttribute
  • 在属性上实现 IFilterFactory。

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

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

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



ServiceFilterAttribute

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

以下代码显示了使用 DI 的 LoggingResponseHeaderFilterService 类:

public class LoggingResponseHeaderFilterService : IResultFilter
{
    private readonly ILogger _logger;

    public LoggingResponseHeaderFilterService(
            ILogger<LoggingResponseHeaderFilterService> logger) =>
        _logger = logger;

    public void OnResultExecuting(ResultExecutingContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuting)}");

        context.HttpContext.Response.Headers.Add(
            nameof(OnResultExecuting), nameof(LoggingResponseHeaderFilterService));
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuted)}");
    }
}

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

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

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

[ServiceFilter(typeof(LoggingResponseHeaderFilterService))]
public IActionResult WithServiceFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");

使用 ServiceFilterAttribute 时,设置 ServiceFilterAttribute.IsReusable:

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

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

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



TypeFilterAttribute

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

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

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

使用 TypeFilterAttribute 时,设置 TypeFilterAttribute.IsReusable:

  • 提供提示:筛选器实例可能在其创建的请求范围之外被重用。 ASP.NET Core 运行时不保证将创建筛选器的单一实例。
  • 不应与依赖于生命周期不同于单一实例的服务的筛选器一起使用。

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

[TypeFilter(typeof(LoggingResponseHeaderFilter),
    Arguments = new object[] { "Filter-Header", "Filter Value" })]
public IActionResult WithTypeFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithTypeFilter)}");



授权筛选器

授权筛选器:

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

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

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

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

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

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



资源筛选器

资源筛选器:

  • 实现 IResourceFilter 或 IAsyncResourceFilter 接口。
  • 执行会覆盖筛选器管道的绝大部分。
  • 只有授权筛选器在资源筛选器之前运行。

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

资源筛选器示例:

  • 之前显示的短路资源筛选器。

  • DisableFormValueModelBindingAttribute:

    • 可以防止模型绑定访问表单数据。
    • 用于上传大型文件,以防止表单数据被读入内存。



操作筛选器

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

操作筛选器:

  • 实现 IActionFilter 或 IAsyncActionFilter 接口。
  • 它们的执行围绕着操作方法的执行。

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

public class SampleActionFilter : 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 提供 Controller 和 Result 以及以下属性:

  • 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);
        }
    }
}

使用 [ApiController] 属性注释的控制器会自动验证模型状态并返回 400 响应。

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

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

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

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

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



异常筛选器

异常筛选器:

  • 实现 IExceptionFilter 或 IAsyncExceptionFilter。
  • 可用于实现常见的错误处理策略。

下面的异常筛选器示例显示在开发应用时发生的异常的相关详细信息:

public class SampleExceptionFilter : IExceptionFilter
{
    private readonly IHostEnvironment _hostEnvironment;

    public SampleExceptionFilter(IHostEnvironment hostEnvironment) =>
        _hostEnvironment = hostEnvironment;

    public void OnException(ExceptionContext context)
    {
        if (!_hostEnvironment.IsDevelopment())
        {
            // Don't display exception details unless running in Development.
            return;
        }

        context.Result = new ContentResult
        {
            Content = context.Exception.ToString()
        };
    }
}

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

[TypeFilter(typeof(SampleExceptionFilter))]
public class ExceptionController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(ExceptionController)}.{nameof(Index)}");
}

异常筛选器:

  • 没有之前和之后的事件。

  • 实现 OnException 或 OnExceptionAsync。

  • 处理 Razor 页面或控制器创建、模型绑定、操作筛选器或操作方法中发生的未经处理的异常。

  • 请不要捕获资源筛选器、结果筛选器或 MVC 结果执行中发生的异常。

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

异常筛选器:

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

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



结果筛选器

结果筛选器:

  • 实现接口:

    • IResultFilter 或 IAsyncResultFilter
    • IAlwaysRunResultFilter 或 IAsyncAlwaysRunResultFilter
  • 它们的执行围绕着操作结果的执行。



IResultFilter 和 IAsyncResultFilter

以下代码显示示例结果筛选器:

public class SampleResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        // Do something before the result executes.
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Do something after the result executes.
    }
}

要执行的结果类型取决于所执行的操作。 返回视图的操作会将所有 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 SampleAsyncResultFilter : IAsyncResultFilter
{
    public async Task OnResultExecutionAsync(
        ResultExecutingContext context, ResultExecutionDelegate next)
    {
        if (context.Result is not EmptyResult)
        {
            await next();
        }
        else
        {
            context.Cancel = true;
        }
    }
}

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



IAlwaysRunResultFilter 和 IAsyncAlwaysRunResultFilter

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

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

例如,当内容协商失败时,以下筛选器始终运行并设置操作结果 (ObjectResult) 422 Unprocessable Entity 状态代码:

public class UnprocessableResultFilter : IAlwaysRunResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        if (context.Result is StatusCodeResult statusCodeResult
            && statusCodeResult.StatusCode == StatusCodes.Status415UnsupportedMediaType)
        {
            context.Result = new ObjectResult("Unprocessable")
            {
                StatusCode = StatusCodes.Status422UnprocessableEntity
            };
        }
    }

    public void OnResultExecuted(ResultExecutedContext context) { }
}



IFilterFactory

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

IFilterFactory.IsReusable:

  • 是工厂的提示,即工厂创建的筛选器实例可以在其创建时的请求范围之外重用。
  • 不应与依赖于具有除单一实例以外的生命周期的服务的筛选器一起使用。

ASP.NET Core 运行时不保证:

  • 将创建筛选器的单一实例。
  • 稍后不会从 DI 容器重新请求筛选器。

仅当筛选器的来源明确、筛选器是无状态的,且筛选器可以安全地跨多个 HTTP 请求使用时,才将 IFilterFactory.IsReusable 配置为返回 true。 例如,如果 IFilterFactory.IsReusable 返回 true,则不从注册为作用域或瞬态的 DI 返回筛选器。

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

public class ResponseHeaderFilterFactory : Attribute, IFilterFactory
{
    public bool IsReusable => false;

    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) =>
        new InternalResponseHeaderFilter();

    private class InternalResponseHeaderFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context) =>
            context.HttpContext.Response.Headers.Add(
                nameof(OnActionExecuting), nameof(InternalResponseHeaderFilter));

        public void OnActionExecuted(ActionExecutedContext context) { }
    }

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

[ResponseHeaderFilterFactory]
public IActionResult Index() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(Index)}");



在属性上实现 IFilterFactory

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

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

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

public class SampleActionTypeFilterAttribute : TypeFilterAttribute
{
    public SampleActionTypeFilterAttribute()
         : base(typeof(InternalSampleActionFilter)) { }

    private class InternalSampleActionFilter : IActionFilter
    {
        private readonly ILogger<InternalSampleActionFilter> _logger;

        public InternalSampleActionFilter(ILogger<InternalSampleActionFilter> logger) =>
            _logger = logger;

        public void OnActionExecuting(ActionExecutingContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuting)}");
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            _logger.LogInformation(
                $"- {nameof(InternalSampleActionFilter)}.{nameof(OnActionExecuted)}");
        }
    }
}

以下代码显示应用筛选器的三种方法:

[SampleActionTypeFilter]
public IActionResult WithDirectAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithDirectAttribute)}");

[TypeFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithTypeFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");

[ServiceFilter(typeof(SampleActionTypeFilterAttribute))]
public IActionResult WithServiceFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithServiceFilterAttribute)}");

在前面的代码中,首选应用筛选器的第一种方法。



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

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

若要将中间件用作筛选器,可创建一个具有 Configure 方法的类型,该方法可指定要注入到筛选器管道的中间件。 以下示例使用中间件设置响应标头:

public class FilterMiddlewarePipeline
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            context.Response.Headers.Add("Pipeline", "Middleware");

            await next();
        });
    }
}

使用 MiddlewareFilterAttribute 运行中间件:

[MiddlewareFilter(typeof(FilterMiddlewarePipeline))]
public class FilterMiddlewareController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}

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



线程安全

将筛选器的 实例 传递到 Add而不是其 Type中时,筛选器是单一实例, 不是 线程安全的。



对比中间件

一般来说,过滤器用于处理业务与应用程序的横切关注点,用法和功能很像中间件,但过滤器允许你将作用范围缩小,并将其插入到应用程序中有意义的位置,例如视图之前或模型绑定之后。过滤器是 MVC 的一部分,可以访问其上下文和构造函数。例如,中间件很难检测到请求的模型验证是否产生错误,并且做出相应的响应。



来源


.net core学习——过滤器(Filter)



ASP.NET Core MVC 过滤器(Filter)



ASP.NET Core 中的筛选器



版权声明:本文为weixin_44231544原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。