Saltar al contenido

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript... y lo que venga ;)

17 años online

el blog de José M. Aguilar

Inicio El autor Contactar

Artículos, tutoriales, trucos, curiosidades, reflexiones y links sobre programación web
ASP.NET Core, MVC, Blazor, SignalR, Entity Framework, C#, Azure, Javascript...

¡Microsoft MVP!
martes, 29 de marzo de 2022
ASP.NET Core

A veces, sobre todo en aplicaciones muy grandes, con las definiciones de rutas muy complejas o cuando nos toca analizar aplicaciones ajenas, puede ser interesante saber qué punto del código está procesando una petición determinada, ya sea un endpoint definido usando Endpoint Routing o Minimal APIs o bien sea una acción de un controlador MVC.

En este post vamos a ver cómo conseguirlo de forma muy sencilla, mediante la implementación un pequeño middleware que, insertado en el pipeline, añadirá en el encabezado información sobre el handler que generó la respuesta de la petición actual.

Un pequeño middleware que "espía" el sistema de routing

Como probablemente sabréis, el sistema de routing de ASP.NET Core está implementado en dos middlewares:

  • EndpointRoutingMiddleware, que, analiza el path de las peticiones entrantes y, en función de las rutas registradas en la aplicación, establece el endpoint al que se delegará su proceso.
  • EndpointMiddleware, que obtiene el endpoint establecido por el middleware anterior y se encarga de ejecutarlo.

En ASP.NET Core 5 y anteriores estos middlewares debíamos introducirlos manualmente en el pipeline, pero gracias a las nuevas abstracciones y convenciones introducidas por minimal hosting en ASP.NET Core 6, esto se realiza de forma automática.

Por tanto, si introducimos en el pipeline un middleware personalizado que tome el control justo después de que EndpointRoutingMiddleware haya realizado su trabajo, podremos conocer qué parte del código va a procesar cada una de las peticiones entrantes.

En el siguiente código vemos cómo conseguirlo; podéis observar que usamos el extensor GetEndpoint() del contexto HTTP para conocer el endpoint seleccionado por EndpointRoutingMiddleware, e incluimos su nombre descriptivo en un encabezado personalizado de la respuesta:

app.Use(async (ctx, next) =>
{
    var endpoint = ctx.GetEndpoint();
    if (endpoint != null && !ctx.Response.HasStarted)
    {
        ctx.Response.Headers.Add("X-Handled-By", endpoint.DisplayName);
    }
    await next(ctx);
});

Fijaos que usamos Response.HasStarted para determinar si la respuesta ya ha comenzado. En caso contrario, no sería posible añadir encabezados porque quizás estos ya fueron enviados al cliente.

Vamos a ver qué nos encontraremos en los encabezados en varios escenarios. Para ello, creamos un proyecto ASP.NET Core MVC y sustituimos su Program.cs por el siguiente código, donde implementamos mapeos de ruta con distintos tipos de handlers:

Verbo Ruta Tipo de Manejador Manejador
GET /inline Delegado en línea Delegado definido en app.MapGet()
GET /local Función local LocalFunctionHandler()
GET /static Método estático Handlers.StaticMethodHandler()
GET /instance Método de instancia Handlers.StaticMethodHandler()
GET /home/index Acción MVC HomeController.Index()
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();

// Definición de endpoints
app.MapGet("/inline", () => "Hello world!");
app.MapGet("/local", LocalFunctionHandler);
app.MapGet("/static", Handlers.StaticMethodHandler);
app.MapGet("/instance", new Handlers().InstanceMethodHandler);
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

// Middleware "espía"
app.Use(async (ctx, next) =>
{
    var endpoint = ctx.GetEndpoint();
    if (endpoint != null && !ctx.Response.HasStarted)
    {
        ctx.Response.Headers.Add("X-Handled-By", endpoint.DisplayName);
    }
    await next(ctx);
});

app.Run();

// Handlers
string LocalFunctionHandler() => "Hello from a function!";
class Handlers
{
    public static string StaticMethodHandler() => "Hello from a function!";
    public string InstanceMethodHandler() => "Hello from a function!";
}

Si enviamos peticiones hacia los endpoints definidos, podremos observar los siguientes valores en el encabezado X-Handled-By:

Petición Valor de encabezado
GET /inline X-Handled-By: HTTP: GET /inline
GET /local X-Handled-By: HTTP: GET /local => LocalFunctionHandler
GET /static X-Handled-By: HTTP: GET /static => StaticMethodHandler
GET /instance X-Handled-By: HTTP: GET /instance => InstanceMethodHandler
GET /home/index X-Handled-By: MyWebApplication.Controllers.HomeController.Index (MyAspNetMvcApplication)

Se puede ver que cuando el manejador es una acción MVC, el encabezado muestra el nombre del proyecto, controlador y acción que ejecutó la petición. Por tanto, podemos saber directamente dónde se encuentra su código.

En el caso de los endpoints es más complicado, porque DisplayName no ofrece más información, pero es más que suficiente para saber desde dónde se gestionan. En todo caso, al aparecer el verbo y la ruta sabremos que es un mapeo, y en caso de estar delegado a métodos o funciones locales tendremos conoceremos cuáles son.

Publicado en Variable not found.

Aún no hay comentarios, ¡sé el primero!