Фильтры в ASP.NET Core

Авторы: Кирк Ларкин (Kirk Larkin) Рик Андерсон (Rick Anderson), Том Дайкстра (Tom Dykstra) и Стив Смит (Steve Smith)

Фильтры в ASP.NET Core позволяют выполнять код до или после определенных этапов в конвейере обработки запросов.

Встроенные фильтры обрабатывают следующие задачи:

  • Авторизация, предотвращение доступа к ресурсам пользователю не разрешено.
  • Кэширование ответов, короткое замыкание конвейера запроса для возврата кэшированного ответа.

Для обработки сквозной функциональности можно создавать настраиваемые фильтры. Примеры сквозной функциональности включают обработку ошибок, кэширование, конфигурирование, авторизацию и ведение журнала. Фильтры предотвращают дублирование кода. Например, можно объединить обработку ошибок с помощью фильтра исключений обработки ошибок.

Этот документ применяется к Razor страницам, контроллерам API и контроллерам с представлениями. Фильтры не работают непосредственно с Razor компонентами. Фильтр может влиять на компонент только косвенно в таких случаях:

  • Компонент внедряется в страницу или представление.
  • Страница или контроллер и представление используют фильтр.

Как работают фильтры

Фильтры выполняются в конвейере вызова действий ASP.NET Core, который иногда называют конвейером фильтров. Конвейер фильтра выполняется после ASP.NET Core выбирает действие для выполнения:

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Типы фильтров

Фильтр каждого типа выполняется на определенном этапе конвейера фильтров:

  • Фильтры авторизации:

    • Сначала выполните команду.
    • Определите, авторизован ли пользователь для запроса.
    • Короткое замыкание конвейера, если запрос не авторизован.
  • Фильтры ресурсов:

    • Выполняются после авторизации.
    • OnResourceExecuting выполняет код до остальной части конвейера фильтров. Например, OnResourceExecuting выполняет код до привязки модели.
    • OnResourceExecuted выполняет код после завершения остальной части конвейера.
  • Фильтры действий:

    • Запустите сразу до и после вызова метода действия.
    • Могут изменять аргументы, передаваемые в действие.
    • Могут изменять результат, возвращенный действием.
    • Не поддерживаются в Razor Pages.
  • Фильтры конечных точек:

    • Запустите сразу до и после вызова метода действия.
    • Могут изменять аргументы, передаваемые в действие.
    • Могут изменять результат, возвращенный действием.
    • Не поддерживаются в Razor Pages.
    • Можно вызывать как для действий, так и для конечных точек на основе обработчика маршрутов.
  • Фильтры исключений применяют глобальные политики к необработанным исключениям, которые происходят до записи данных в тело ответа.

  • Фильтры результатов:

    • Запустите сразу до и после выполнения результатов действия.
    • Выполняется только в том случае, если метод действия успешно выполняется.
    • Полезны для логики, которая должна окружать представление или выполнение форматирования.

На следующей схеме показано, как типы фильтров взаимодействуют в конвейере фильтров:

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

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 реализует следующие интерфейсы:

Реализуйте синхронный или асинхронный интерфейс фильтра, но не оба варианта. Среда выполнения сначала проверяет, реализует ли фильтр асинхронный интерфейс. Если да, вызывается именно он. В противном случае вызываются методы синхронного интерфейса. Если асинхронный и синхронный интерфейсы реализованы в одном классе, вызывается только асинхронный метод. При использовании абстрактных классов, например 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

Некоторые интерфейсы фильтров имеют соответствующие атрибуты, которые можно использовать как базовые классы для пользовательских реализаций.

Атрибуты фильтров:

Фильтры нельзя применить к Razor методам обработчика страницы. Их можно применять либо к Razor модели страницы, либо глобально.

Области и порядок выполнения фильтров

Фильтр можно добавить в конвейер в одной из трех областей:

  • Использование атрибута на контроллере или Razor странице.
  • С помощью атрибута в действии контроллера. Атрибуты фильтра нельзя применять к Razor методам обработчика Pages.
  • Глобально для всех контроллеров, действий и Razor страниц, как показано в следующем коде:
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

Порядок выполнения по умолчанию

Если для определенного этапа конвейера имеется несколько фильтров, область определяет порядок их выполнения по умолчанию. Глобальные фильтры заключают в себя фильтры классов, которые, в свою очередь, заключают в себя фильтры методов.

В результате такого вложения последующий код фильтров выполняется в порядке, обратном выполнению предшествующего кода. Последовательность фильтров:

  • Предшествующий код глобальных фильтров.
    • Предшествующий код фильтров контроллера.
      • Предшествующий код фильтров методов действий.
      • Последующий код фильтров методов действий.
    • Последующий код фильтров контроллера.
  • Последующий код глобальных фильтров.

В следующем примере показано порядок выполнения методов фильтрации для синхронных фильтров действий:

Sequence Область фильтра Метод фильтра
1 Глобальный OnActionExecuting
2 Контроллер OnActionExecuting
3 Действие OnActionExecuting
4 Действие OnActionExecuted
5 Контроллер OnActionExecuted
6 Глобальный OnActionExecuted

Фильтры на уровне контроллера

Каждый контроллер, наследующий от Controller включает OnActionExecutionAsyncOnActionExecutingметоды и 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:<port>/ControllerFilters для выполнения следующего кода:

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

Фильтры на уровне контроллера задают для свойства Order значение int.MinValue. Их нельзя настроить, чтобы они запускались после применения фильтров к методам. Свойство Order рассматривается в следующем разделе.

Сведения о Razor страницах см. в разделе "Реализация Razor фильтров страницы", переопределяя методы фильтрации.

Переопределение порядка по умолчанию

Чтобы переопределить порядок выполнения по умолчанию, можно реализовать IOrderedFilter. IOrderedFilter предоставляет свойство Order, которое имеет приоритет над областью и определяет порядок выполнения. Фильтр со значением меньше Order:

  • Выполняется перед кодом, выполняемым до фильтра с более высоким значением Order.
  • Выполняется после кода, выполняемого после фильтра с более высоким значением Order.

