Autor en Google+
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 ;)

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

Hace unos días fue lanzada la nueva versión de ASP.NET Core basada en el flamante .NET 5, que incluye un buen número de novedades para los desarrolladores Blazor, algunas de ellas bastante jugosas.

En este post vamos a ver por encima las que creo que son más destacables en esta nueva entrega:

  1. .NET 5 y mejoras de rendimiento
  2. CSS Isolation
  3. JavaScript Isolation
  4. Virtualización
  5. InputRadio & InputRadioGroup
  6. Soporte para IAsyncDisposable
  7. Soporte para upload de archivos
  8. Control del foco
  9. Parámetros de ruta catch-all
  10. Protected browser storage
  11. Prerenderización WebAssembly
  12. Lazy loading de áreas de aplicación en Web Assembly
  13. Otros cambios y mejoras
¡Vamos allá!

1. .NET 5 y mejoras de rendimiento

Mejoras de rendimiento en renderización de rejillas en Blazor WebAssemblyBlazor aprovecha ventajas introducidas en .NET 5, tanto en el servidor como en el navegador. Esto, acompañado de bastantes mejoras internas, hace que las aplicaciones construidas sobre este framework se ejecuten de forma mucho más rápida. Se habla incluso de llegar cuadruplicar el rendimiento en las renderizaciones, o duplicar o triplicar el rendimiento en Blazor WebAssembly.

Al mismo tiempo, hay mejoras en componentes como <Virtualize>, que comentaremos algo más adelante, pensados para optimizar escenarios específicos como la visualización de listas extensas.

2. CSS Isolation

Esta es una de las novedades más interesantes de esta nueva entrega, pues permite que cada componente defina sus propios estilos en archivos .css de forma aislada e independiente del resto, muy al estilo de lo que ya permiten otros frameworks SPA como Angular o React. De esta forma, conseguiremos tener archivos .css más pequeños, fáciles de leer y escribir, y centrados exclusivamente en un componente, el lugar del clásico y monstruoso site.css con los estilos de toda la aplicación.

En la práctica, lo único que tendremos que hacer para definir los estilos de un componente llamado MyComponent.razor es crear un archivo con su mismo nombre terminado en .css, como en MyComponent.razor.css. En él podremos introducir reglas de estilo que sólo se aplicarán al componente (aunque opcionalmente podemos indicar que también queremos que se apliquen a sus sub-componentes).

3. JavaScript Isolation

Se trata de otra característica interesante, que en esta ocasión nos ayudará a la hora de mejorar la organización del código JavaScript que usamos a través de los mecanismos de interoperación de Blazor.

La idea es que el código JS podremos ahora organizarlo en módulos estándar y exportar las funciones a utilizar desde Blazor, como en el siguiente ejemplo:

export function showPrompt(message, defaultValue) {
    return prompt(message, defaultValue);
}

Luego, ya desde Blazor, podremos cargar de forma dinámica dicho módulo realizando una llamada a InvokeAsync() con el identificador "import" (una palabra reservada para estos efectos) y la ruta del archivo .js donde está definido. Tras ello, podremos llamar a sus funciones exportadas de la forma habitual:

@page "/test"
@inject IJSRuntime Js  

<button @onclick="Click">Get name</button>
<p>Your name: @name</p>

@code {
    string name = "Unknown";
    IJSObjectReference module;

    async Task Click()
    {
        name = await module.InvokeAsync<string>("prompt", "Your name?", "Peter");
    }
    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
        {
            _module = await Js.InvokeAsync<IJSObjectReference>(
                       "import", "./modules/MyPrompt.js"
            );
        }
    }
}

4. Virtualización de listas

Como ocurre con otros frameworks similares, la renderización de listas extensas en el navegador (hablamos de miles de elementos) supone siempre un problema de rendimiento.

Con objeto de mejorar la experiencia de usuario, Blazor 5 proporciona el nuevo componente <Virtualize> que permite virtualizar una lista de elementos para que sólo sean renderizados aquellos que realmente son visibles en pantalla. Por ejemplo, si tenemos una tabla con un listado de 10.000 filas pero en el navegador sólo caben 20, sólo éstas serán renderizadas, y conforme el usuario vaya scrolleando se irán renderizando el resto bajo demanda.

Por ejemplo, sin usar virtualización, la siguiente página, que muestra 100.000 números, normalmente tardará varios segundos en renderizarse:

@page "/numbers"
<h3>Numbers</h3>
<ul>
    @foreach(var n in numbers)
    {
        <li>@n</li>
    }
</ul>

@code {
    List<int> numbers = Enumerable.Range(1, 100_000).ToList();
}

Usar la virtualización es tan sencillo como sustituir el @foreach por el componente <Virtualize>, facilitándole la colección de elementos a mostrar. Este simple cambio hará que la carga de la lista sea virtualmente inmediata:

