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

«Какие фильтры (Filters) ты знаешь в ASP.NET Core MVC?» — вопрос из категории ASP.NET Core, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Фильтры в 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.