В примере фильтров уровня контроллера есть глобальные область, поэтому он выполняется до SampleActionFilterAttributeтого, GlobalSampleActionFilter как он область контроллера. Чтобы выполнить SampleActionFilterAttribute первую команду, задайте для нее значение int.MinValue:

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

Чтобы выполнить первый запуск глобального фильтра GlobalSampleActionFilter , задайте для нее значение Orderint.MinValue:

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

Отмена и сокращенное выполнение

Вы можете настроить сокращенное выполнение конвейера фильтров, задав свойство Result для параметра ResourceExecutingContext, передаваемого в метод фильтра. Например, следующий фильтр ресурсов предотвращает выполнение остальной части конвейера:

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, так как атрибуты должны иметь свои параметры конструктора, предоставленные им, где они применяются.

Следующие фильтры поддерживают зависимости конструктора, предоставленные путем внедрения зависимостей:

Предыдущие фильтры можно применить к контроллеру или действию.

Средства ведения журнала доступны путем внедрения зависимостей. Но следует избегать создания и использования фильтров исключительно для целей ведения журнала. Встроенное средство ведения журнала обычно предоставляет все необходимое для ведения журнала. При добавлении ведения журналов в фильтры следует учитывать следующее:

  • Следует сосредоточиться на бизнес-среде или поведении в привязке к фильтру.
  • Не следует регистрировать действия или другие события платформы, Встроенные фильтры уже регистрируют действия и события платформы.

ServiceFilterAttribute

Типы реализации фильтра службы регистрируются в Program.cs. Атрибут ServiceFilterAttribute извлекает экземпляр фильтра из внедрения зависимостей.

В следующем коде LoggingResponseHeaderFilterService показан класс, который использует DI:

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:

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

В следующем коде атрибут ServiceFilter извлекает экземпляр фильтра LoggingResponseHeaderFilterService из внедрения зависимостей:

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

При использовании ServiceFilterAttribute, параметр ServiceFilterAttribute.IsReusable:

  • Указывает, что экземпляр фильтра можно многократно использовать за пределами области запроса, в которой он был создан. Среда выполнения ASP.NET Core не гарантирует:
    • что будет создан хотя бы один экземпляр фильтра;
    • что фильтр не будет повторно запрошен из контейнера внедрения зависимостей на более позднем этапе.
  • Не следует использовать с фильтром, который зависит от служб с временем существования, отличного от одноэлементного.

ServiceFilterAttribute реализует IFilterFactory. IFilterFactory предоставляет метод CreateInstance для создания экземпляра IFilterMetadata. CreateInstance загружает указанный тип из внедрения зависимостей.

TypeFilterAttribute

Атрибут TypeFilterAttribute похож на ServiceFilterAttribute, но его тип не разрешается напрямую из контейнера внедрения зависимостей. Он создает экземпляр типа с помощью Microsoft.Extensions.DependencyInjection.ObjectFactory.

Так как типы TypeFilterAttribute не разрешаются напрямую из контейнера внедрения зависимостей:

  • Типы, на которые ссылаются с помощью TypeFilterAttribute, не нужно регистрировать в контейнере внедрения зависимостей. Их зависимости выполняются самим контейнером.
  • Атрибут TypeFilterAttribute может также принимать аргументы конструктора для типа.

При использовании TypeFilterAttribute, параметр TypeFilterAttribute.IsReusable:

  • Указывает, что экземпляр фильтра можно многократно использовать за пределами области запроса, в которой он был создан. Среда выполнения ASP.NET Core не предоставляет никаких гарантий, что будет создан хотя бы один экземпляр фильтра.

  • Не следует использовать с фильтром, который зависит от служб со временем существования, кроме элементов singleton.

В следующем примере показано, как передавать аргументы в тип с помощью TypeFilterAttribute:

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

Фильтры авторизации

Фильтры авторизации:

  • Выполняются в первую очередь в конвейере фильтров.
  • Контролируют доступ к методам действий.
  • Имеют предшествующий, но не последующий метод.

Для работы пользовательских фильтров авторизации требуется настраиваемая платформа авторизации. Настройка политик авторизации или определение пользовательской политики авторизации предпочтительнее создания пользовательского фильтра. Встроенный фильтр авторизации:

  • Вызывает систему авторизации.
  • Не выполняет авторизацию запросов.

Не вызывайте исключения в фильтрах авторизации:

  • Исключение не будет обработано.
  • Фильтры исключений не будут обрабатывать исключение.

При возникновении исключения в фильтре авторизации попробуйте создать запрос.

Дополнительные сведения об авторизации.

Фильтры ресурсов

Фильтры ресурсов:

Фильтры ресурсов полезны для сокращенного выполнения большей части конвейера. Например, фильтр кэширования предотвращает выполнение остальной части конвейера при попадании в кэше.

Примеры фильтров ресурсов:

Фильтры действий

Фильтры действий не применяются к Razor Страницам. Razor Страницы поддерживают 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:
    • Будет эффективно обрабатываться исключение.
    • 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. Дополнительные сведения см. в разделе Автоматические отклики HTTP 400.

Метод OnActionExecuted выполняется после метода действия:

  • Он имеет доступ к результатам действия и может управлять ими с помощью свойства Result.
  • Canceled будет иметь значение true, если выполнение действия было сокращено другим фильтром.
  • Exception будет иметь ненулевое значение, если действие или последующий фильтр действий вызвали исключение. Значение 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<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

В следующем коде показан пример фильтра результатов:

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 будет иметь ненулевое значение, если результат действия или последующий фильтр результатов вызвали исключение. Присвоение Exception значения NULL приводит к обработке исключения и предотвращает его последующий вызов на дальнейших этапах конвейера. Надежный способ записи данных в ответ при обработке исключения в фильтре результатов отсутствует. Если результат действия вызывает исключение в процессе выполнения и заголовки уже были переданы в клиент, надежного механизма отправки кода сбоя не существует.

