.NET Tutorial
Middle6 мин чтенияВопрос 6 из 19

Что такое Filters: Authorization, Action, Result, Exception — порядок выполнения?

Конвейер фильтров

нет: 401 / 403

Middleware pipeline

Authorization filters
доступ разрешён?

Ответ

Resource filters — до
(кэш, ранний выход)

Model binding + валидация

Action filters — OnActionExecuting
видят аргументы, ModelState

Метод действия

Action filters — OnActionExecuted
видят результат или исключение

Exception filters
(если было исключение)

Result filters — до

Выполнение результата
(сериализация в ответ)

Result filters — после

Resource filters — после

нет: 401 / 403

Middleware pipeline

Authorization filters
доступ разрешён?

Ответ

Resource filters — до
(кэш, ранний выход)

Model binding + валидация

Action filters — OnActionExecuting
видят аргументы, ModelState

Метод действия

Action filters — OnActionExecuted
видят результат или исключение

Exception filters
(если было исключение)

Result filters — до

Выполнение результата
(сериализация в ответ)

Result filters — после

Resource filters — после

Порядок выполнения фильтров MVC
Тип Интерфейс Когда Типичное применение
Authorization IAuthorizationFilter, IAsyncAuthorizationFilter Первым [Authorize], проверка API-ключа
Resource IResourceFilter, IAsyncResourceFilter До и после всего остального Кэширование ответа, отключение model binding для загрузки файлов
Action IActionFilter, IAsyncActionFilter Вокруг метода действия Валидация, логирование аргументов, аудит, транзакции
Exception IExceptionFilter, IAsyncExceptionFilter При исключении в действии/фильтрах действия Преобразование исключений в ответы (но лучше — middleware)
Result IResultFilter, IAsyncResultFilter Вокруг выполнения результата Добавление заголовков, обёртка ответа
Always run result IAlwaysRunResultFilter Всегда, даже при коротком замыкании Заголовки, которые нужны в любом ответе

Пример: Action-фильтр

C#
public sealed class AuditFilter(IAuditLog audit, ICurrentUser user) : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // до действия
        var action = context.ActionDescriptor.DisplayName;
        var args = context.ActionArguments;

        var executed = await next();                        // выполнить действие (и внутренние фильтры)

        // после действия
        var success = executed.Exception is null || executed.ExceptionHandled;
        await audit.WriteAsync(user.Id, action!, args, success);
    }
}

// Регистрация
builder.Services.AddScoped<AuditFilter>();

[ServiceFilter(typeof(AuditFilter))]          // берётся из DI со своим временем жизни
public class PaymentsController : ControllerBase { }

Короткое замыкание

C#
public sealed class RequireIdempotencyKeyAttribute : ActionFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        if (!context.HttpContext.Request.Headers.ContainsKey("Idempotency-Key"))
        {
            context.Result = new BadRequestObjectResult("Нужен заголовок Idempotency-Key");
            // установка Result = действие НЕ выполнится, дальше пойдут result-фильтры
        }
    }
}

Области и порядок

C#
builder.Services.AddControllers(o => o.Filters.Add<GlobalLogFilter>());   // глобальный

[TypeFilter(typeof(ControllerFilter))]                                   // контроллер
public class OrdersController : ControllerBase
{
    [ActionLogFilter]                                                    // действие
    public IActionResult Get() => Ok();
}
МетодДействие-фильтрКонтроллерГлобальныйOnActionExecutingOnActionExecutingвызовOnActionExecutedOnActionExecutedOnActionExecuted
МетодДействие-фильтрКонтроллерГлобальныйOnActionExecutingOnActionExecutingвызовOnActionExecutedOnActionExecutedOnActionExecuted
Вложенность по областям

Свойство Order (у атрибутов и IOrderedFilter) меняет порядок: меньше — раньше (снаружи). Сам контроллер тоже является action-фильтром: его методы OnActionExecuting/OnActionExecuted выполняются внутри всех остальных фильтров.

Способы подключить фильтр с зависимостями

Способ Как создаётся Зависимости
Атрибут-наследник ActionFilterAttribute Экземпляр атрибута — один на действие (кэшируется) Нет DI в конструкторе
[ServiceFilter(typeof(T))] Из DI (нужно зарегистрировать T) Да, с временем жизни из регистрации
[TypeFilter(typeof(T))] Через ActivatorUtilities (регистрация не нужна) Да + можно передать аргументы
IFilterFactory Своя логика создания Полный контроль
Глобально options.Filters.Add<T>() Через DI-активатор Да

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

C#
public sealed class DomainExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        if (context.Exception is DomainException ex)
        {
            context.Result = new UnprocessableEntityObjectResult(new ProblemDetails
            {
                Title = "Бизнес-правило нарушено",
                Detail = ex.Message,
                Status = 422
            });
            context.ExceptionHandled = true;
        }
    }
}

Exception-фильтр ловит только исключения из действия и action-фильтров, но не из result-фильтров, middleware, model binding (частично) или сериализации ответа. Поэтому глобальную обработку ошибок делают через UseExceptionHandler + IExceptionHandler, а фильтры — для MVC-специфичных случаев.

Фильтры в Minimal API

C#
app.MapPost("/orders", CreateOrder)
   .AddEndpointFilter(async (ctx, next) =>
   {
       var sw = Stopwatch.StartNew();
       var result = await next(ctx);                 // вызвать обработчик
       ctx.HttpContext.Response.Headers["X-Elapsed-Ms"] = sw.ElapsedMilliseconds.ToString();
       return result;
   });

IEndpointFilter — один тип фильтра вокруг обработчика с доступом к аргументам (ctx.Arguments). Фильтры группы (MapGroup(...).AddEndpointFilter) применяются ко всем эндпоинтам группы.

Вопросы на засыпку

Где лучше делать валидацию — в фильтре или в сервисе?

Формальную валидацию запроса (формат, обязательные поля) — на границе: [ApiController] + DataAnnotations или фильтр с FluentValidation. Бизнес-правила (достаточно ли средств, уникальность) — в доменной/прикладной логике, потому что они не зависят от HTTP.

Чем Resource-фильтр полезен при загрузке файлов?

Он выполняется до model binding. В нём можно отключить value providers форм (FormValueProviderFactory), чтобы большие multipart-запросы не буферизовались в память, и стримить файл напрямую.

Выполняются ли Result-фильтры, если Authorization-фильтр вернул 401?

Обычные IResultFilter — нет (конвейер замкнут на раннем этапе). IAlwaysRunResultFilter — да, именно для таких случаев.

Можно ли применить фильтр ко всем действиям с определённым атрибутом?

Да: глобальный фильтр проверяет метаданные — context.ActionDescriptor.EndpointMetadata.OfType<MyAttribute>().Any() — и действует только при наличии атрибута. Так реализуют, например, [Transactional].

Итог

Фильтры — «middleware внутри MVC» с доступом к контексту действия. Запомните порядок: Authorization → Resource → Action → Exception → Result, вложенность глобальный → контроллер → действие, и подключайте фильтры с зависимостями через ServiceFilter/TypeFilter. Глобальные HTTP-задачи и обработку ошибок оставляйте middleware.