Какие фильтры (Filters) ты знаешь в ASP.NET Core MVC?

Ответ

Фильтры в ASP.NET Core — это компоненты, которые выполняют код до или после определенных этапов конвейера обработки запроса (pipeline). Они позволяют инкапсулировать сквозную функциональность (cross-cutting concerns), такую как авторизация, логирование, валидация, кэширование.

Фильтры выполняются в строгом порядке, известном как Pipeline фильтров:

  1. Authorization Filters → 2. Resource Filters → 3. Action Filters → 4. Exception Filters → 5. Result Filters

Основные типы фильтров:

1. Authorization Filters (IAuthorizationFilter, IAsyncAuthorizationFilter)

  • Цель: Определить, авторизован ли пользователь для доступа к ресурсу.
  • Выполняется: Первыми, до привязки модели и выполнения действия.
  • Пример: [Authorize], [AllowAnonymous].
  • Зачем свой? Для реализации кастомной политики доступа (например, на основе роли и региона).

2. Resource Filters (IResourceFilter, IAsyncResourceFilter)

  • Цель: Обработка запроса до и после остальных фильтров в конвейере. Идеальны для кэширования или валидации состояния ресурса.
  • Выполняется: После Authorization, но до Model Binding. Имеют метод OnResourceExecuting (до) и OnResourceExecuted (после).
  • Пример использования: Короткое замыкание конвейера (early exit) для возврата закэшированного результата.
public class CustomCacheResourceFilter : IAsyncResourceFilter
{
    public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
    {
        // 1. ДО выполнения остального конвейера: проверяем кэш
        var cacheKey = GenerateCacheKey(context.HttpContext.Request);
        if (_cache.TryGetValue(cacheKey, out var cachedResult))
        {
            context.Result = cachedResult as IActionResult; // Короткое замыкание
            return;
        }

        // 2. Позволяем конвейеру выполниться
        var executedContext = await next();

        // 3. ПОСЛЕ выполнения: сохраняем результат в кэш
        if (executedContext.Result is OkObjectResult okResult)
        {
            _cache.Set(cacheKey, okResult.Value, TimeSpan.FromMinutes(5));
        }
    }
}

3. Action Filters (IActionFilter, IAsyncActionFilter)

  • Цель: Логика, специфичная для действия контроллера. Выполняются до и после вызова метода действия, но после привязки модели.
  • Использование: Валидация модели, логирование параметров, изменение аргументов или результата действия.
  • Глобальный пример: [ServiceFilter(typeof(LoggingActionFilter))].

4. Exception Filters (IExceptionFilter, IAsyncExceptionFilter)

  • Цель: Глобальная обработка необработанных исключений, возникших в контроллере, фильтрах или привязке модели.
  • Выполняется: Только при возникновении исключения.
  • Важно: Не перехватывают исключения в Middleware или при генерации результата (для этого есть UseExceptionHandler middleware).
  • Пример: Отправка специфичных ошибок API в определенном формате (JSON).

5. Result Filters (IResultFilter, IAsyncResultFilter)

  • Цель: Логика, выполняемая до и после выполнения результата действия (например, перед рендерингом представления).
  • Выполняется: После метода действия, но до выполнения IActionResult (вызова View() или Json()).
  • Использование: Изменение HTTP-заголовков, форматирование ответа, логирование результата.

Способы применения фильтров:

  • На уровне действия: [TypeFilter(typeof(MyActionFilter))] на методе контроллера.
  • На уровне контроллера: Атрибут применяется ко всем действиям в контроллере.
  • Глобально: Регистрация в Program.cs или Startup.ConfigureServices:
    services.AddControllers(options =>
    {
        options.Filters.Add(new MyGlobalFilter()); // Экземпляр
        options.Filters.Add<MyGlobalFilter>();    // Тип (будет разрешен через DI)
    });

Ключевое правило: Фильтры, зарегистрированные глобально, применяются ко всем действиям. Фильтры на контроллере/действии выполняются в порядке: глобальные → контроллер → действие (для OnActionExecuting), и в обратном порядке для OnActionExecuted.

Ответ 18+ 🔞

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

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

Порядок — это пиздец какой важный, запомни раз и навсегда:

  1. Фильтры авторизации — тебя вообще пускать внутрь? Нет прав — иди нахуй, дальше даже не начинаем.
  2. Ресурсные фильтры — типа "подожди, а может, мы уже это где-то делали?" Идеально для кэша, чтобы лишний раз не париться.
  3. Фильтры действий — вот тут уже конкретика: перед самым вызовом твоего метода и сразу после.
  4. Фильтры исключений — если где-то выше по цепочке пиздец случился, они пытаются это прикрыть.
  5. Фильтры результатов — последний штрих: прямо перед тем, как отправить ответ клиенту.

Ну а теперь подробнее, с приправами:

1. Авторизация (IAuthorizationFilter) Это как вышибала в клубе. "Ты кто такой? А, нет тебя в списке — свободен". Выполняется самым первым, ещё до того, как мы вообще понимаем, что за запрос пришёл. Стандартный [Authorize] — это оно и есть. Свой пишешь, если тебе мало просто проверки на логин — например, нужно ещё и чтобы пользователь из нужного региона был, и не полупидор.

2. Ресурсные фильтры (IResourceFilter) О, это хитрая жопа! Они выполняются ДО всего основного конвейера (прямо после авторизации) и ПОСЛЕ него. Идеально, чтобы срезать углы. Например, если у тебя в кэше уже лежит готовый ответ — зачем вообще выполнять действие, бензин жечь? Вот тебе живой пример, смотри:

public class CustomCacheResourceFilter : IAsyncResourceFilter
{
    public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
    {
        // 1. Сначала: проверяем, нет ли уже всего готового?
        var cacheKey = GenerateCacheKey(context.HttpContext.Request);
        if (_cache.TryGetValue(cacheKey, out var cachedResult))
        {
            context.Result = cachedResult as IActionResult; // Всё, приехали, дальше не идём
            return;
        }

        // 2. Пускаем запрос по всему конвейеру (действие, базы и прочую хуйню)
        var executedContext = await next();

        // 3. После того как всё посчитали — кладём в кэш на будущее
        if (executedContext.Result is OkObjectResult okResult)
        {
            _cache.Set(cacheKey, okResult.Value, TimeSpan.FromMinutes(5));
        }
    }
}

Видишь магию? Если нашли в кэше — сразу return, и весь оставшийся pipeline, включая твой тяжёлый метод в контроллере, даже не запустится. Экономия — ебать просто!

3. Фильтры действий (IActionFilter) Это уже ближе к делу. Выполняются прямо перед вызовом твоего метода и сразу после. Модель уже привязана, параметры готовы — можно, например, проверить, а валидна ли она вообще. Или залогировать, что за аргументы пришли. Или, если результат не нравится, подменить его в OnActionExecuted. Удобно, как карманный пердак.

4. Фильтры исключений (IExceptionFilter) Ловят всё, что не поймали до этого. Упало исключение в контроллере или в фильтре — они пытаются это обработать и выдать адекватную ошибку, а не просто "500 Internal Server Error, иди нахуй". Но запомни: они НЕ ловят исключения из middleware — для этого есть отдельная, блядь, штука UseExceptionHandler. Не перепутай, а то будешь как дурак.

5. Фильтры результатов (IResultFilter) Всё, действие выполнилось, результат сформирован (например, View() или Json()), но ещё не отправлен клиенту. Вот тут-то они и работают. Можно добавить какие-нибудь хидеры в ответ, изменить формат, залогировать, что же мы, собственно, отправили. Последний шанс всё подправить.


Куда это всё пихать?

Да куда угодно, блядь! Можно навесить на конкретный метод контроллера — тогда только для него. Можно на весь контроллер — тогда все методы там будут через это проходить. А можно глобально, в настройках — и тогда ВСЕ запросы во ВСЕХ контроллерах будут этим пользоваться. Это как с солью: перебор — и всё испортишь.

Вот так регистрируешь глобально, в Program.cs:

services.AddControllers(options =>
{
    options.Filters.Add(new MyGlobalFilter()); // Просто инстанс
    options.Filters.Add<MyGlobalFilter>();    // Или через DI, чтобы зависимости инжектить
});

Важное правило про порядок, а то запутаешься: для методов "до" (типа OnActionExecuting) порядок такой: сначала глобальные, потом на контроллере, потом на действии. А для методов "после" (OnActionExecuted) — всё наоборот: сначала действие, потом контроллер, потом глобальные. Как матрёшка, только в разобранном и собранном виде.

Короче, фильтры — это мощно. Позволяют не засирать контроллеры одинаковым кодом, а вынести всю эту рутину в отдельные компоненты. Главное — не увлекаться и понимать, в каком порядке они выстрелят, а то получится "вротберунчик", и будешь дебажить до пенсии.