Для IAsyncResultFilter вызов к await next для ResultExecutionDelegate приводит к выполнению последующих фильтров результатов и результата действия. Чтобы получить короткий канал, установите 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 не гарантирует:

  • что будет создан хотя бы один экземпляр фильтра;
  • что фильтр не будет повторно запрошен из контейнера внедрения зависимостей на более позднем этапе.

Предупреждение

Только настроить IFilterFactory.IsReusable возврат, true если источник фильтров является однозначной, фильтры без отслеживания состояния, а фильтры безопасны для нескольких HTTP-запросов. Например, не возвращайте фильтры из di, зарегистрированные как область или временные, если IFilterFactory.IsReusable возвращаетсяtrue.

Еще один подход к созданию фильтров заключается в реализации 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, используются для фильтров, которые:

  • Не требуют передачи параметров.
  • Содержат зависимости конструктора, которые должны заполняться путем внедрения зависимостей.

TypeFilterAttribute реализует IFilterFactory. IFilterFactory предоставляет метод CreateInstance для создания экземпляра IFilterMetadata. CreateInstance загружает указанный тип из контейнера служб (внедрение зависимостей).

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<SampleActionTypeFilterAttribute>]
public IActionResult WithTypeFilterAttribute() =>
    Content($"- {nameof(FilterFactoryController)}.{nameof(WithTypeFilterAttribute)}");

[ServiceFilter<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<FilterMiddlewarePipeline>]
public class FilterMiddlewareController : Controller
{
    public IActionResult Index() =>
        Content($"- {nameof(FilterMiddlewareController)}.{nameof(Index)}");
}

Фильтры ПО промежуточного слоя выполняются на том же этапе конвейера фильтров, что и фильтры ресурсов, перед привязкой модели и после остальной части конвейера.

Потокобезопасность

При передаче экземпляра фильтра Addв , а не его Type, фильтр является одним и не является потокобезопасным.

Дополнительные ресурсы

Авторы: Кирк Ларкин (Kirk Larkin) Рик Андерсон (Rick Anderson), Том Дайкстра (Tom Dykstra) и Стив Смит (Steve Smith)

Фильтры в ASP.NET Core позволяют выполнять код до или после определенных этапов в конвейере обработки запросов.

Встроенные фильтры обрабатывают следующие задачи:

  • Авторизация, предотвращение доступа к ресурсам пользователю не разрешено.
  • Кэширование ответов, короткое замыкание конвейера запроса для возврата кэшированного ответа.

Для обработки сквозной функциональности можно создавать настраиваемые фильтры. Примеры сквозной функциональности включают обработку ошибок, кэширование, конфигурирование, авторизацию и ведение журнала. Фильтры предотвращают дублирование кода. Например, можно объединить обработку ошибок с помощью фильтра исключений обработки ошибок.

Этот документ применяется к Razor страницам, контроллерам API и контроллерам с представлениями. Фильтры не работают непосредственно с Razor компонентами. Фильтр может влиять на компонент только косвенно в таких случаях:

  • Компонент внедряется в страницу или представление.
  • Страница или контроллер и представление используют фильтр.

Как работают фильтры

Фильтры выполняются в конвейере вызова действий ASP.NET Core, который иногда называют конвейером фильтров. Конвейер фильтра выполняется после ASP.NET Core выбирает действие для выполнения:

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Типы фильтров

Фильтр каждого типа выполняется на определенном этапе конвейера фильтров:

  • Фильтры авторизации:

    • Сначала выполните команду.
    • Определите, авторизован ли пользователь для запроса.
    • Короткое замыкание конвейера, если запрос не авторизован.
  • Фильтры ресурсов:

    • Выполняются после авторизации.
    • OnResourceExecuting выполняет код до остальной части конвейера фильтров. Например, OnResourceExecuting выполняет код до привязки модели.
    • OnResourceExecuted выполняет код после завершения остальной части конвейера.
  • Фильтры действий:

    • Запустите сразу до и после вызова метода действия.
    • Могут изменять аргументы, передаваемые в действие.
    • Могут изменять результат, возвращенный действием.
    • Не поддерживаются в Razor Pages.
  • Фильтры конечных точек:

    • Запустите сразу до и после вызова метода действия.
    • Могут изменять аргументы, передаваемые в действие.
    • Могут изменять результат, возвращенный действием.
    • Не поддерживаются в Razor Pages.
    • Можно вызывать как для действий, так и для конечных точек на основе обработчика маршрутов.
  • Фильтры исключений применяют глобальные политики к необработанным исключениям, которые происходят до записи данных в тело ответа.

  • Фильтры результатов:

    • Запустите сразу до и после выполнения результатов действия.
    • Выполняется только в том случае, если метод действия успешно выполняется.
    • Полезны для логики, которая должна окружать представление или выполнение форматирования.

На следующей схеме показано, как типы фильтров взаимодействуют в конвейере фильтров:

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

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 реализует следующие интерфейсы:

Реализуйте синхронный или асинхронный интерфейс фильтра, но не оба варианта. Среда выполнения сначала проверяет, реализует ли фильтр асинхронный интерфейс. Если да, вызывается именно он. В противном случае вызываются методы синхронного интерфейса. Если асинхронный и синхронный интерфейсы реализованы в одном классе, вызывается только асинхронный метод. При использовании абстрактных классов, например 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

Некоторые интерфейсы фильтров имеют соответствующие атрибуты, которые можно использовать как базовые классы для пользовательских реализаций.

Атрибуты фильтров:

Фильтры нельзя применить к Razor методам обработчика страницы. Их можно применять либо к Razor модели страницы, либо глобально.

Области и порядок выполнения фильтров

