0

Asp.Net 5 Release Candidate 1

En el post anterior vimos que las novedades de .Net, ahora le toca a Asp.Net. En esta versión hay muchas novedades para ver, muchas orientadas a blazor, pero todas muy interesantes.

  • Mejoras en el rendimiento de Blazor WebAssembly
  • Virtualización de componentes Blazor
  • Procesamiento previo de Blazor WebAssembly
  • Analizador de compatibilidad de navegadores para Blazor WebAssembly
  • Referencias de objetos y aislamiento de Blazor JavaScript
  • Soporte de entrada de archivos Blazor
  • Atributos de clase de validación personalizados en Blazor
  • Soporte de Blazor para ontoggle event
  • Modelo vinculante DateTime cómo UTC
  • Control de activación de la clase de inicio
  • Open API Specification (Swagger) activada de forma predeterminada en proyectos de API de ASP.NET Core
  • Mejor experiencia F5 para proyectos de API ASP.NET Core
  • Invocaciones de hub paralelo de SignalR
  • Se agregó compatibilidad con Messagepack en el cliente Java de SignalR 
  • Opciones específicas de endpoint de Kestrel mediante configuración

En esta versión se realizaron mejoras muy significativas en los tiempos de ejecución de Blazor WebAssembly enfocada a la representación de interfaz de usuario complejas. Se ha logrado hasta un 2 a 3 veces más rendimiento en la mayoría de los escenarios.

Esas mejoras están relacionadas con las mejoras del intérprete .Net IL. Veamos el cuadro comparativo siguiente:

Otro punto de mejora es cuando tenemos muchos componentes, como por ejemplo grillas bastante complejas, para mejorar esto se implementaron 3 tipos diferentes de implementación:

  • Fast Grid: una implementación mínima y altamente optimizada de una cuadrícula
  • Plain Table: una implementación mínima pero no optimizada de una cuadrícula.
  • Complex Grid: una implementación máxima, no optimizada de una cuadrícula, que utiliza una amplia gama de características de Blazor a la vez, con la intención deliberada de crear un caso negativo para el renderizador.

Blazor component virtualization

Es posible mejorar más el rendimiento  de la representación de componentes utilizando el nuevo soporte de virtualización integrado. La virtualización es una técnica para limitar la representación de la interfaz de usuario a solo las partes que están visibles actualmente, como cuando tiene una lista o tabla larga con muchas filas y solo un pequeño subconjunto es visible en un momento dado. Blazor en .NET 5 agrega un nuevo componente Virtualize que puede usarse para agregar fácilmente virtualización a sus componentes.

Una lista típica o un componente basado en tabla podría usar un bucle foreach de C # para representar cada elemento de la lista o cada fila de la tabla:

@foreach (var employee in employees)
{
    <tr>
        <td>@employee.FirstName</td>
        <td>@employee.LastName</td>
        <td>@employee.JobTitle</td>
    </tr>
}

Supongamos que la lista tiene miles de registros, repocesarla llevaría bastante tiempo generando un retraso importante en UI. En lugar del foreach podemos usar el nuevo componente Virtualize para afectar solo las filas visibles:

<Virtualize Items="employees" Context="employee">
    <tr>
        <td>@employee.FirstName</td>
        <td>@employee.LastName</td>
        <td>@employee.JobTitle</td>
    </tr>
</Virtualize>

El componente Virtualize calculará cuántos elementos renderizar según la altura del contenedor y el tamaño de los elementos renderizados.

Si no deseamos cargar todos los elementos en la memoria, podemos especificar un ItemsProvider:

<Virtualize ItemsProvider="LoadEmployees" Context="employee">
     <tr>
        <td>@employee.FirstName</td>
        <td>@employee.LastName</td>
        <td>@employee.JobTitle</td>
    </tr>
</Virtualize>

Un proveedor de elementos es un método delegado que recupera de forma asincrónica los elementos solicitados a pedido. El proveedor de elementos recibe una ItemsProviderRequest, que especifica el número requerido de elementos a partir de un índice de inicio específico. El proveedor de artículos luego recupera los artículos solicitados de una base de datos u otro servicio y los devuelve como ItemsProviderResult <TItem> junto con un recuento del número total de artículos disponibles. El proveedor de elementos puede optar por recuperar los elementos con cada solicitud o almacenarlos en caché para que estén disponibles.

