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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Реализация

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

Синхронные фильтры выполняют код до и после этапа конвейера. Например, OnActionExecuting вызывается перед вызовом метода действия, а OnActionExecuted — после возврата метода действия.

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

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

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

Асинхронные фильтры определяют метод On-Stage-ExecutionAsync. Например, OnActionExecutionAsync:

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

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

В приведенном выше коде SampleAsyncActionFilter включает ActionExecutionDelegate (next) для выполнения метода действия.

Несколько этапов фильтра

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

Реализуйте синхронный или асинхронный интерфейс фильтра, но не оба варианта. Среда выполнения сначала проверяет, реализует ли фильтр асинхронный интерфейс. Если да, вызывается именно он. В противном случае вызываются методы синхронного интерфейса. Если асинхронный и синхронный интерфейсы реализованы в одном классе, вызывается только асинхронный метод. При использовании абстрактных классов ActionFilterAttribute , таких как, переопределяйте только синхронные методы или асинхронные методы для каждого типа фильтра.

Встроенные атрибуты фильтров

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

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

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

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

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

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

Для изучения заголовков используйте средство разработчика браузера . В заголовке ответа отображается author: Rick Anderson.

В следующем коде реализуется класс ActionFilterAttribute, который выполняет следующие действия:

  • Считывает заголовок и имя в системе конфигурации. В отличие от предыдущего примера, для указанного ниже кода не нужно добавлять параметры фильтра.
  • Добавляет заголовок и имя к заголовку ответа.
public class MyActionFilterAttribute : ActionFilterAttribute
{
    private readonly PositionOptions _settings;

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

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

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

{
    "Position": {
        "Title": "Редактор",
        "Name": "Joe Smith"
    },
    "Logging": {
        "LogLevel": {
            "Default": "Information",
            "Microsoft": "Warning",
            "Microsoft.Hosting.Lifetime": "Information"
        }
    },
    "AllowedHosts": "*"
}

В StartUp.ConfigureServices:

  • Класс PositionOptions добавляется в контейнер службы с помощью области конфигурации "Position".
  • MyActionFilterAttribute добавляется в контейнер службы.
public void ConfigureServices(IServiceCollection services)
{
    services.Configure<PositionOptions>(
             Configuration.GetSection("Position"));
    services.AddScoped<MyActionFilterAttribute>();

    services.AddControllersWithViews();
}

В следующем коде демонстрируется класс PositionOptions:

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

В следующем коде применяется атрибут MyActionFilterAttribute к методу Index2:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Среда выполнения 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) отобразятся следующие заголовки ответа, добавленные примером кода:

  • Автор:Rick Anderson
  • globaladdheader: Result filter added to MvcOptions.Filters
  • внутренний:My 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 , фильтр является Singleton-классом и не является потокобезопасным.

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

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

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

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

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

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

Этот документ применяется к Razor страницам, контроллерам API и контроллерам с представлениями.

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

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

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

Запрос обрабатывается с использованием прочего ПО промежуточного слоя, ПО промежуточного слоя маршрутизации, выбора действия и конвейера вызова действий ASP.NET Core. Обработка запроса продолжается в обратном порядке посредством выбора действия, ПО промежуточного слоя маршрутизации и прочего ПО промежуточного слоя, пока не будет получен запрос, отправляемый клиенту.

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

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

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

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

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

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

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

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

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

Реализация

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

Синхронные фильтры могут выполнять код до (On-Stage-Executing) и после (On-Stage-Executed) этапа конвейера. Например, OnActionExecuting вызывается перед вызовом метода действия, а OnActionExecuted — после возврата метода действия.

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

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

Асинхронные фильтры определяют метод On-Stage-ExecutionAsync:

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

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

В приведенном выше коде SampleAsyncActionFilter включает ActionExecutionDelegate (next) для выполнения метода действия. Каждый из методов On-Stage-ExecutionAsync принимает FilterType-ExecutionDelegate для выполнения этапа конвейера фильтра.

Несколько этапов фильтра

Реализовать интерфейсы для нескольких этапов фильтра можно в одном классе. Например, класс ActionFilterAttribute реализует интерфейсы IActionFilter и IResultFilter, а также их асинхронные эквиваленты.

Реализуйте синхронный или асинхронный интерфейс фильтра, но не оба варианта. Среда выполнения сначала проверяет, реализует ли фильтр асинхронный интерфейс. Если да, вызывается именно он. В противном случае вызываются методы синхронного интерфейса. Если асинхронный и синхронный интерфейсы реализованы в одном классе, вызывается только асинхронный метод. При использовании абстрактных классов, таких как ActionFilterAttribute, следует переопределять только синхронные методы или асинхронный метод каждого типа фильтра.

Встроенные атрибуты фильтров

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

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

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

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

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

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

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

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

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

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

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

  • С помощью атрибута в действии.
  • С помощью атрибута в контроллере.
  • Глобально для всех контроллеров и действий, как показано в следующем коде:
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(options =>
    {
        options.Filters.Add(new AddHeaderAttribute("GlobalAddHeader",
            "Result filter added to MvcOptions.Filters"));         // An instance
        options.Filters.Add(typeof(MySampleActionFilter));         // By type
        options.Filters.Add(new SampleGlobalActionFilter());       // An instance
    }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}

Предыдущий код глобально добавляет три фильтра с помощью коллекции MvcOptions.Filters.

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

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

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

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

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

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

Эта последовательность показывает:

  • Фильтр метода вкладывается в фильтр контроллера.
  • Фильтр контроллера вкладывается в глобальный фильтр.

RazorФильтры уровня контроллера и страницы

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

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

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

TestController:

  • Применяет SampleActionFilterAttribute ([SampleActionFilter]) для действия FilterTest2.
  • Переопределяет OnActionExecuting и OnActionExecuted.
public class TestController : Controller
{
    [SampleActionFilter]
    public IActionResult FilterTest2()
    {
        return Content($"From FilterTest2");
    }

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

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

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

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

Для Razor страниц см. раздел реализация Razor фильтров страниц путем переопределения методов фильтров.

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

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

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

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

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

Рассмотрим три фильтра действий, показанные в предыдущем примере. Если свойство Order контроллера и глобальные фильтры имеют значения 1 и 2 соответственно, порядок выполнения инвертируется.

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

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

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

Вы можете настроить сокращенное выполнение конвейера фильтров, задав свойство 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", "Joe Smith")]
public class SampleController : Controller
{
    public IActionResult Index()
    {
        return Content("Examine the headers using the F12 developer tools.");
    }

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

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

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

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

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

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

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

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

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

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.
    }
}

В следующем коде в контейнер внедрения зависимостей добавляется AddHeaderResultServiceFilter:

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

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

В следующем коде атрибут ServiceFilter извлекает экземпляр фильтра 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}");
}
public class LogConstantFilter : IActionFilter
{
    private readonly string _value;
    private readonly ILogger<LogConstantFilter> _logger;

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

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

    public void OnActionExecuted(ActionExecutedContext context)
    { }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Важно!

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

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

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

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

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

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

ActionExecutingContext предоставляет следующие свойства:

  • ActionArguments позволяет считать входные данные в метод действия.
  • Controller позволяет управлять экземпляром контроллера.
  • Result позволяет при задании свойства Result сократить выполнение метода действия и последующих фильтров действий.

Вызов исключения в методе действия:

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

ActionExecutedContext предоставляет 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);
        }
    }

Метод 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 IHostingEnvironment _hostingEnvironment;
    private readonly IModelMetadataProvider _modelMetadataProvider;

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

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

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

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

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

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

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

ПО промежуточного слоя хорошо подходит для обработки исключений. Используйте фильтры исключений, только если обработка ошибок осуществляется по-разному с учетом вызванного метода действия. Например, в приложении могут использоваться методы действий как для конечных точек API, так и для представлений или HTML. Конечные точки API могут возвращать сведения об ошибках в формате JSON, в то время как действия на основе представлений могут возвращать страницу ошибки в формате HTML.

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

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

IResultFilter и IAsyncResultFilter

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

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

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

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

Тип выполняемого результата зависит от соответствующего действия. Действие, возвращающее представление, будет включать всю обработку Razor в рамках выполняемого объекта ViewResult. В процессе выполнения результата метод API может производить сериализацию. Дополнительные сведения о результатах действий.

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

  • Фильтр авторизации или ресурсов выполняет сокращение конвейера.
  • Фильтр исключений обрабатывает исключение и выдает результат действия.

Метод Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuting может сокращать выполнение результата действия и последующих фильтров результатов, присваивая свойству Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext.Cancel значение true. При сокращении выполнения выполните запись в объект ответа, чтобы избежать формирования пустого ответа. Вызов исключения в IResultFilter.OnResultExecuting:

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

Если запускается метод Microsoft.AspNetCore.Mvc.Filters.IResultFilter.OnResultExecuted, ответ, скорее всего, уже был отправлен клиенту. Если ответ уже был отправлен клиенту его больше нельзя изменить.

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

ResultExecutedContext.Exception будет иметь ненулевое значение, если результат действия или последующий фильтр результатов вызвали исключение. Присвоение Exception значения NULL приводит к обработке исключения и предотвращает его последующий вызов ASP.NET Core на дальнейших этапах конвейера. Надежный способ записи данных в ответ при обработке исключения в фильтре результатов отсутствует. Если результат действия вызывает исключение в процессе выполнения и заголовки уже были переданы в клиент, надежного механизма отправки кода сбоя не существует.

Для 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 с помощью настраиваемых атрибутов:

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

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

        public void OnResultExecuted(ResultExecutedContext context)
        {
        }
    }

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

Приведенный выше код можно проверить, выполнив скачиваемый пример:

  • Вызовите средства для разработчика (F12).
  • Перейдите на страницу https://localhost:5001/Sample/HeaderWithFactory.

В средствах для разработчика (F12) отобразятся следующие заголовки ответа, добавленные примером кода:

  • Автор:Joe Smith
  • globaladdheader: Result filter added to MvcOptions.Filters
  • внутренний:My 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("Business action starting...");
            // perform some business logic work

        }

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

В коде ниже приведено три примера применения [SampleActionFilter]:

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

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

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

В приведенном выше коде декорирование метода с использованием [SampleActionFilter] является рекомендуемым способом применения SampleActionFilter.

Использование ПО промежуточного слоя в конвейере фильтров

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

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

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

        var options = new RequestLocalizationOptions
        {

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

        applicationBuilder.UseRequestLocalization(options);
    }
}

Используйте MiddlewareFilterAttribute для выполнения ПО промежуточного слоя:

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

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

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

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

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