Фильтр можно добавить в конвейер в одной из трех областей:

  • Использование атрибута на контроллере или Razor странице.
  • С помощью атрибута в действии контроллера. Атрибуты фильтра нельзя применять к Razor методам обработчика Pages.
  • Глобально для всех контроллеров, действий и Razor страниц, как показано в следующем коде:
    var builder = WebApplication.CreateBuilder(args);
    
    // Add services to the container.
    builder.Services.AddControllersWithViews(options =>
    {
        options.Filters.Add<GlobalSampleActionFilter>();
    });
    

Порядок выполнения по умолчанию

Если для определенного этапа конвейера имеется несколько фильтров, область определяет порядок их выполнения по умолчанию. Глобальные фильтры заключают в себя фильтры классов, которые, в свою очередь, заключают в себя фильтры методов.

В результате такого вложения последующий код фильтров выполняется в порядке, обратном выполнению предшествующего кода. Последовательность фильтров:

  • Предшествующий код глобальных фильтров.
    • Предшествующий код фильтров контроллера.
      • Предшествующий код фильтров методов действий.
      • Последующий код фильтров методов действий.
    • Последующий код фильтров контроллера.
  • Последующий код глобальных фильтров.

В следующем примере показано порядок выполнения методов фильтрации для синхронных фильтров действий:

Sequence Область фильтра Метод фильтра
1 Глобальный OnActionExecuting
2 Контроллер OnActionExecuting
3 Действие OnActionExecuting
4 Действие OnActionExecuted
5 Контроллер OnActionExecuted
6 Глобальный OnActionExecuted

Фильтры на уровне контроллера

Каждый контроллер, наследующий от Controller включает OnActionExecutionAsyncOnActionExecutingметоды и 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:<port>/ControllerFilters для выполнения следующего кода:

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

Фильтры на уровне контроллера задают для свойства Order значение int.MinValue. Их нельзя настроить, чтобы они запускались после применения фильтров к методам. Свойство Order рассматривается в следующем разделе.

Сведения о Razor страницах см. в разделе "Реализация Razor фильтров страницы", переопределяя методы фильтрации.

Переопределение порядка по умолчанию

Чтобы переопределить порядок выполнения по умолчанию, можно реализовать IOrderedFilter. IOrderedFilter предоставляет свойство Order, которое имеет приоритет над областью и определяет порядок выполнения. Фильтр со значением меньше Order:

  • Выполняется перед кодом, выполняемым до фильтра с более высоким значением Order.
  • Выполняется после кода, выполняемого после фильтра с более высоким значением Order.

В примере фильтров уровня контроллера есть глобальные область, поэтому он выполняется до SampleActionFilterAttributeтого, GlobalSampleActionFilter как он область контроллера. Чтобы выполнить SampleActionFilterAttribute первую команду, задайте для нее значение int.MinValue:

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

Чтобы выполнить первый запуск глобального фильтра GlobalSampleActionFilter , задайте для нее значение Orderint.MinValue:

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

Отмена и сокращенное выполнение

Вы можете настроить сокращенное выполнение конвейера фильтров, задав свойство Result для параметра ResourceExecutingContext, передаваемого в метод фильтра. Например, следующий фильтр ресурсов предотвращает выполнение остальной части конвейера:

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, так как атрибуты должны иметь свои параметры конструктора, предоставленные им, где они применяются.

Следующие фильтры поддерживают зависимости конструктора, предоставленные путем внедрения зависимостей:

Предыдущие фильтры можно применить к контроллеру или действию.

Средства ведения журнала доступны путем внедрения зависимостей. Но следует избегать создания и использования фильтров исключительно для целей ведения журнала. Встроенное средство ведения журнала обычно предоставляет все необходимое для ведения журнала. При добавлении ведения журналов в фильтры следует учитывать следующее:

  • Следует сосредоточиться на бизнес-среде или поведении в привязке к фильтру.
  • Не следует регистрировать действия или другие события платформы, Встроенные фильтры уже регистрируют действия и события платформы.

ServiceFilterAttribute

Типы реализации фильтра службы регистрируются в Program.cs. Атрибут ServiceFilterAttribute извлекает экземпляр фильтра из внедрения зависимостей.

В следующем коде LoggingResponseHeaderFilterService показан класс, который использует DI:

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:

builder.Services.AddScoped<LoggingResponseHeaderFilterService>();

В следующем коде атрибут ServiceFilter извлекает экземпляр фильтра LoggingResponseHeaderFilterService из внедрения зависимостей:

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

При использовании ServiceFilterAttribute, параметр ServiceFilterAttribute.IsReusable:

  • Указывает, что экземпляр фильтра можно многократно использовать за пределами области запроса, в которой он был создан. Среда выполнения ASP.NET Core не гарантирует:
    • что будет создан хотя бы один экземпляр фильтра;
    • что фильтр не будет повторно запрошен из контейнера внедрения зависимостей на более позднем этапе.
  • Не следует использовать с фильтром, который зависит от служб с временем существования, отличного от одноэлементного.

ServiceFilterAttribute реализует IFilterFactory. IFilterFactory предоставляет метод CreateInstance для создания экземпляра IFilterMetadata. CreateInstance загружает указанный тип из внедрения зависимостей.

TypeFilterAttribute

Атрибут TypeFilterAttribute похож на ServiceFilterAttribute, но его тип не разрешается напрямую из контейнера внедрения зависимостей. Он создает экземпляр типа с помощью Microsoft.Extensions.DependencyInjection.ObjectFactory.

Так как типы TypeFilterAttribute не разрешаются напрямую из контейнера внедрения зависимостей:

  • Типы, на которые ссылаются с помощью TypeFilterAttribute, не нужно регистрировать в контейнере внедрения зависимостей. Их зависимости выполняются самим контейнером.
  • Атрибут TypeFilterAttribute может также принимать аргументы конструктора для типа.

При использовании TypeFilterAttribute, параметр TypeFilterAttribute.IsReusable:

  • Указывает, что экземпляр фильтра можно многократно использовать за пределами области запроса, в которой он был создан. Среда выполнения ASP.NET Core не предоставляет никаких гарантий, что будет создан хотя бы один экземпляр фильтра.

  • Не следует использовать с фильтром, который зависит от служб со временем существования, кроме элементов singleton.

В следующем примере показано, как передавать аргументы в тип с помощью TypeFilterAttribute:

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

Фильтры авторизации

Фильтры авторизации:

  • Выполняются в первую очередь в конвейере фильтров.
  • Контролируют доступ к методам действий.
  • Имеют предшествующий, но не последующий метод.

Для работы пользовательских фильтров авторизации требуется настраиваемая платформа авторизации. Настройка политик авторизации или определение пользовательской политики авторизации предпочтительнее создания пользовательского фильтра. Встроенный фильтр авторизации:

  • Вызывает систему авторизации.
  • Не выполняет авторизацию запросов.

Не вызывайте исключения в фильтрах авторизации:

  • Исключение не будет обработано.
  • Фильтры исключений не будут обрабатывать исключение.

При возникновении исключения в фильтре авторизации попробуйте создать запрос.

Дополнительные сведения об авторизации.

Фильтры ресурсов

Фильтры ресурсов:

Фильтры ресурсов полезны для сокращенного выполнения большей части конвейера. Например, фильтр кэширования предотвращает выполнение остальной части конвейера при попадании в кэше.

Примеры фильтров ресурсов:

Фильтры действий

Фильтры действий не применяются к Razor Страницам. Razor Страницы поддерживают 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:
    • Будет эффективно обрабатываться исключение.
    • 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. Дополнительные сведения см. в разделе Автоматические отклики HTTP 400.

Метод OnActionExecuted выполняется после метода действия:

  • Он имеет доступ к результатам действия и может управлять ими с помощью свойства Result.
  • Canceled будет иметь значение true, если выполнение действия было сокращено другим фильтром.
  • Exception будет иметь ненулевое значение, если действие или последующий фильтр действий вызвали исключение. Значение 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

В следующем коде показан пример фильтра результатов:

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 будет иметь ненулевое значение, если результат действия или последующий фильтр результатов вызвали исключение. Присвоение Exception значения NULL приводит к обработке исключения и предотвращает его последующий вызов на дальнейших этапах конвейера. Надежный способ записи данных в ответ при обработке исключения в фильтре результатов отсутствует. Если результат действия вызывает исключение в процессе выполнения и заголовки уже были переданы в клиент, надежного механизма отправки кода сбоя не существует.

Для IAsyncResultFilter вызов к await next для ResultExecutionDelegate приводит к выполнению последующих фильтров результатов и результата действия. Чтобы получить короткий канал, установите 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 не гарантирует:

  • что будет создан хотя бы один экземпляр фильтра;
  • что фильтр не будет повторно запрошен из контейнера внедрения зависимостей на более позднем этапе.

Предупреждение

Только настроить IFilterFactory.IsReusable возврат, true если источник фильтров является однозначной, фильтры без отслеживания состояния, а фильтры безопасны для нескольких HTTP-запросов. Например, не возвращайте фильтры из di, зарегистрированные как область или временные, если IFilterFactory.IsReusable возвращаетсяtrue.

Еще один подход к созданию фильтров заключается в реализации 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, используются для фильтров, которые:

  • Не требуют передачи параметров.
  • Содержат зависимости конструктора, которые должны заполняться путем внедрения зависимостей.

TypeFilterAttribute реализует IFilterFactory. IFilterFactory предоставляет метод CreateInstance для создания экземпляра IFilterMetadata. CreateInstance загружает указанный тип из контейнера служб (внедрение зависимостей).

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, фильтр является одним и не является потокобезопасным.

Дополнительные ресурсы

Авторы: Кирк Ларкин (Kirk Larkin) Рик Андерсон (Rick Anderson), Том Дайкстра (Tom Dykstra) и Стив Смит (Steve Smith)

Фильтры в ASP.NET Core позволяют выполнять код до или после определенных этапов в конвейере обработки запросов.

Встроенные фильтры обрабатывают следующие задачи:

  • Авторизация, предотвращение доступа к ресурсам пользователю не разрешено.
  • Кэширование ответов, короткое замыкание конвейера запроса для возврата кэшированного ответа.

Для обработки сквозной функциональности можно создавать настраиваемые фильтры. Примеры сквозной функциональности включают обработку ошибок, кэширование, конфигурирование, авторизацию и ведение журнала. Фильтры предотвращают дублирование кода. Например, можно объединить обработку ошибок с помощью фильтра исключений обработки ошибок.

Этот документ применяется к Razor страницам, контроллерам API и контроллерам с представлениями. Фильтры не работают непосредственно с Razor компонентами. Фильтр может влиять на компонент только косвенно в таких случаях:

  • Компонент внедряется в страницу или представление.
  • Страница или контроллер и представление используют фильтр.

Просмотреть или скачать пример (как скачивать).

Как работают фильтры

Фильтры выполняются в конвейере вызова действий ASP.NET Core, который иногда называют конвейером фильтров. Конвейер фильтров запускается после того, как платформа ASP.NET Core выбирает выполняемое действие.

The request is processed through Other Middleware, Routing Middleware, Action Selection, and the Action Invocation Pipeline. The request processing continues back through Action Selection, Routing Middleware, and various Other Middleware before becoming a response sent to the client.

Типы фильтров

Фильтр каждого типа выполняется на определенном этапе конвейера фильтров:

  • Фильтры авторизации. Применяются в первую очередь и служат для определения того, авторизован ли пользователь для выполнения запроса. Эти фильтры могут сократить выполнение конвейера, если запрос не авторизован.

  • Фильтры ресурсов:

    • Выполняются после авторизации.
    • OnResourceExecuting выполняет код до остальной части конвейера фильтров. Например, OnResourceExecuting выполняет код до привязки модели.
    • OnResourceExecuted выполняет код после завершения остальной части конвейера.
  • Фильтры действий:

    • выполняют код непосредственно до и после вызова метода действия.
    • Могут изменять аргументы, передаваемые в действие.
    • Могут изменять результат, возвращенный действием.
    • Не поддерживаются в Razor Pages.
  • Фильтры исключений применяют глобальные политики к необработанным исключениям, которые происходят до записи данных в тело ответа.

  • Фильтры результатов выполняют код непосредственно до и после выполнения результатов действия. Они выполняются только в том случае, если метод действия выполнен успешно. Они полезны для логики, которая должна включать исключения для представлений или модуля форматирования.

