Фильтры

Введение в фильтры

Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7

Последнее обновление: 11.12.2019

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

  • Фильтры авторизации: определяют, авторизован ли пользователь для выполнения текущего запроса. Если пользователь не авторизованн для доступа к ресурсу, то фильтр завершает обработку запроса.

  • Фильтры ресурсов: выполняются после фильтров авторизации. Его метод OnResourceExecuting() выполняется до всех остальных фильтров и до привязки модели, а его метод OnResourceExecuted() выполняется после всех остальных фильтров

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

  • Фильтры RazorPages: применяется только к страницам RazorPages, выполняются перед и после обработки запроса страницей Razor Page

  • Фильтры исключений: определяют действия в отношении необработанных исключений

  • Фильтры результатов действий: фильтр применяется к результатам методов контроллера и страниц Razor Pages, выполняется как до, так и после получения результата

Вместе все эти типы фильтров образуют конвейер фильтров (filter pipeline), который встроен в процесс обработки запроса в MVC и который начинает выполняется после того, как инфраструктура MVC выбрала метод контроллера для обработки запроса. На разных этапах обработки запроса в этом конвейере вызывается соответствующий фильтр:

Filter Pipeline in ASP.NET Core

Определение фильтров

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

Тип фильтра

Синхронный интерфейс

Асинхронный интерфейс

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

IAuthorizationFilter

IAsyncAuthorizationFilter

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

IResourceFilter

IAsyncResourceFilter

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

IActionFilter

IAsyncActionFilter

Фильтр RazorPages

IPageFilter

IAsyncPageFilter

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

IExceptionFilter

IAsyncExceptionFilter

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

IResultFilter

IAsyncResultFilter

Все фильтры имеют одну и ту же схему. Синхронный интерфейс, который реализуют фильтры, называется I[Stage]Filter, где [Stage] - это этап обработки запроса, на котором вызывается фильтр. Например, для фильтра авторизации этап условно называется Authorization, для фильтров ресурсов - Resoure, для фильтров действий - Action, для фильтров результатов - Result, для фильтров исключений - Exception.

Синхронные фильтры (за исключением фильтра Razor Page) определяют два метода: On[Stage]Executing и On[Stage]Executed. Метод On[Stage]Executing вызывается непосредственно перед этапом Stage, а метод On[Stage]Executed сразу после завершения этапа [Stage].

Например, типовая реализация для синхронного фильтра действий:

using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersApp.Filters
{
    public class SimpleResourceFilter : IActionFilter
    {
        public void OnActionExecuting(ActionExecutingContext context)
        {
            // код метода
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            // код метода
        }
    }
}

Асинхронные интерфейсы фильтров называются IAsync[Stage]Filter, и они определяют только один метод - On[Stage]ExecutionAsync (исключение - фильтр RazorPages, где определяется два метода)

using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersApp.Filters
{

    public class SimpleAsynActionFilter : IAsyncActionFilter
    {
        public async Task OnActionExecutionAsync(ActionExecutingContext context, 
									ActionExecutionDelegate next)
        {
            // код метода
            await next();
        }
    }
}

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

К примеру, создадим свой фильтр. Для этого создадим новый проект ASP.NET Core по типу ASP.NET Core Web App (Model-View-Controller). Затем в этот проект добавим папку Filters, в которой будут храниться фильтры. И далее добавим в эту папку новый класс SimpleResourceFilter, который будет представлять простейший фильтр ресурсов

using System;
using Microsoft.AspNetCore.Mvc.Filters;

namespace FiltersApp.Filters
{
    public class SimpleResourceFilter : Attribute, IResourceFilter
    {
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            context.HttpContext.Response.Cookies.Append("LastVisit", DateTime.Now.ToString("dd/MM/yyyy hh-mm-ss"));
        }

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
			// реализация отсутствует
        }
    }
}

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

Обратите внимание! Изменение объекта Response (то есть по сути изменение ответа пользователю) проивзодится в методе OnResourceExecuting, который выполняется до методов контроллера или страницы Razor Pages и соответственно до выполнения результата и формирования ответа пользователю. Если мы попытаемся выполнить все те же действия в методе OnResourceExecuted, то приложение в процессе выполнения сгенерирует исключение. Это нужно иметь в виду при реализации всех видов фильтров.

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

Применение фильтров

Фильтры могут иметь различную область действия:

  • Метод контроллера. Атрибут применяется к методу контроллера:

    public class HomeController : Controller
    {
        [SimpleResourceFilter]
        public IActionResult Index()
        {
            return View();
        }
    }
    
  • Весь контроллер или страница RazorPage.

    Атрибут применяется к классу контроллера:

    [SimpleResourceFilter]
    public class HomeController : Controller
    {
        // содержимое контроллера
    }
    

    Атрибут применяется к классу страницы Razor Page:

    [SimpleResourceFilter]
    public class IndexModel : PageModel
    {
    	public void OnGet()
    	{
    	}
    }
    
  • Глобальная область действия, при которой фильтр применяется ко всем методам всех контроллеров.

    Для определения фильтра как глобального нам надо изменить в методе ConfigureServices() класса Startup подключение соответствующих сервисов MVC. Подключение глобально для всех сервисов MVC (и для контроллеров, и для Razor Pages):

    public void ConfigureServices(IServiceCollection services)
    {
    	// глобально - все сервисы MVC - и контроллеры, и Razor Page
        services.AddMvc(options =>
        {
            options.Filters.Add(typeof(SimpleResourceFilter)); // подключение по типу
            
    		// альтернативный вариант подключения
    		//options.Filters.Add(new SimpleResourceFilter()); // подключение по объекту
        });
    }
    

    Установка отдельно для контроллеров:

    public void ConfigureServices(IServiceCollection services)
    {
    	services.AddControllersWithViews(options=>
    	{ 
    		options.Filters.Add(new SimpleResourceFilter());	// подключение по объекту
    		options.Filters.Add(typeof(SimpleResourceFilter));	// подключение по типу
    	});
    }
    

    Установка отдельно для RazorPages:

    public void ConfigureServices(IServiceCollection services)
    	services.AddRazorPages().AddMvcOptions(options =>
    	{
    		options.Filters.Add(new SimpleResourceFilter());	// подключение по объекту
    		//options.Filters.Add(typeof(SimpleResourceFilter)); // подключение по типу
    	});
    }
    

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

services.AddControllersWithViews(options=> options.Filters.Add(new SimpleResourceFilter()));

Таким образом, мы встроили наш фильтр в конвейер фильтров.

Запустим приложение, и после получения ответа в куках окажутся данные с ключом "LastVisit":

Применение фильтров в ASP.NET Core

Если бы мы не использовали бы фильтр, то в каждом методе нам надо было бы определить строку кода:

HttpContext.Response.Cookies.Append("LastVisit", DateTime.Now.ToString("dd/MM/yyyy hh-mm-ss"));

Таким образом, фильтры позволяют нам избежать повторения в соответствии принципом DRY (Don't Repeat Yourself)

Выход из конвейера фильтров

Как показано выше, фильтры организованы в конвейер, в котором они последовательно выполняются. Однако мы можем на любом этапе выйти из этой конвейера, предотвратив выполнение последующих фильтров. Для этого надо установить свойство Result переданного в фильтр контекста.

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

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;

namespace FiltersApp.Filters
{
    public class FakeNotFoundResourceFilter : Attribute, IResourceFilter
    {
        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            
        }

        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            context.Result = new ContentResult { Content = "Ресурс не найден" };
        }
    }
}

Это фильтр ресурсов, который устанавливает свойство Result. Значение этого свойства фактически будет использоваться в качестве результата выполнения запроса.

Затем применим фильтр к методу контроллера:

public class HomeController : Controller
{
    [FakeNotFoundResourceFilter]
    public IActionResult Index()
    {
        return View();
    }
}

При обращении к методу Index сработает фильтр FakeNotFoundResourceFilter, который установит результат обработки запроса. И после него фильтр SimpleResourceFilter уже не будет действовать.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850