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, 17 de noviembre de 2020
Blazor

Un alumno del curso de Blazor que tutorizo en CampusMVP me exponía problema bastante curioso con el que se había topado al implementar una página que, simplificando el escenario, venía a ser como la siguiente:

@page "/sum/{a:decimal}/{b:decimal}"

<h1>The sum is: @(A+B)</h1>

@code {
    [Parameter] public decimal A { get; set; }
    [Parameter] public decimal B { get; set; }
}

Esta página funcionaba correctamente con números enteros: podíamos acceder a "/sum/1/2" y veíamos el resultado correcto ("The sum is: 3"). Esto era así tanto accediendo a través del sistema de routing de Blazor (es decir, usando un link hacia esa ruta desde dentro de la aplicación) como accediendo directamente a la página introduciendo la ruta en la barra de direcciones del navegador.

Sin embargo, el problema surgía al usar valores decimales. Si desde dentro de la aplicación pulsábamos un enlace hacia "/sum/1.1/2.2", el resultado obtenido era correcto ("The sum is: 3.3"). Sin embargo, si en ese momento refrescábamos esa página, o bien accedíamos directamente a ella introduciendo la URL en el navegador, el servidor retornaba un error 404.

Extraño, ¿no?

Acotando el problema

Tras reproducir el escenario, vi claro que el problema no estaba en el sistema de routing de Blazor, pues los accesos a la página usando links funcionaban correctamente.

El error 404 lo estaba retornando el servidor cuando cargábamos la página por completo, es decir, refrescando o accediendo directamente a la URL con el navegador. Como consecuencia, el origen no estaba en Blazor sino más abajo, en ASP.NET Core.

Y aunque podría parecer lo contrario en un principio, el tema no tenía que ver con decimales o algo parecido, sino con la propia ruta que estamos usando en la petición. Es decir, el problema era exactamente el mismo con el siguiente componente si hacíamos una petición a "/hello/john.smith":

@page "/hello/{name}"

<h1>Hello: @Name</h1>
@code {
    [Parameter] public string Name { get; set;}
}

Es decir, este comportamiento ocurre cuando el último fragmento de la ruta de acceso a la página contiene un punto ".", como en "1.3" o "john.smith".

Esto podemos comprobarlo si añadimos a la ruta cualquier fragmento adicional, de forma que el valor con punto no sea el último fragmento, como en el siguiente ejemplo; en este caso, peticiones como "/john.smith/hello" funcionará sin problemas:

@page "/{name}/hello"

¿Por qué ocurre esto? Y sobre todo, ¿cómo lo solucionamos?

Pues básicamente porque si la ruta de acceso termina por un fragmento que contiene un punto, el framework piensa que se trata de una petición a un archivo estático, y, según la configuración por defecto, no ejecuta el fallback hacia _Host.cshtml.

El culpable se encuentra en la llamada a MapFallbackToPage() que encontramos en el método Configure() de la clase Startup:

app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub();
    endpoints.MapFallbackToPage("/_Host");
});

En principio, esa línea mapearía un fallback para que cualquier ruta que no haya sido configurada en otro endpoint sea procesada por la página Pages/_Host.cshtml. El problema es que internamente esa línea equivale a la siguiente:

endpoints.MapFallbackToPage("{*path:nonfile}", "/_Host");

Como se puede ver, el fallback mapea una ruta catch-all para asociarla a la página _Host, pero le añade la restricción nonfile, lo que indica que no se tendrán en cuenta peticiones que aparentemente vayan dirigidas a un recurso estático.

De esta forma, cualquiera de las peticiones que hacíamos más arriba, como "/sum/1.1/2.2" o "/hello/john.smith" no superaban la restricción y, por tanto, no se hacía el fallback hacia la página host de la aplicación Blazor. Además, dado que no existía otro endpoint para dichas URLs, la única opción para el sistema era retornar un 404, indicando que el recurso no había podido ser localizado.

Para solucionarlo, simplemente debemos sustituir el mapeo del fallback, eliminando la restricción nonfile de la siguiente forma:

app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub();
    // endpoints.MapFallbackToPage("/_Host");
    endpoints.MapFallbackToPage("{*path}", "/_Host");
});

¡Espero que os sea de ayuda!

Publicado en Variable not found.

2 Comentarios:

MontyCLT dijo...

Hola José. Si se hace esto, ¿podría entonces acceder a ficheros estáticos guardados en wwwroot sin problema? ¿O se mapearían hacía la página de fallback?

¿Hay alguna opción para que la aplicación Blazor se cargue en una ruta en concreta? De forma que pueda poner example.com/blazor para ir a ella, y por supuesto a sus componentes enrutados con example.com/blazor/sum/1.1/2.2

¿Quizás añadiendo un prefijo en MapFallbackPage()?

Muchas gracias.

José María Aguilar dijo...

Hola,

En principio, una petición a un archivo existente debería cazarlo antes el middleware StaticFiles, por lo que no debería ser un problema.

Respecto a tu segunda pregunta, sí, de hecho es el motivo de que siempre expreses las rutas como relativas en el interior de tu aplicación. Puedes leer más sobre esto aquí: https://docs.microsoft.com/es-es/aspnet/core/blazor/host-and-deploy/?view=aspnetcore-5.0&tabs=visual-studio#app-base-path

Saludos!