На приведенной ниже схеме показано, как типы фильтров взаимодействуют друг с другом в конвейере фильтров.

The request is processed through Authorization Filters, Resource Filters, Model Binding, Action Filters, Action Execution and Action Result Conversion, Exception Filters, Result Filters, and Result Execution. On the way out, the request is only processed by Result Filters and Resource Filters before becoming a response sent to the client.

Внедрение

Фильтры поддерживают как синхронные, так и асинхронные реализации посредством различных определений интерфейсов.

Синхронные фильтры выполняют код до и после этапа конвейера. Например, 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);
    }
}

В приведенном выше коде MyDebug — это служебная функция в примере скачивания.

Асинхронные фильтры определяют метод 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": "Editor",
    "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.");
    }

В заголовке ответа при вызове конечной точки Sample/Index2 отображается author: Rick Anderson и Editor: Joe Smith.

Следующий код применяется MyActionFilterAttribute к странице и AddHeaderAttribute к ней Razor :

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

Фильтры нельзя применить к Razor методам обработчика страницы. Их можно применять либо к Razor модели страницы, либо глобально.

Некоторые интерфейсы фильтров имеют соответствующие атрибуты, которые можно использовать как базовые классы для пользовательских реализаций.

Атрибуты фильтров:

Области и порядок выполнения фильтров

Фильтр можно добавить в конвейер в одной из трех областей:

  • С помощью атрибута в действии контроллера. Атрибуты фильтра нельзя применять к Razor методам обработчика Pages.
  • Использование атрибута на контроллере или Razor странице.
  • Глобально для всех контроллеров, действий и Razor страниц, как показано в следующем коде:
public void ConfigureServices(IServiceCollection services)
{
    services.AddControllersWithViews(options =>
   {
        options.Filters.Add(typeof(MySampleActionFilter));
    });
}

Порядок выполнения по умолчанию

Если для определенного этапа конвейера имеется несколько фильтров, область определяет порядок их выполнения по умолчанию. Глобальные фильтры заключают в себя фильтры классов, которые, в свою очередь, заключают в себя фильтры методов.

В результате такого вложения последующий код фильтров выполняется в порядке, обратном выполнению предшествующего кода. Последовательность фильтров:

  • Предшествующий код глобальных фильтров.
    • Перед кодом контроллера и Razor фильтров страниц.
      • Предшествующий код фильтров методов действий.
      • Последующий код фильтров методов действий.
    • После кода фильтров контроллера и Razor страницы.
  • Последующий код глобальных фильтров.

В следующем примере показан порядок вызова методов фильтров для синхронных фильтров действий.

Sequence Область фильтра Метод фильтра
1 Глобальный OnActionExecuting
2 Контроллер или Razor страница OnActionExecuting
3 Метод OnActionExecuting
4 Метод OnActionExecuted
5 Контроллер или Razor страница OnActionExecuted
6 Глобальный OnActionExecuted

Фильтры на уровне контроллера

Каждый контроллер, наследующий от Controller базового класса, Controller.OnActionExecutionAsyncвключает Controller.OnActionExecutingи Controller.OnActionExecutedOnActionExecuted методы. Эти методы:

  • Заключают фильтры, которые выполняются для указанного действия.
  • OnActionExecuting вызывается перед всеми фильтрами действий.
  • OnActionExecuted вызывается после всех фильтров действий.
  • OnActionExecutionAsync вызывается перед всеми фильтрами действий. Код в фильтре после next выполняется после метода действия.

Например, в скачиваемом примере MySampleActionFilter применяется глобально при запуске.

TestController:

  • Применяет SampleActionFilterAttribute ([SampleActionFilter]) для действия FilterTest2.
  • Переопределяет OnActionExecuting и OnActionExecuted.
public class TestController : Controller
{
    [SampleActionFilter(Order = int.MinValue)]
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

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

MyDisplayRouteInfo предоставляется пакетом NuGet Rick.Docs.Samples.RouteInfo и отображает информацию о маршруте.

Переходит к https://localhost:5001/Test/FilterTest2 для выполнения следующего кода:

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

Фильтры на уровне контроллера задают для свойства Order значение int.MinValue. Их нельзя настроить, чтобы они запускались после применения фильтров к методам. Свойство Order рассматривается в следующем разделе.

Сведения о Razor страницах см. в разделе "Реализация Razor фильтров страницы", переопределяя методы фильтрации.

Переопределение порядка по умолчанию

Чтобы переопределить порядок выполнения по умолчанию, можно реализовать IOrderedFilter. IOrderedFilter предоставляет свойство Order, которое имеет приоритет над областью и определяет порядок выполнения. Фильтр со значением меньше Order:

