18:29 0 0
Como usar prerender y lazy loading en Blazor WebAssembly App

Como usar prerender y lazy loading en Blazor WebAssembly App

  DrUalcman |  octubre 282023

Hola compi, hoy quiero hablar de la funcionalidad que nos ofrede Blazor WebAseembly para evitar que se vea lo de cargando y además mejorar el SEO, ya que la primera carga de la aplicación se hará del lado del servidor, pero continuará la carga mientras el usuario navega, y así seguirá siendo una aplicación Blazor WebAseembly, pero con, aun más, una mejor experiencia del usuario.

Esto lo vamos a conseguir aplicando las técnicas de renderizado del lado del serviodr y la carga perezosa, o prerenser y lazy loading en sus términos en inglés. Comentar que hay varias fromas de hacerlo, yo, como me gustan las minimal apis, voy a utilizar una minimal api y un cliente Blazor Web Aseembly, pero comentar que en la documentación oficial de Microsoft, el ejemplo lo ponen con una aplicación Web Assembly Hosted. Así, con este post, tendréis un punto de vista diferente y, creo yo, más intenresante para mantener un desaclopamiento y un codigo más limpio.

Por otro lado, comentar que esta solución, si la quieres subir a un servicio en la nuve como Azure, no puede ser ya hosteada en una Static Web App, debes de utilizar el servicio de Web App, por lo que, si no queires sustos en la factura, asegurate de seleccionar algún servicio gratuito. O si eres como to, que ya tienes un Web App pues lo hosteas en el mismo Web Service App y listo, no te costará dinero adicional.

Empecemos. 

Creo que sobra decirlo, pero nuestro primer paso es crear dos projectos. Blazor WebAssembly App y otro Asp.Net Core Web Api y, en mi caso, utilizar Minimal Api, pero aclarar que se puede hacer con cualquier tipo de api.

Paso 1: Hacer una carga del lado del servidor. Pasito a pasito para no liarnos mucho

* Agregar el pague nuget Microsoft.AspNetCore.Components.WebAssembly.Server al proyecto de la Web Api
* Eliminar el archivo index.html del proyecto Blazor WebAssembly.
* Eliminar las líneas siguientes del archivo Program.cs en el proyecto Blazor WebAssembly:
builder.RootComponents.Add< App >("#app");
builder.RootComponents.Add< HeadOutlet >("head::after");

* Agregar referencia del proyecto Blazor WebAssembly al proyecto Web Api.

* Agregar un archivo _Host.cshtml (o el nombre que mas te guste) al proyecto de la Web Api. Normalmente se hubica dentro de una capeta llamanda Pages, pero esto tambien es opcional. Este archivo lo puedes conseguir si creas una Blazor WebAssembly Server App o lo descargas aqui.

* Cambiar el espacio de nombre al comienzo del archivo por el espacio de nombre del proyecto Web Api, con el nombre completo donde se encuentre tu archivo _Host.cshtml. Si lo almacentaste en la carpeta Pages, entonces será el nombre del proyecto.Pages.

* Agregar una directiva @using antes del @namespace para que pueda acceder ver la clase App generada por el archivo App.Razor del proyecto cliente Blazor WebAssembly.

* Actualizar los vínculos de los archivos estáticos con el nombre del proyecto cliente, que es donde ahora se guardan estos archivos.

< link href="[el nomrbe de tu proyecto Blazor WebAssembly cliente].styles.css" rel="stylesheet" / >

O cualquier otro que tengas alojado en la applicacion cliente.

* Actualizar el origen del codigo javascript ,si has copiado desde la plantilla Blazor WebAssembly Server, para que utilice la versión para WebAssembly.

Quitar:

< script src="_framework/blazor.server.js" >< /script >

Poner:

< script src="_framework/blazor.webassembly.js">< /script >
Muy importante ahora en la Web Api la debemos de configurar para que ejecute paginas estaticas, por lo que hay que hacer estos cambios en tu archivo Program.cs de la Web Api

