{"id":207,"date":"2026-03-16T20:11:16","date_gmt":"2026-03-17T00:11:16","guid":{"rendered":"https:\/\/juredev.com\/blog\/?p=207"},"modified":"2026-03-20T10:40:52","modified_gmt":"2026-03-20T14:40:52","slug":"middleware-y-el-pipeline-http-en-asp-net-core","status":"publish","type":"post","link":"https:\/\/juredev.com\/blog\/2026\/03\/middleware-y-el-pipeline-http-en-asp-net-core\/","title":{"rendered":"Middleware y el Pipeline HTTP en ASP.NET Core: C\u00f3mo fluye realmente una petici\u00f3n"},"content":{"rendered":"\n<p>Cuando una petici\u00f3n HTTP llega a tu aplicaci\u00f3n <a href=\"https:\/\/juredev.com\/blog\/?s=ASP.NET+Core\">ASP.NET Core<\/a>, no va directo al controlador ni salta m\u00e1gicamente a tu endpoint de Minimal API.<\/p>\n\n\n\n<p>Antes tiene que atravesar algo que casi todos usamos a diario, pero que mucha gente no termina de entender del todo: <strong>el pipeline de middleware<\/strong>.<\/p>\n\n\n\n<p>Cada pieza de middleware que est\u00e1 en esa cadena tiene la oportunidad de:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>inspeccionar la petici\u00f3n<\/li>\n\n\n\n<li>modificar el <code>HttpContext<\/code><\/li>\n\n\n\n<li>decidir cortar el flujo ah\u00ed mismo<\/li>\n\n\n\n<li>o simplemente dejar que la petici\u00f3n siga su camino hacia el siguiente componente<\/li>\n<\/ul>\n\n\n\n<p>Entender bien c\u00f3mo funciona este pipeline te va a salvar de muchos dolores de cabeza. Es clave para:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>implementar autenticaci\u00f3n y autorizaci\u00f3n de forma correcta<\/li>\n\n\n\n<li>registrar logs \u00fatiles (y sin duplicados raros)<\/li>\n\n\n\n<li>capturar y manejar errores de manera global<\/li>\n\n\n\n<li>medir rendimiento real<\/li>\n\n\n\n<li>mantener el c\u00f3digo limpio y predecible<\/li>\n<\/ul>\n\n\n\n<p>Vamos a ver paso a paso c\u00f3mo funciona realmente.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\u00bfQu\u00e9 es (y c\u00f3mo se ve) el pipeline HTTP?<\/h2>\n\n\n\n<p>En <a href=\"https:\/\/en.wikipedia.org\/wiki\/ASP.NET_Core\">ASP.NET Core<\/a> el pipeline no es m\u00e1s que una cadena de middlewares por la que pasa la petici\u00f3n.<\/p>\n\n\n\n<p>Cada middleware recibe dos cosas importantes:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>El <code>HttpContext<\/code> (con toda la info de la request y response)<\/li>\n\n\n\n<li>Una referencia al siguiente middleware (normalmente llamada next)<\/li>\n<\/ul>\n\n\n\n<p>El flujo se ve m\u00e1s o menos as\u00ed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Request\n   \u2193\nMiddleware 1\n   \u2193\nMiddleware 2\n   \u2193\nMiddleware 3\n   \u2193\nEndpoint (Controller \/ Minimal API)\n   \u2191\nMiddleware 3\n   \u2191\nMiddleware 2\n   \u2191\nMiddleware 1\n   \u2191\nResponse<\/code><\/pre>\n\n\n\n<p>F\u00edjate en algo s\u00faper importante: la respuesta vuelve en sentido inverso.<\/p>\n\n\n\n<p>Eso significa que un middleware puede ejecutar c\u00f3digo antes de llamar al siguiente\u2026 y tambi\u00e9n despu\u00e9s de que todos los siguientes hayan terminado.<\/p>\n\n\n\n<p>Ese doble momento (before + after) es lo que hace tan poderoso al sistema de middlewares.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Un middleware sencillo para empezar<\/h2>\n\n\n\n<p>Mira este ejemplo cl\u00e1sico de logging:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public class RequestLoggingMiddleware\n{\n    private readonly RequestDelegate _next;\n    private readonly ILogger&lt;RequestLoggingMiddleware> _logger;\n\n    public RequestLoggingMiddleware(\n        RequestDelegate next,\n        ILogger&lt;RequestLoggingMiddleware> logger)\n    {\n        _next = next;\n        _logger = logger;\n    }\n\n    public async Task InvokeAsync(HttpContext context)\n    {\n        _logger.LogInformation(\n            \"Iniciando request {Method} {Path}\", \n            context.Request.Method, \n            context.Request.Path);\n\n        await _next(context);\n\n        _logger.LogInformation(\n            \"Finalizando request {Method} {Path} \u2192 {StatusCode}\", \n            context.Request.Method, \n            context.Request.Path, \n            context.Response.StatusCode);\n    }\n}<\/code><\/pre>\n\n\n\n<p>\u00bfQu\u00e9 pasa aqu\u00ed?<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Recibe la petici\u00f3n &#8211;> escribe log \u201centrando\u201d<\/li>\n\n\n\n<li>Llama a <code>await _next(context)<\/code> &#8211;> le da el control al siguiente<\/li>\n\n\n\n<li>Cuando todo lo dem\u00e1s termina (incluso el endpoint), escribe el log \u201csaliendo\u201d<\/li>\n<\/ol>\n\n\n\n<p>Ese patr\u00f3n <strong>antes &#8211;> next &#8211;> despu\u00e9s<\/strong> es literalmente la base de casi todo lo que ves en el pipeline.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">C\u00f3mo (y d\u00f3nde) se registran los middlewares<\/h2>\n\n\n\n<p>Todo se configura en <code>Program.cs<\/code> (o en el viejo <code>Startup.cs<\/code> si est\u00e1s en versiones m\u00e1s antiguas).<\/p>\n\n\n\n<p>La forma directa:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.UseMiddleware&lt;RequestLoggingMiddleware>();<\/code><\/pre>\n\n\n\n<p>Pero lo normal es usar los m\u00e9todos de extensi\u00f3n que ya trae el framework:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.UseRouting();\napp.UseAuthentication();\napp.UseAuthorization();\napp.UseStaticFiles();           \/\/ si sirve archivos est\u00e1ticos\napp.MapControllers();           \/\/ o MapRazorPages, MapMinimalApis, etc.<\/code><\/pre>\n\n\n\n<p>El orden en el que los escribes es cr\u00edtico.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El orden del pipeline: donde la mayor\u00eda se equivoca<\/h2>\n\n\n\n<p>Un error muy frecuente es subestimar el orden.<\/p>\n\n\n\n<p>Ejemplo cl\u00e1sico de configuraci\u00f3n incorrecta:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.UseAuthorization();     \/\/ \u00a1mal!\napp.UseAuthentication();<\/code><\/pre>\n\n\n\n<p>\u00bfPor qu\u00e9 est\u00e1 mal? Porque <code>UseAuthorization<\/code> necesita saber <strong>qui\u00e9n es el usuario<\/strong> (es decir, ya debe haber pasado por autenticaci\u00f3n).<\/p>\n\n\n\n<p>El orden correcto m\u00e1s com\u00fan ser\u00eda algo as\u00ed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.UseExceptionHandler(\"\/error\");     \/\/ lo m\u00e1s temprano posible (captura excepciones abajo)\napp.UseHsts();                         \/\/ seguridad\napp.UseHttpsRedirection();             \/\/ redirecci\u00f3n https\n\napp.UseStaticFiles();                  \/\/ archivos est\u00e1ticos primero (r\u00e1pido y barato)\n\napp.UseRouting();\n\napp.UseCors();                         \/\/ si usas CORS\napp.UseAuthentication();\napp.UseAuthorization();\n\napp.MapControllers();                  \/\/ o MapRazorPages, endpoints de Minimal API, etc.<\/code><\/pre>\n\n\n\n<p>Si pones <code>UseAuthentication<\/code> despu\u00e9s de <code>UseAuthorization<\/code>, mucha gente se encontrar\u00e1 con 401\/403 fantasma que cuestan horas de debug.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Las tres formas principales de agregar middleware<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">1. <code>Use<\/code> &#8211;> el m\u00e1s usado: Permite ejecutar c\u00f3digo antes y despu\u00e9s.<\/h3>\n\n\n\n<pre class=\"wp-block-code\"><code>app.Use(async (context, next) =>\n{\n    \/\/ c\u00f3digo antes\n    await next();\n    \/\/ c\u00f3digo despu\u00e9s\n});<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">2. <code>Run<\/code> &#8211;> termina el pipeline<\/h3>\n\n\n\n<p>No llama a nadie m\u00e1s. Se usa para respuestas terminales (por ejemplo, un 404 custom o un \u201cHello World\u201d de prueba).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.Run(async context =>\n{\n    await context.Response.WriteAsync(\"\u00a1Hola desde el final del pipeline!\");\n});<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">3. <code>Map<\/code> (y <code>MapWhen<\/code>) \u2192 sub-pipelines condicionales<\/h3>\n\n\n\n<p>\u00datil para aplicar middlewares solo a ciertas rutas.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>app.Map(\"\/api\", api =>\n{\n    api.UseMiddleware&lt;ApiSpecificLoggingMiddleware>();\n    api.UseMiddleware&lt;RateLimitingMiddleware>();\n});<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Dependency Injection dentro de los middlewares<\/h2>\n\n\n\n<p>Una de las cosas m\u00e1s bonitas de ASP.NET Core es que puedes inyectar servicios directamente en el constructor del middleware:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public class RequestLoggingMiddleware\n{\n    private readonly RequestDelegate _next;\n    private readonly ILogger&lt;RequestLoggingMiddleware> _logger;\n    private readonly ICurrentUserService _userService;\n\n    public RequestLoggingMiddleware(\n        RequestDelegate next,\n        ILogger&lt;RequestLoggingMiddleware> logger,\n        ICurrentUserService userService)\n    {\n        _next = next;\n        _logger = logger;\n        _userService = userService;\n    }\n\n    \/\/ ...\n}<\/code><\/pre>\n\n\n\n<p>El contenedor de DI resuelve todo autom\u00e1ticamente. Eso te permite meter logging, m\u00e9tricas, servicios de negocio, etc., sin tener que hacer malabares.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Errores t\u00edpicos que veo una y otra vez<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Olvidarte de llamar a <code>await next()<\/code> &#8211;> la petici\u00f3n se queda colgada ah\u00ed<\/li>\n\n\n\n<li>Poner consultas pesadas o lecturas de archivos grandes dentro de un middleware &#8211;> se ejecuta en cada request<\/li>\n\n\n\n<li>Resolver servicios manualmente con <code>context.RequestServices.GetService&lt;\u2026>() <\/code>&#8211;> mejor siempre constructor injection<\/li>\n\n\n\n<li>Armar mal el orden (sobre todo con routing\/auth\/authorization)<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">\u00bfCu\u00e1ndo vale la pena crear un middleware personalizado?<\/h2>\n\n\n\n<p>No todo tiene que ser middleware, pero brillan cuando quieres resolver cross-cutting concerns que afectan muchas (o todas) las peticiones:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Logging estructurado de requests\/responses<\/li>\n\n\n\n<li>Manejo global de excepciones<\/li>\n\n\n\n<li>M\u00e9tricas y Prometheus\/OpenTelemetry<\/li>\n\n\n\n<li>Auditor\u00eda de acciones sensibles<\/li>\n\n\n\n<li>Rate limiting \/ throttling<\/li>\n\n\n\n<li>Agregar headers de seguridad (CSP, Permissions-Policy, etc.)<\/li>\n\n\n\n<li>Correlation IDs para tracing distribuido<\/li>\n<\/ul>\n\n\n\n<p>Si la l\u00f3gica debe ejecutarse \u201csiempre\u201d o \u201ccasi siempre\u201d, es un gran candidato a middleware.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Para terminar: \u00bfpor qu\u00e9 deber\u00edas prestarle atenci\u00f3n al pipeline?<\/h2>\n\n\n\n<p>El pipeline de middleware es el mecanismo central con el que ASP.NET Core procesa peticiones HTTP.<\/p>\n\n\n\n<p>Cuando lo entiendes bien:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>vitas comportamientos raros que cuestan d\u00edas de debug<\/li>\n\n\n\n<li>Escribes c\u00f3digo m\u00e1s limpio y predecible<\/li>\n\n\n\n<li>Aprovechas al m\u00e1ximo la inyecci\u00f3n de dependencias y la extensibilidad del framework<\/li>\n\n\n\n<li>Puedes crear soluciones reutilizables que luego usas en varios proyectos<\/li>\n<\/ul>\n\n\n\n<p>T\u00f3mate un rato para revisar el orden de tu <code>Program.cs<\/code> actual. Muchas veces con solo reordenar 3\u20134 l\u00edneas se soluciona un problema que parec\u00eda imposible.<\/p>\n\n\n\n<p>Y si alguna vez te preguntan \u201c\u00bfpor qu\u00e9 mi autenticaci\u00f3n no funciona?\u201d o \u201c\u00bfd\u00f3nde rayos se est\u00e1 colgando esta petici\u00f3n?\u201d, ya sabes por d\u00f3nde empezar: <strong>el pipeline<\/strong>.<\/p>\n\n\n\n<p>Sigamos codificando.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Cuando una petici\u00f3n HTTP llega a tu aplicaci\u00f3n ASP.NET Core, no va directo al controlador ni salta m\u00e1gicamente a tu endpoint de Minimal API. Antes tiene que atravesar algo que casi todos usamos a diario, pero que mucha gente no termina de entender del todo: el pipeline de middleware. Cada pieza de middleware que est\u00e1 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7],"tags":[26,11],"class_list":["post-207","post","type-post","status-publish","format-standard","hentry","category-desarrollo","tag-net","tag-c"],"_links":{"self":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts\/207","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/comments?post=207"}],"version-history":[{"count":0,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/posts\/207\/revisions"}],"wp:attachment":[{"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/media?parent=207"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/categories?post=207"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/juredev.com\/blog\/wp-json\/wp\/v2\/tags?post=207"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}