  • Выполняется перед кодом, выполняемым до фильтра с более высоким значением Order.
  • Выполняется после кода, выполняемого после фильтра с более высоким значением Order.

Свойство Order задается с помощью параметра конструктора:

[SampleActionFilter(Order = int.MinValue)]

Рассмотрим два фильтра действий в следующем контроллере:

[MyAction2Filter]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

    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
      • MyAction2FilterAttribute.OnResultExecuting
    • MySampleActionFilter.OnActionExecuted
  • Test2Controller.OnActionExecuted

Свойство Order переопределяет область при определении порядка выполнения фильтров. Фильтры сначала сортируются по порядку, а затем очередность окончательно определяется по области. Все встроенные фильтры реализуют IOrderedFilter и задают значение по умолчанию Order, равное 0. Как упоминалось ранее, фильтры на уровне контроллера задают для свойства Order значение int.MinValue. Во встроенных фильтрах область определяет порядок, если для Order не задано ненулевое значение.

В указанном выше коде MySampleActionFilter имеет глобальную область действия и запускается перед MyAction2FilterAttribute, у которого область действия контроллера. Чтобы MyAction2FilterAttribute запускался первым, задайте для свойства Order значение int.MinValue:

[MyAction2Filter(int.MinValue)]
public class Test2Controller : Controller
{
    public IActionResult FilterTest2()
    {
        return ControllerContext.MyDisplayRouteInfo();
    }

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

Отмена и сокращенное выполнение

Вы можете настроить сокращенное выполнение конвейера фильтров, задав свойство Result для параметра ResourceExecutingContext, передаваемого в метод фильтра. Например, приведенный ниже фильтр ресурсов предотвращает выполнение остальной части конвейера:

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)
    {
    }
}

В приведенном ниже коде как фильтр ShortCircuitingResourceFilter, так и фильтр AddHeader нацелены на метод действия 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.");
    }

    [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.");
    }
}

Внедрение зависимостей

Фильтры можно добавлять по типу или экземпляру. Добавляемый экземпляр используется для каждого запроса. Если добавляется тип, он является активированным. Активация фильтра означает, что:

Фильтры, которые реализуются как атрибуты и добавляются непосредственно в классы контроллеров или методы действий, не могут иметь зависимости конструктора, предоставленные посредством внедрения зависимостей. Зависимости конструктора не могут предоставляться путем внедрения зависимостей, так как:

  • Параметры конструктора должны предоставляться для атрибутов в месте их применения.
  • Это ограничение, налагаемое на использование атрибутов.

Следующие фильтры поддерживают зависимости конструктора, предоставленные путем внедрения зависимостей:

Предыдущие фильтры могут применяться к контроллеру или методу действия.

Средства ведения журнала доступны путем внедрения зависимостей. Но следует избегать создания и использования фильтров исключительно для целей ведения журнала. Встроенное средство ведения журнала обычно предоставляет все необходимое для ведения журнала. При добавлении ведения журналов в фильтры следует учитывать следующее:

  • Следует сосредоточиться на бизнес-среде или поведении в привязке к фильтру.
  • Не следует регистрировать действия или другие события платформы, так как это делают встроенные фильтры.

ServiceFilterAttribute

Типы реализации фильтра службы регистрируются в ConfigureServices. Атрибут ServiceFilterAttribute извлекает экземпляр фильтра из внедрения зависимостей.

В следующем коде используется 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:

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 извлекает экземпляр фильтра AddHeaderResultServiceFilter из внедрения зависимостей:

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

При использовании ServiceFilterAttribute, параметр ServiceFilterAttribute.IsReusable:

  • Указывает, что экземпляр фильтра можно многократно использовать за пределами области запроса, в которой он был создан. Среда выполнения ASP.NET Core не гарантирует:

    • что будет создан хотя бы один экземпляр фильтра;
    • что фильтр не будет повторно запрошен из контейнера внедрения зависимостей на более позднем этапе.
  • Не следует использовать с фильтром, который зависит от служб со временем существования, кроме элементов singleton.

ServiceFilterAttribute реализует IFilterFactory. IFilterFactory предоставляет метод CreateInstance для создания экземпляра IFilterMetadata. CreateInstance загружает указанный тип из внедрения зависимостей.

TypeFilterAttribute

Атрибут TypeFilterAttribute похож на ServiceFilterAttribute, но его тип не разрешается напрямую из контейнера внедрения зависимостей. Он создает экземпляр типа с помощью Microsoft.Extensions.DependencyInjection.ObjectFactory.

Так как типы TypeFilterAttribute не разрешаются напрямую из контейнера внедрения зависимостей:

  • Типы, на которые ссылаются с помощью TypeFilterAttribute, не нужно регистрировать в контейнере внедрения зависимостей. Их зависимости выполняются самим контейнером.
  • Атрибут TypeFilterAttribute может также принимать аргументы конструктора для типа.

При использовании TypeFilterAttribute, параметр TypeFilterAttribute.IsReusable:

  • Указывает, что экземпляр фильтра можно многократно использовать за пределами области запроса, в которой он был создан. Среда выполнения ASP.NET Core не предоставляет никаких гарантий, что будет создан хотя бы один экземпляр фильтра.

  • Не следует использовать с фильтром, который зависит от служб со временем существования, кроме элементов singleton.

В следующем примере показано, как передавать аргументы в тип с помощью TypeFilterAttribute:

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

Фильтры авторизации

Фильтры авторизации:

  • Выполняются в первую очередь в конвейере фильтров.
  • Контролируют доступ к методам действий.
  • Имеют предшествующий, но не последующий метод.

Для работы пользовательских фильтров авторизации требуется настраиваемая платформа авторизации. Настройка политик авторизации или определение пользовательской политики авторизации предпочтительнее создания пользовательского фильтра. Встроенный фильтр авторизации:

  • Вызывает систему авторизации.
  • Не выполняет авторизацию запросов.

Не вызывайте исключения в фильтрах авторизации:

  • Исключение не будет обработано.
  • Фильтры исключений не будут обрабатывать исключение.

При возникновении исключения в фильтре авторизации попробуйте создать запрос.

Дополнительные сведения об авторизации.

Фильтры ресурсов

Фильтры ресурсов:

Фильтры ресурсов полезны для сокращенного выполнения большей части конвейера. Например, фильтр кэширования предотвращает выполнение остальной части конвейера при попадании в кэше.

Примеры фильтров ресурсов:

Фильтры действий

Фильтры действий не применяются к Razor Страницам. Razor Страницы поддерживают IPageFilter и IAsyncPageFilter. Дополнительные сведения см. в разделе Методы фильтрации для Razor Pages.

Фильтры действий:

  • Реализуют либо интерфейс IActionFilter, либо интерфейс IAsyncActionFilter.
  • Их выполнение охватывает выполнение методов действия.

В следующем фрагменте кода показан пример фильтра действий:

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 предоставляет Controller и Result, а также следующие свойства:

  • Canceled будет иметь значение true, если выполнение действия было сокращено другим фильтром.

  • Exception будет иметь ненулевое значение, если предыдущее выполнение фильтра действий вызвало исключение. При присвоении этому свойству ненулевого значения:

    • Будет эффективно обрабатываться исключение.
    • 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. Дополнительные сведения см. в разделе Автоматические отклики HTTP 400. Метод OnActionExecuted выполняется после метода действия:

  • Он имеет доступ к результатам действия и может управлять ими с помощью свойства Result.

  • Canceled будет иметь значение true, если выполнение действия было сокращено другим фильтром.

  • Exception будет иметь ненулевое значение, если действие или последующий фильтр действий вызвали исключение. Если установить для 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);
    }
}

Фильтры исключений

Фильтры исключений:

  • Реализуют IExceptionFilter или IAsyncExceptionFilter.
  • Можно использовать для реализации политик обработки стандартных ошибок.

В следующем примере фильтра исключений используется пользовательское представление ошибок для отображения подробных сведений об исключениях, которые происходят во время разработки приложения:

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.");
    }
}

Фильтры исключений:

  • Не имеют предшествующих и последующих событий.
  • Реализуют OnException или OnExceptionAsync.
  • Обработка необработанных исключений, возникающих при Razor создании страницы или контроллера, привязке модели, фильтрах действий или методах действий.
  • Не перехватывают исключения, которые возникают в фильтрах ресурсов, фильтрах результатов или при выполнении результата MVC.

Чтобы обработать исключение, задайте ExceptionHandled для свойства true значение или назначьте это Result свойство. Это предотвратит распространение исключения. Фильтр исключений не может преобразовать исключение в успешное выполнение. Это может сделать только фильтр действий.

Фильтры исключений:

  • Хорошо подходят для перехвата исключений, возникающих в действиях.
  • Не обладает такой гибкостью, как ПО промежуточного слоя обработки ошибок.

ПО промежуточного слоя хорошо подходит для обработки исключений. Используйте фильтры исключений, только если обработка ошибок осуществляется по-разному с учетом вызванного метода действия. Например, в приложении могут использоваться методы действий как для конечных точек 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 будет иметь ненулевое значение, если результат действия или последующий фильтр результатов вызвали исключение. Присвоение Exception значения NULL приводит к обработке исключения и предотвращает его последующий вызов на дальнейших этапах конвейера. Надежный способ записи данных в ответ при обработке исключения в фильтре результатов отсутствует. Если результат действия вызывает исключение в процессе выполнения и заголовки уже были переданы в клиент, надежного механизма отправки кода сбоя не существует.

Для IAsyncResultFilter вызов к await next для ResultExecutionDelegate приводит к выполнению последующих фильтров результатов и результата действия. Чтобы получить короткий канал, установите 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

Интерфейсы IAlwaysRunResultFilter и IAsyncAlwaysRunResultFilter объявляют реализацию IResultFilter, которая выполняется для всех результатов действий. Сюда включены результаты действий, созданные:

  • фильтрами авторизации и фильтрами ресурсов, которые сокращают ответ;
  • фильтрами исключений.

Например, следующий фильтр всегда выполняется, задавая результат действия (ObjectResult) с кодом состояния 422 Unprocessable Entity при сбое согласования содержимого:

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

    public void OnResultExecuted(ResultExecutedContext context)
    {
    }
}

IFilterFactory

IFilterFactory реализует IFilterMetadata. Поэтому экземпляр IFilterFactory можно использовать в качестве экземпляра IFilterMetadata в любом месте конвейера фильтров. Когда среда выполнения готовится вызвать фильтр, она пытается привести его к IFilterFactory. Если приведение проходит успешно, вызывается метод CreateInstance для создания вызываемого экземпляра IFilterMetadata. Это обеспечивает высокую степень гибкости, так как при запуске приложения нет необходимости в явном и точном определении конвейера фильтров.

IFilterFactory.IsReusable:

  • Указание фабрики о том, что экземпляр фильтра, созданный фабрикой, может повторно использоваться за пределами запроса, область он был создан внутри.
  • Не следует использовать с фильтром, который зависит от служб с временем существования, отличного от одноэлементного.

Среда выполнения ASP.NET Core не гарантирует:

  • что будет создан хотя бы один экземпляр фильтра;
  • что фильтр не будет повторно запрошен из контейнера внедрения зависимостей на более позднем этапе.

Предупреждение

Только настроить IFilterFactory.IsReusable возврат, true если источник фильтров является однозначной, фильтры без отслеживания состояния, а фильтры безопасны для нескольких HTTP-запросов. Например, не возвращайте фильтры из di, зарегистрированные как область или временные, если IFilterFactory.IsReusable возвращаетсяtrue. Еще один подход к созданию фильтров заключается в реализации 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
  • internalMy header:

Приведенный выше код создает заголовок ответа internal:My header.

Реализация IFilterFactory в атрибуте

Фильтры, реализующие IFilterFactory, используются для фильтров, которые:

  • Не требуют передачи параметров.
  • Содержат зависимости конструктора, которые должны заполняться путем внедрения зависимостей.

TypeFilterAttribute реализует IFilterFactory. IFilterFactory предоставляет метод CreateInstance для создания экземпляра IFilterMetadata. CreateInstance загружает указанный тип из контейнера служб (внедрение зависимостей).

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

Фильтры ПО промежуточного слоя выполняются на том же этапе конвейера фильтров, что и фильтры ресурсов, перед привязкой модели и после остальной части конвейера.

Потокобезопасность

При передаче экземпляра фильтра Addв , а не его Type, фильтр является одним и не является потокобезопасным.

Дальнейшие действия