* Agregar los servicios para el renderizado de paginas.

builder.Services.AddRazorPages();

* Agregar las lineas para utilizar archivos staticos, renderizar paginas.

app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.MapRazorPages();

* Y por último, y casi que lo más importante, hacer que lance la página por defecto, nuesto archivo _Host.cshtml

app.MapFallbackToPage("/_Host");

Si seleccionaste agregar un proyecto WebAssembly Hosted, sólo tienes que cambiar la línea que mapea a una página concreta, index.html, para que mapee a nuestra nueva página.

Muy importante: Si tus proyectos cliente y api, estan utilizando servicios comunes, debes de registrarlos en las dos aplicaciones. Siempre y cuando sean necesario al renderizar la página al acceder directametne a ella. Por ejemplo el HttpClient.

Si no registrar el HttpClient, cuando intens acceder a la página que hace la llamada a la api con los datos de ejemplo de las temperaturas, utilizando la plantilla de Blazor WebAssembly App, no la Empty, podrás comporbar que recibes un mensaje de error como el que te muestro a continuación.

Error en HttpClient si no se registran los servicios correctamente en los dos proyectos, cliente y api

Pero esto tiene facil solución, aqui os pongo un ejemplo de como solucionarlo, puede que haya otras formas, pero esta es la que he aplicado yo. En la api hay que registrar el un cliente HttpClient.
builder.Services.AddSingleton(sp =>
        {
            // Get the address that the app is currently running at
            var server = sp.GetRequiredService< IServer >();
            var addressFeature = server.Features.Get< IServerAddressesFeature >();
            string baseAddress = addressFeature.Addresses.First();
            return new HttpClient { BaseAddress = new Uri(baseAddress) };
        });

Y ya. Si ahora ejecutas no debería de haber problemas. Hasta aqui ya hemos convertido nuestra applicación Blazor WebAssembly para que utilice la posibilidad de hacer una carga inicial del lado del servidor.

Paso2: Utilizar la carga perezosa para mejorar el rendimiento de la Blazor WebAssembly App.

Bueno, esta segunda parte, por así decirlo, es mucho más pesada y con más trabajo que hacer, pero, nos brindará muchas ventajas. Y con la llegada de las nuevas utilidades en .NET 8 es posible que esta sección, o la anterior, cambien un poco, por lo que cuando investigue un poco más afondo estas nuevas opciones de carga que trae NET 8, ya crearé otro post, para mantener mi post mensual, y hablaremos de eso que se viene nuevo. Comencemos con el lazy loading.

Comentar que esta funcionalidad está pensada para librerías que se usen sólo en ciertas páginas, yo intenté hacer una versión para que fuera para todas las librerias no indispensables en la carga inicial, pero aun estoy trabajando en ello. Si quieres ver el experimiento, puedes clonar este  Repositorio de pruebas de prerender., ya te aviso que no esta documentado y puede ser algo confuso. Y como es de peubas e investigación, tampoco se ha siguido un codigo muy ¨"impio" que digamos.

Habilizar LazyLoading

Primer paso: Tenemos que tener claro que archivos DLL vamos ha pasar que esten en carga perezosa. Recordar que si estas haciendo referencia a proyectos dentro de la misma solución, el nomrbe del proyecto es el nombre del archivo DLL que debes de configurar como carga perezosa.

Para ello debes de hacer doble click sobre el nombre del proyecto Blazor WebAssembly para que se habran las propiedades del mismo.

Una vez que tenemos abrierto el archivo de proyecto agregaremos un ItemGroup que va a contener los archivos DLL que deseamos pasar a carga perezosa utilizando el tag BlazorWebAssemblyLazyLoad.

< ItemGroup >
  < BlazorWebAssemblyLazyLoad Include="{ASSEMBLY NAME}.dll" / >
< /ItemGroup >

Segundo paso: Hay que agregar un código a nuestro archivo App.Razor para que cuando se ejecute la navegación, cargue los assemblies de los archivos DLL que hemos configurado previamente en el archivo de proyecto.