async ValueTask<ItemsProviderResult<Employee>> LoadEmployees(ItemsProviderRequest request)
{
    var numEmployees = Math.Min(request.Count, totalEmployees - request.StartIndex);
    var employees = await EmployeesService.GetEmployeesAsync(request.StartIndex, numEmployees, request.CancellationToken);
    return new ItemsProviderResult<Employee>(employees, totalEmployees);
}

El solicitar elementos de una fuente de datos remota puede llevar algún tiempo, también tenemos la opción de mostrar loading hasta que los datos del elemento estén disponibles.

<Virtualize ItemsProvider="LoadEmployees" Context="employee">
    <ItemContent>
        <tr>
            <td>@employee.FirstName</td>
            <td>@employee.LastName</td>
            <td>@employee.JobTitle</td>
        </tr>
    </ItemContent>
    <Placeholder>
        <tr>
            <td>Loading...</td>
        </tr>
    </Placeholder>
</Virtualize>

Blazor WebAssembly prerendering

Ahora las etiquetas tienen 2 modos de renderizados adicionales para prerenderizado un componente desde la aplicación:

  • WebAssemblyPrerendered: procesa previamente el componente en HTML estático e incluye un marcador para una aplicación Blazor WebAssembly para utilizarlo posteriormente para hacer que el componente sea interactivo cuando se carga en el navegador.
  • WebAssembly: renderiza un marcador para que una aplicación Blazor WebAssembly lo use para incluir un componente interactivo cuando se carga en el navegador. El componente no se procesa previamente. Esta opción simplemente facilita la representación de diferentes componentes de Blazor WebAssembly en diferentes páginas cshtml.

Los pasos a seguir son los siguientes:

  • Alojamos la aplicación Blazor WebAssembly en una aplicación ASP.NET Core.
  • Reemplazamos el archivo index.html estático predeterminado en el proyecto del cliente con un archivo _Host.cshtml en el proyecto del servidor.
  • Actualizamos la lógica de inicio del servidor para volver a _Host.cshtml en lugar de index.html (similar a cómo se configura la plantilla de Blazor Server).
  • Actualizamos _Host.cshtml para usar el asistente de etiqueta de componente para preprocesar el componente raíz de la aplicación:
<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />

Si tenemos parámetros serializables, es posible pasarlos como parámetros a las etiquetas de renderización del tipo WebAssembly. Deben ser serializados para que puedan transferirse sin problemas al cliente.

<component type="typeof(Counter)" render-mode="WebAssemblyPrerendered" param-IncrementAmount="10" />

No es necesario configurar componente por componente, podemos hacerlo en páginas o vistas, podemos utilizarlos como componentes raíz en la aplicación o agregar nuestras propias etiquetas de marcador en la página; Blazor se encarga de eso por nosotros.

Browser compatibility analyzer for Blazor WebAssembly

.NET 5 se incluye un analizador de compatibilidad de plataformas que nos avisara cuando nuestra aplicación use API que no sean compatibles con sus plataformas de destino. Para las aplicaciones Blazor WebAssembly, verificará que las API sean compatibles con los navegadores.

Para activar la comprobación de compatibilidad  debemos modificar el archivo de proyecto de la siguiente manera:

<SupportedPlatform Include="browser" />

Podemos indicar, opcionalmente, que una API en particular no es compatible con los navegadores aplicando UnsupportedOSPlatformAttribute:

[UnsupportedOSPlatform("browser")]
private static string GetLoggingDirectory()
{
    // ...
}

Referencias de objetos y aislamiento de Blazor JavaScript

Ahora podemos aislar JavaScript como módulos estándar de JavaScript. Beneficios: el JavaScript importado ya no contamina el espacio de nombres global, los consumidores de las bibliotecas y componentes no necesitan importan manualmente el Javascript relacionado:

export function showPrompt(message) {
    return prompt(message, 'Type anything here');
}

Podemos agregar este módulo JavaScript a nuestra biblioteca .NET como un archivos estático (wwwroo  exampleJsInterop.js) y luego importar el módulo a nuestro código .NET utilizando el servicio IJSRuntime:

var module = await jsRuntime.InvokeAsync<JSObjectReference>("import", "./_content/MyComponents/exampleJsInterop.js");

IJSRuntime importa el módulo como JSObjectReference, que representa una referencia a un objeto JavaScript del código .NET. Podemos utilizar JSObjectReference para invocar funciones de JavaScript exportadas desde el módulo, veamos:

public async ValueTask<string> Prompt(string message)
{
    return await module.InvokeAsync<string>("showPrompt", message);
}

Blazor file input support

Tenemos disponible un componente InputFile para mejorar la carga de archivos. Este componente representa el tag html file. Podemos agregar el atributo “múltiple” para seleccionar más de un archivo. Cuando el usuario selecciona uno o más archivos, el componente InputFile desencadena un evento OnChange y pasa un InputFileChangeEventArgs que proporciona acceso a los archivos seleccionados y detalles sobre cada uno.

<InputFile OnChange="OnInputFileChange" multiple />

<div class="image-list">
    @foreach (var imageDataUrl in imageDataUrls)
    {
        <img src="@imageDataUrl" />
    }
</div>

@code {
    IList<string> imageDataUrls = new List<string>();

    async Task OnInputFileChange(InputFileChangeEventArgs e)
    {
        var imageFiles = e.GetMultipleFiles();

        var format = "image/png";
        foreach (var imageFile in imageFiles)
        {
            var resizedImageFile = await imageFile.RequestImageFileAsync(format, 100, 100);
            var buffer = new byte[resizedImageFile.Size];
            await resizedImageFile.OpenReadStream().ReadAsync(buffer);
            var imageDataUrl = $"data:{format};base64,{Convert.ToBase64String(buffer)}";
            imageDataUrls.Add(imageDataUrl);
        }
    }
}

Atributos de clase de validación personalizados en Blazor

Podemos especificar nombres de clases de validación personalizados. Esto es útil cuando integramos CSS o Bootstrap. Para hacerlo debemos crear una clase derivada de FieldCssClassProvider y configurarla en la instancia de EditContext.

var editContext = new EditContext(model);
editContext.SetFieldCssClassProvider(new MyFieldClassProvider());

// ...

class MyFieldClassProvider : FieldCssClassProvider
{
    public override string GetFieldCssClass(EditContext editContext, in FieldIdentifier fieldIdentifier)
    {
        var isValid = !editContext.GetValidationMessages(fieldIdentifier).Any();
        return isValid ? "legit" : "totally-bogus";
    }
}

Soporte de Blazor para ontoggle event

Ahora tenemos soporte para este tipo de evento:

<div>
    @if (detailsExpanded)
    {
        <p>Read the details carefully!</p>
    }
    <details id="details-toggle" @ontoggle="OnToggle">
        <summary>Summary</summary>
        <p>Detailed content</p>
    </details>
</div>

@code {
    bool detailsExpanded;
    string message { get; set; }

    void OnToggle()
    {
        detailsExpanded = !detailsExpanded;
    }
}

Modelo vinculante DateTime cómo UTC

Ahora admite el enlace correcto de las cadenas de tiempo UTC con DateTime. Si la solicitud contiene una cadena de hora UTC (por ejemplo, https://example.com/mycontroller/myaction?time=2019-06-14T02%3A30%3A04.0576719Z), el enlace del modelo lo vinculará correctamente a una fecha y hora UTC sin el necesidad de una mayor personalización.

Control de activación de la clase de inicio

Tenemos nueva sobrecarga en UseStartup que nos proporciona un método de tipo Fabric para controlar la activación de la clase de inicio.  Es útil si deseamos pasar parámetros adicionales al inicio que se inicializan junto con el host.

public class Program
{
    public static async Task Main(string[] args)
    {
        var logger = CreateLogger();
        var host = Host.CreateDefaultBuilder()
            .ConfigureWebHost(builder =>
            {
                builder.UseStartup(context => new Startup(logger));
            })
            .Build();

        await host.RunAsync();
    }
}

Open API Specification (Swagger) activada de forma predeterminada en proyectos de API de ASP.NET Core

La plantilla de API de ASP.NET Core en RC1 viene precableada con una dependencia de NuGet en Swashbuckle, este paquete NuGet de código abierto que utilizamos para documentar de API abierta de forma dinámica. Swashbuckle hace esto mediante la introspección de los controladores de API y la generación del documento de API abierta en tiempo de ejecución, o en el tiempo de compilación mediante la CLI de Swashbuckle.

<ItemGroup>
    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.5.1" />
</ItemGroup>

No debemos olvidar que debemos configurarlo en el método ConfigureServices en el Starups.cs

public void ConfigureServices(IServiceCollection services)
{

    services.AddControllers();
    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApplication1", Version = "v1" });
    });
}

En el método Configure también está preconfigurado iniciar middleware Swashbuckle. Esto activa el proceso de generación de documentos y activa la página de la IU de Swagger de forma predeterminada en el modo de desarrollo. De esta manera, tenemos la seguridad de que la experiencia lista para usar no expondrá accidentalmente la descripción de su API cuando se publique en producción.

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
        app.UseSwagger();
        app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1"));
    }

    // ...
}

Azure API Management Import

Cuando nuestros proyectos de la API de ASP.NET están conectados para generar una API abierta de esta manera, la experiencia de publicación de Visual Studio 2019 versión 16.8 Preview 2.1 ofrece automáticamente un paso adicional en el flujo de publicación. Los que usamos Azure API Management tenemos la oportunidad de importar automáticamente nuestras API a Azure API Management durante el flujo de publicación.

Mejor experiencia F5 para proyectos de API ASP.NET Core

Ahora, Open API habilitado de forma predeterminada, se mejoró la experiencia de F5 para los desarrolladores de API web. Con .NET 5 RC1, la plantilla de API web viene preconfigurada para cargar la página de la interfaz de usuario de Swagger. La página IU de Swagger nos proporciona la documentación que ha agregado para nuestra  API, pero nos permite probar sus API con un solo clic.

Invocaciones de hub paralelo de SignalR

SignalR ahora es capaz de manejar hub paralelo. Podemos cambiar el comportamiento predeterminado y permitir que los clientes invoquen más de un método hub a la vez.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSignalR(options =>
    {
        options.MaximumParallelInvocationsPerClient = 5;
    });
}

Tenemos compatibilidad con Messagepack en el cliente Java de SignalR 

Tenemos un nuevo paquete, com.microsoft.signalr.messagepack, que nos brinda compatibilidad con el paquete de mensajes al cliente java de SignalR. Para utilizar el protocolo de concentrador de paquete de mensajes, debemos agregar .withHubProtocol (new MessagePackHubProtocol ()) al constructor de conexiones.

HubConnection hubConnection = HubConnectionBuilder.create("http://localhost:53353/MyHub")
        .withHubProtocol(new MessagePackHubProtocol())
        .build();

Opciones específicas de endpoint de Kestrel mediante configuración:

Se agregó soporte para configurar las opciones específicas de endpoint Kestrel a través de la configuración. Incluyen los protocolos Http utilizados, los protocolos TLS utilizados, el certificado seleccionado y el modo de certificado de cliente.

Podemos configurar qué certificado se selecciona en función del nombre de servidor especificado como parte de la extensión de Indicación de nombre de servidor (SNI) del protocolo TLS según lo indicado por el cliente. La configuración de Kestrel también admite un prefijo comodín en el nombre de host.

El siguiente ejemplo le muestra cómo especificar un punto final específico mediante un archivo de configuración:

{
  "Kestrel": {
    "Endpoints": {
      "EndpointName": {
        "Url": "https://*",
        "Sni": {
          "a.example.org": {
            "Protocols": "Http1AndHttp2",
            "SslProtocols": [ "Tls11", "Tls12"],
            "Certificate": {
              "Path": "testCert.pfx",
              "Password": "testPassword"
            },
            "ClientCertificateMode" : "NoCertificate"
          },
          "*.example.org": {
            "Certificate": {
              "Path": "testCert2.pfx",
              "Password": "testPassword"
            }
          },
          "*": {
            // At least one subproperty needs to exist per SNI section or it
            // cannot be discovered via IConfiguration
            "Protocols": "Http1",
          }
        }
      }
    }
  }
}

Conclusiones

Muchas novedades. Si todavía no entraste en Blazor, es un buen momento para hacerlo, está tomando mucha fuerza y es un gran foco del equipo del Asp.Net. En el próximo post, veremos las novedades de Entity Framework.

Fernando Sonego

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *