.NET y backend

Middleware vs Filters vs Handlers en ASP.NET Core

Tres formas de interceptar una petición en ASP.NET Core que se confunden a menudo: qué ve cada una, en qué orden se ejecutan y cuál elegir en cada caso.

Por Leyton Manuel Espitia Diaz 5 min de lectura

Fragmento de código C# con un middleware y un endpoint filter de ASP.NET Core

Hay que agregar algo que se ejecute en todas las peticiones: medir el tiempo de respuesta, validar un encabezado, registrar quién llamó. En ASP.NET Core hay al menos tres lugares donde ponerlo, y elegir el equivocado no falla: simplemente hace que el código se ejecute cuando no debería, o que no vea la información que necesita.

La diferencia entre middleware, filtros y handlers no está en qué pueden hacer, sino en qué ven y cuándo se ejecutan.

El recorrido de una petición

Petición
  → Middleware 1 (p. ej. manejo de excepciones)
    → Middleware 2 (autenticación)
      → Routing → Endpoint
          → Filtros (MVC o endpoint filters)
              → Acción / handler del endpoint
          ← Filtros
    ← Middleware 2
  ← Middleware 1
Respuesta

El middleware envuelve todo. Los filtros solo existen dentro de un endpoint que ya fue elegido.

Middleware: todo el tráfico, sin contexto del endpoint

El middleware ve cada petición que llega a la aplicación, incluidas las que terminan en archivos estáticos, health checks o rutas que no existen.

public class RequestTimingMiddleware(RequestDelegate next, ILogger<RequestTimingMiddleware> logger)
{
    public async Task InvokeAsync(HttpContext context)
    {
        var start = Stopwatch.GetTimestamp();
        await next(context);
        var elapsed = Stopwatch.GetElapsedTime(start);

        logger.LogInformation("{Method} {Path} → {StatusCode} en {Elapsed} ms",
            context.Request.Method, context.Request.Path, context.Response.StatusCode, elapsed.TotalMilliseconds);
    }
}

app.UseMiddleware<RequestTimingMiddleware>();

Lo usamos para lo transversal a toda la aplicación: manejo global de errores, encabezados de seguridad, compresión, CORS, correlation IDs y métricas por petición.

Lo que no ve: el modelo ya enlazado, el resultado de la validación ni (antes del routing) qué endpoint se va a ejecutar. Si lo registras después de UseRouting, puedes leer el endpoint con context.GetEndpoint() y sus metadatos, pero sigue sin ver los argumentos de la acción.

El orden importa. El middleware se ejecuta en el orden en que se registra. Un manejador de excepciones registrado después de otro middleware no captura los errores de ese middleware.

Filtros: dentro del endpoint, con el modelo enlazado

Filtros de MVC (controladores)

Tienen acceso a los argumentos de la acción, al ModelState y al resultado:

public class RequireTenantFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        if (!context.HttpContext.Request.Headers.ContainsKey("X-Tenant-Id"))
        {
            context.Result = new BadRequestObjectResult("Falta el encabezado X-Tenant-Id");
            return; // corta la ejecución: la acción no se llama
        }

        await next();
    }
}

Hay varios tipos, que se ejecutan en este orden: autorización, recursos, acción, excepción y resultado. Cada uno tiene su momento.

Endpoint filters (Minimal APIs)

Desde .NET 7, las Minimal APIs tienen su propio mecanismo, más simple:

app.MapPost("/api/orders", CreateOrder)
   .AddEndpointFilter(async (context, next) =>
   {
       var request = context.GetArgument<CreateOrderRequest>(0);
       if (request.Lines.Count == 0)
           return Results.Problem("El pedido debe tener al menos una línea", statusCode: 400);

       return await next(context);
   });

Se pueden aplicar a un grupo de rutas con MapGroup(...).AddEndpointFilter(...).

Los usamos para lógica que depende del endpoint o de sus argumentos: validación de modelos, auditoría de una acción concreta o comprobaciones que solo aplican a cierto grupo de rutas.

"Handlers": tres cosas distintas con el mismo nombre

La palabra handler aparece en ASP.NET Core con significados diferentes, y conviene no mezclarlos:

Handler Qué hace
IExceptionHandler (.NET 8+) Convierte excepciones no controladas en respuestas, dentro del middleware de excepciones
AuthorizationHandler<T> Evalúa un requisito de una política de autorización
DelegatingHandler Intercepta las peticiones salientes de un HttpClient

El último se confunde con frecuencia con el middleware, pero trabaja en la dirección contraria: no ve las peticiones que llegan a tu API, sino las que tu API hace a otros servicios.

public class CorrelationIdHandler(IHttpContextAccessor accessor) : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken ct)
    {
        // Propaga el ID de correlación a los servicios que llamamos
        var id = accessor.HttpContext?.TraceIdentifier;
        if (id is not null) request.Headers.TryAddWithoutValidation("X-Correlation-Id", id);
        return base.SendAsync(request, ct);
    }
}

builder.Services.AddTransient<CorrelationIdHandler>();
builder.Services.AddHttpClient<InventoryClient>().AddHttpMessageHandler<CorrelationIdHandler>();

Cómo elegir

Necesitas... Usa
Algo en todas las peticiones, incluidas las que no llegan a un endpoint Middleware
Los argumentos o el modelo ya validado Filtro
Aplicarlo solo a ciertos endpoints o grupos Filtro (o middleware condicionado por metadatos)
Convertir excepciones en respuestas ProblemDetails IExceptionHandler
Decidir si un usuario puede acceder a un recurso Política + AuthorizationHandler
Modificar llamadas a otros servicios DelegatingHandler

El trade-off

El middleware es la opción más general y la más fácil de entender, pero cuesta un poco en cada petición y no tiene contexto del endpoint. Los filtros son precisos, pero quedan atados a MVC o a Minimal APIs y es fácil olvidar registrarlos en un endpoint nuevo. Un filtro global evita ese olvido, a cambio de ejecutarse donde quizás no hace falta.

Cuándo no usar ninguno

Si una regla solo aplica a un endpoint, a veces lo más claro es ponerla en el propio endpoint. Un filtro con una sola aplicación es una indirección más que el próximo desarrollador tendrá que buscar para entender qué pasa.

Conclusión

Middleware para lo que afecta a toda la aplicación; filtros para lo que depende del endpoint; handlers para tres problemas concretos que no son ninguno de los anteriores. Cuando el código está en el nivel correcto, nadie tiene que preguntarse por qué se ejecuta, o por qué no.

Leyton Manuel Espitia Diaz

Escrito por

Leyton Manuel Espitia Diaz

CEO y fundador de Nakanaba

Desarrollador de software y fundador de Nakanaba. Lidera la arquitectura de Vitrin: backend en .NET, aplicaciones en Angular y .NET MAUI, SQL Server y servicios en Cloudflare.

¿Necesitas un equipo .NET para tu proyecto?

Construimos y modernizamos backends en .NET y SQL Server: APIs, integraciones y sistemas empresariales a medida.

Ver desarrollo a medida