Aclarar que, ya que deseamos que estos archivos solo se cargen cuando sean requeridos en una página en concreta, deberemos de poner un IF o un SWITCH para gestionar que página es la que se está haciendo la navegación, y así, cargar los archivos DLL que necesitemos para esa página. Cosa que en un código limpio, no es muy buena acción y es en lo que estoy trabajando.

Bueno, como decía, debemos de manejar el evento OnNavigateAsync del compomente Route para gestionar que página se está haciendo la navecación para, de esta forma, incluir en los assemblies del componente Route una lista con los nuevos asemblies que se van a utilizar. He aqui el ejemplo de código que no ofrece Microsoft.

@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.WebAssembly.Services
@using Microsoft.Extensions.Logging
@inject LazyAssemblyLoader AssemblyLoader
@inject ILogger Logger
    OnNavigateAsync="@OnNavigateAsync">
    ...

@code {
    private async Task OnNavigateAsync(NavigationContext args)
    {
        try
           {
               if (args.Path == "{PATH}")
               {
                   var assemblies = await AssemblyLoader.LoadAssembliesAsync(
                       new[] { {LIST OF ASSEMBLIES} });
               }
           }
           catch (Exception ex)
           {
               Logger.LogError("Error: {Message}", ex.Message);
           }
    }
}

Pasp 3: Posibles problemas y como solucionarlos

Cuando se accede a la página por primera vez, se renderiza en el servidor y se devuelve al cliente, pero cuando los archivos de Blazor WebAssembly están completamente cargados, la página vuelve a hacer un renderizado. Por lo tanto puedes observar qeu si acceder al enlace /fetchdata la página hace como un reload y los datos, si tienes desabilitada la cache, cambian.

Esto tiene una solución bastante sencilla, que la si conoces bien el ciclo de vida de las páginas Blazor es muy probable que des con la solución sin seguir leyendo estas líneas. Pero si no, no te preocupes, que aqui te cuento como solucionarlo.

La página hace una carga inicial del lado del servidor, hasta ahi todos ya lo sabíamos, pero resulta qeu cuando termina de cargar todos los archivos necesarios para que se ejecute 100% como Web Assembly, entonces éste coge el palo del relevo y necesita recargar la página, pero, por suerte, tenemos un evento en el ciclo de vida que es OnAfterRender y OnAfterRenderAsync, sólo debemos de cambiar el código que obtiene los datos de la api a OnAfterRenderAsync, ya que es un proceso asyncrono, y controlar que solo se ejecute en el primer renderizado de la página. Asi quedaría la página de /fetchdata una vez cambiado el código.

@page "/fetchdata"
@inject HttpClient Http
[... HTML ...]
@code {
    private WeatherForecast[]? forecasts;

    protected override async Task OnAfterRenderAsync(bool firstRender) 
    {
        if(firstRender)
        {
            forecasts = await Http.GetFromJsonAsync("sample-data/weather.json");
await InvokeAsync(StateHasChanged);
        }
    }
    public class WeatherForecast
    {
        public DateOnly Date { get; set; }
        public int TemperatureC { get; set; }
        public string? Summary { get; set; }
        public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    }
}
Por simplicidad he copiado sólo la parte de código ya que en el HTML nada cambia.


Por ahora yo no he encontrado mas problemas, si ves algún otro ponlo en los comentarios y a ver si encontramos como solucionarlos. En la documentación microsoft comenta que si hay autenticación de por medio, debes de tener otras consideraciones, pero eso ya te lo dejo a ti. Yo no me he visto en la necesidad aun y no lo he probado.

Conclusiones

Bueno, esta opción del prerender me parece muy interesando y hay que investigas mas afondo, uniendo estas técnicas junto con otras podremos tener unas App Blazor WebAssembly muy robustas y, sobre todo, muy amigables para el usuario final.


Happy coding


#blazor #csharp #web



0 Guest reviews

 
 
 
Error en HttpClient si no se registran los servicios correctamente en los dos proyectos, cliente y api

File