@page "/numbers"
<h3>Numbers</h3>
<ul>
    <Virtualize Items="numbers">
        <li>@context</li>
    </Virtualize>
</ul>

@code {
    List<int> numbers = Enumerable.Range(1, 100_000).ToList();
}

La virtualización va mucho más allá, y permite incluso obtener los elementos bajo demanda con objeto de mantener en memoria colecciones completas, de las cuales sólo serán mostradas algunos elementos.

5. Los componentes InputRadio y InputRadioGroup

En las versiones anteriores de Blazor, los controles de formulario tipo radio teníamos que implementarlos de forma manual, porque el framework no los incluía de serie. Con esta nueva entrega, podemos usar <InputRadioGroup> para agrupar <InputRadio> y bindear sus valores a un campo del viewmodel asociado al formulario:

<InputRadioGroup @bind-Value="Color">
    <label><InputRadio Value=0 /> Red</label>
    <label><InputRadio Value=1 /> Green</label>
    <label><InputRadio Value=2 /> Blue</label>
</InputRadioGroup>

6. Soporte de IAsyncDisposable

Los componentes pueden ahora implementar IAsyncDisposable para liberar sus recursos de forma asíncrona.

@page "/"
@implements IAsyncDisposable
...
@code {
    public ValueTask DisposeAsync()
    {
        // Dispose your resources here
    }
}

7. Soporte para upload de archivos

Pues sí, sorprendentemente, la versión anterior de Blazor no incluía de serie mecanismos para facilitar la selección y subida de archivos al servidor, aunque era compensado por el paquete NuGet BlazorInputFile que el propio Steve Sanderson, creador de Blazor, había publicado para nosotros.

La nueva versión de Blazor ya incluye <InputFile>, un componente que actúa como wrapper del clásico <input type="file"> de HTML, pero que, además, proporciona mecanismos para acceder desde C# tanto a las propiedades como contenidos de los archivos seleccionados.

Un ejemplo de uso sencillo podría ser el siguiente:

@page "/Upload"
<InputFile OnChange="@OnInputFileChange" />
@if (file != null)
{
    <p>
        File: @file.Name, Size: @file.Size,
        Last Modified: @file.LastModified
    </p>
    <button @onclick="UploadAsync">Upload</button>
}

@code {
    IBrowserFile file;
    Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        file = e.File;
        return Task.CompletedTask;
    }
    Task UploadAsync()
    {
        if(file == null) return;
        using var stream = file.OpenReadStream();

        // Hacer algo con el stream, como:
        // - salvarlo a un archivo en disco (Blazor Server)
        // - enviarlo a una API (Blazor WebAssembly)

        file = null;
    }
}

8. Control de posición del foco

La clase ElementReference, utilizada para referenciar elementos del DOM mediante la directiva @ref, dispone ahora del método FocusAsync(), que, como su nombre indica, sirve para pasar el foco al elemento indicado.

En el siguiente componente podemos ver un ejemplo de uso, en una interfaz en la que incluimos tres <input>, y un botón:

<h3>Focus Demo</h3>

<input />
<input />
<input @ref="third" />
<button @onclick='()=>third.FocusAsync()'>Set focus</button>

@code {
    ElementReference third;

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        if (firstRender)
            await third.FocusAsync();
    }
}

Inicialmente, el foco se encontrará en el tercer cuadro de texto, porque así lo inicializamos en el OnAfterRenderAsync(). Aparte, la pulsación del botón hará que en cualquier momento el foco vuelva a dicho cuadro de texto.

9. Parámetros de ruta catch-all

El sistema de routing de Blazor soporta ahora el uso de parámetros de ruta catch-all, cuya misión es capturar todos los segmentos de la dirección URL desde la posición donde se encuentran hasta el final.

Por ejemplo, imaginad que tenemos una página definida con una ruta como la siguiente:

@page "/catalog/{*data}"
...
@code {
    [Parameter] public string Data { get; set; }
}

Si accedemos a la página con una ruta como "/catalog/computers/laptop/lenovo", la propiedad Data contendrá "computers/laptops/lenovo". Esto puede ser interesante para casos en los que el esquema de rutas sea complejo o flexible, y queramos procesarlas nosotros de forma manual.

10. Protected browser storage

Se trata de un mecanismo exclusivo para Blazor Server, que permite guardar información de forma segura tanto en el local storage como en el session storage del navegador. De hecho, es muy similar al uso de estos mecanismos habituales del lado cliente, pero la diferencia es que la encriptación y desencriptación se realizan en el servidor, mientras el almacenamiento se realizará en el lado cliente.

Para usarlo, basta con instalar el paquete NuGet Microsoft.AspNetCore.Components.Web.Extensions, e inyectar los servicios ProtectedLocalStorage o ProtectedSessionStorate (dependiendo del almacenamiento que queramos usar, local o de sesión) y usarlos para leer o escribir valores:

@inject ProtectedLocalStorage ProtectedLocalStorage
...
private async Task IncrementCount()
{
    await ProtectedLocalStorage.SetAsync("count", ++currentCount);
}

Seguro que os preguntaréis por qué no podemos utilizar esto en Blazor WebAssembly. Y la respuesta es sencilla: sería inseguro de todas formas. Al encriptarlo desde el lado servidor, las claves nunca viajan al lado cliente y por tanto podemos estar seguros de lo que está almacenado no puede ser manipulado; sin embargo, si todo el proceso se realiza en cliente, es imposible asegurar este extremo.

11. Prerenderización en Blazor WebAssembly

La prerenderización era un mecanismo interesante de Blazor Server para adelantar la llegada de contenidos al navegador gracias a una renderización inicial de los componentes Blazor en el servidor, en el momento de generar la página contenedora de la aplicación SPA, y antes de establecerse el circuito con SignalR. Además de dar a los usuarios una mayor sensación de velocidad de carga, era positivo desde el punto de vista del SEO.

La nueva versión de Blazor lo hace posible también en el hosting WebAssembly con un nuevo tipo de render-mode en el tag helper <component>:

<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />

12. Lazy loading de áreas de aplicación en Web Assembly

Como sabemos, las aplicaciones Blazor WebAssembly deben descargarse al browser por completo antes de iniciar su ejecución; y no sólo hablamos del runtime de .NET y ensamblados de la BCL, sino también de los ensamblados de nuestra aplicación y sus dependencias (por ejemplo, los paquetes NuGet externos que utilicemos).

Para aliviar un poco este peso inicial, la nueva versión de Blazor permite cargar bajo demanda ensamblados con áreas de la aplicación, de forma que sólo serán descargados cuando vayan a ser utilizados. Una vez cargados, quedarán en memoria para usos posteriores y no será necesario descargarlos de nuevo.

A grandes rasgos, la idea consiste básicamente en:

  • Especificar qué ensamblados no deben descargarse inicialmente, al cargar la aplicación, introduciendo algunos elementos en el archivo .csproj.
  • Luego, tomar el control del sistema de routing en el momento en que se detecta un cambio de ruta para, si nos interesa, cargar el ensamblado que contenga la página de destino antes de mostrarla.

Podéis ver un ejemplo completo de uso en esta página de la documentación oficial.

13. Otros cambios y mejoras

Obviamente, en una major release como esta hay muchos más detalles, aunque ya son de menor impacto, como:

  • Eliminación de Internet Explorer 11 y Edge Legacy (es decir, el Edge anterior a saltar a Chromium) de la lista de browsers soportados, tanto en Blazor Server como Blazor WebAssembly.
  • Eliminación de espacios no significativos del marcado en tiempo de compilación.
  • Introducción de hot reload para agilizar el ciclo de desarrollo.
  • Posibilidad de especificar nombres de clases personalizadas para los estados de validación de controles de formulario.
  • Introducción de analizadores de compatibilidad de código para Blazor WebAssembly.
  • Introducción de IComponentActivator, para tomar el control en la activación (instanciación) de componentes.
  • Introducción del parámetro DisplayName en algunos controles de formulario.
  • Soporte para el evento ontoggle.

Y con esto, creo que hemos visto lo más destacable de esta nueva entrega, pero, ¡no dudéis en decírmelo si véis algo que consideréis importante y haya pasado por alto!

Publicado en Variable not found.

Estos contenidos se publican bajo una licencia de Creative Commons Licencia Reconocimiento-No comercial-Compartir bajo la misma licencia 3.0 España de Creative Commons

4 Comentarios:

Anónimo dijo...

Cuando puedas escribite algo sobre cómo migrar de 3.1 a 5, tanto en desarrollo como en producción.
Gracias
Saludos

José María Aguilar dijo...

Hola!

Es algo casi trivial, y viene muy bien detallado en la documentación oficial:

https://docs.microsoft.com/en-us/aspnet/core/migration/31-to-50?view=aspnetcore-5.0&tabs=visual-studio#update-the-target-framework

Saludos!

Crahun dijo...

Quizas te haya faltado hablar sobre Microsoft.AspNetCore.Components.Web.Extensions por los componentes Link, Title y Meta que aunque sigan en preview son muy necesarios para hacernos la vida más fácily evitarnos javascript.

Muy buen artículo, y con ejemplos. Gracias!

José María Aguilar dijo...

Hola!

Sí, no quise incluirlos en la lista por que están en preview y aún no forman parte oficialmente de la plataforma, pero son muy interesantes. Hay un post en el horno para ello ;)

Muchas gracias por comentar!

Artículos relacionados: