Como no podía faltar, tenemos las nueva preview para Asp.Net Core 7, en este caso, la número 6. Los cambios más importantes son: Solicitar middleware de descompresión, Middleware de almacenamiento en caché de salida, Actualizaciones al middleware de limitación de velocidad, Soporte de Kestrel para WebSockets sobre HTTP/2, Mejoras en el rendimiento de Kestrel en máquinas de alto núcleo, Soporte para registrar encabezados de solicitud adicionales en W3CLogger, Plantillas de proyecto de Blazor vacías, Compatibilidad con System.Security.Cryptography en WebAssembly, Los elementos personalizados de Blazor ya no son experimentales, Componente experimental QuickGrid para Blazor, gRPC JSON transcodificando parámetros de varios segmentos, Soporte de MapGroup para más métodos de extensión
Solicitar middleware de descompresión
Este nuevo middleware de descompresión de solicitudes utiliza el encabezado HTTP de codificación de contenido para identificar y descomprimir automáticamente las solicitudes con contenido comprimido para que el servidor no tenga que manejar esto por sí mismo.
Se agrega mediante el método de extensión UseRequestDecompression en IApplicationBuilder y el método de extensión AddRequestDecompression para IServiceCollection.
La compatibilidad con Brotli (br), Deflate (desinflar) y GZip (gzip) se incluye de fábrica. Podemos agregar otras codificaciones de contenido registrando una clase de proveedor de descompresión personalizada, que implementa la interfaz IDecompressionProvider, junto con un valor de encabezado de codificación de contenido en RequestDecompressionOptions.
Middleware de almacenamiento en caché de salida
El almacenamiento en caché de resultados es un nuevo middleware que no ayudará a almacenar los resultados de nuestra aplicación web y servirlos desde un caché en lugar de ir a buscarlos cada vez, lo que mejora el rendimiento y libera recursos para otras actividades. Debemos usar el método de extensión AddOutputCache en IServiceCollection y el método de extensión UseOutputCache en IApplicationBuilder. Luegoo, podemos comenzar a configurar el almacenamiento en caché de salida en sus puntos finales. Aquí hay un ejemplo simple del uso del almacenamiento en caché de salida en un punto final que devuelve marcas de tiempo.
app.MapGet("/notcached", () => DateTime.Now.ToString());
app.MapGet("/cached", () => DateTime.Now.ToString()).CacheOutput();
Las solicitudes enviadas a «/notcached» verán la hora actual. Pero el extremo «/cached» usa la extensión .CacheOutput(), por lo que cada solicitud a «/cached después de la primera obtendrá una respuesta en caché (el tiempo devuelto no se actualizará después de la primera solicitud).
Hay muchas formas más avanzadas de personalizar el almacenamiento en caché de resultados. El siguiente ejemplo muestra cómo se puede usar VaryByQuery para controlar el almacenamiento en caché en función de un parámetro de consulta:
// Cached entries will vary by culture, but any other additional query
// is ignored and returns the same cached content.
app.MapGet("/query", () => DateTime.Now.ToString()).CacheOutput(p => p.VaryByQuery("culture"));
Con esta configuración, el punto final «/query» almacenará en caché su salida únicamente por valor del parámetro cultural. Las solicitudes de /query?culture=en obtendrán una respuesta en caché y las solicitudes de /query?culture=es obtendrán una respuesta en caché diferente.
Además de variar según las cadenas de consulta, también hay soporte para variar las respuestas almacenadas en caché por encabezados (VaryByHeader) o por valores personalizados (VaryByValue).
El middleware de almacenamiento en caché de salida ha incorporado protección para algunos errores comunes del almacenamiento en caché. Imaginemos la situación en la que tenemos un servicio web ocupado y revoca/invalida una entrada de caché popular. Cuando eso sucede, puede haber muchas solicitudes para esa entrada al mismo tiempo, y debido a que la respuesta no está en el caché en ese momento, el servidor tendría que procesar cada una de esas solicitudes.
El bloqueo de recursos es una característica del almacenamiento en caché de resultados que evita que el servidor se vea abrumado por estas «estampidas de caché». Para ello, solo permite que se procese una solicitud del recurso, mientras que el resto espera a que se actualice la entrada de caché. Una vez que eso sucede, todas las solicitudes en espera se pueden atender desde la memoria caché, lo que evita que el servidor se vea inundado por trabajo redundante.
Actualizaciones al middleware de limitación de velocidad
El middleware de limitación de velocidad ahora admite la limitación de velocidad en puntos finales específicos, que se pueden combinar con un limitador global que se ejecuta en todas las solicitudes. A continuación se muestra un ejemplo que agrega un TokenBucketRateLimiter a un punto final y usa un ConcurrencyLimiter global:
var coolEndpointName = "coolPolicy";
// Define endpoint limiters and a global limiter.
// coolEndpoint will be limited by a TokenBucket Limiter with a max permit count of 6 and a queue depth of 3.
var options = new RateLimiterOptions()
.AddTokenBucketLimiter(coolEndpointName, new TokenBucketRateLimiterOptions(3, QueueProcessingOrder.OldestFirst, 6, TimeSpan.FromSeconds(10), 1))
// The global limiter will be a concurrency limiter with a max permit count of 10 and a queue depth of 5.
options.GlobalLimiter = PartitionedRateLimiter.Create<HttpContext, string>(context =>
{
return RateLimitPartition.CreateConcurrencyLimiter<string>("globalLimiter", key => new ConcurrencyLimiterOptions(10, QueueProcessingOrder.NewestFirst, 5));
});
app.UseRateLimiter(options);
app.MapGet("/", () => "Hello World!").RequireRateLimiting(coolEndpointName);
También se admite agregar políticas de limitación de tarifas personalizadas a través de los nuevos métodos AddPolicy en RateLimiterOptions.
Soporte de Kestrel para WebSockets sobre HTTP/2
Los WebSockets se diseñaron originalmente para HTTP/1.1, pero desde entonces se han adaptado para funcionar en HTTP/2. El uso de WebSockets sobre HTTP/2 nos permite aprovechar las nuevas funciones, como la compresión de encabezados y la multiplexación, reduciendo el tiempo y los recursos necesarios al realizar varias solicitudes al servidor. Ese soporte ahora está disponible en Kestrel en todas las plataformas habilitadas para HTTP/2.
La negociación de la versión HTTP es automática en los navegadores y Kestrel, por lo que no se necesitan nuevas API. Debemos tener en cuenta que HTTP/2 WebSockets usa solicitudes CONNECT en lugar de GET, por lo que es posible que sus rutas y controladores necesiten una actualización.
SignalR y el cliente de JavaScript del navegador SignalR se han actualizado para admitir WebSockets a través de HTTP/2. El soporte también llegará pronto a ClientWebSocket y YARP.
Kestrel performance improvements on high core machines
Kestrel usa ConcurrentQueue para muchos propósitos. Un propósito es programar operaciones de E/S en el transporte de socket predeterminado. La partición de ConcurrentQueue en función del socket asociada a reducción de la contención y aumenta el rendimiento en máquinas con muchos núcleos.
En esta versión, el grupo de memoria de Kestrel se particiona de la misma manera que su cola de E/S, lo que genera una contención mucho menor y un mayor rendimiento en máquinas de alto núcleo. Se ve una mejora de RPS de más del 500% en la prueba comparativa de texto sin formato de TechEmpower en las máquinas virtuales ARM64 de 80 núcleos mencionadas anteriormente y una mejora de casi el 100 % en las máquinas virtuales AMD de 48 núcleos en nuestra prueba comparativa HTTPS JSON.
Soporte para registrar encabezados de solicitud adicionales en W3CLogger
Ahora podemos especificar encabezados de solicitud adicionales para registrar cuando usamos el registrador W3C llamando a AdditionalRequestHeaders() en W3CLoggerOptions:
services.AddW3CLogging(logging =>
{
logging.AdditionalRequestHeaders.Add("x-forwarded-for");
logging.AdditionalRequestHeaders.Add("x-client-ssl-protocol");
});
Plantillas de proyecto de Blazor vacías
Dos nuevas plantillas de proyecto para comenzar desde cero: «Blazor Server App Empty» y «Blazor WebAssembly App Empty». Estas plantillas vacías solo tienen una página de inicio muy básica, y también eliminamos Bootstrap para que pueda comenzar con el marco CSS que prefiera.
Desde la consola:
dotnet new blazorserver-empty
dotnet new blazorwasm-empty
Compatibilidad con System.Security.Cryptography en WebAssembly
.NET 6 admitía la familia SHA de algoritmos hash cuando se ejecutaba en WebAssembly. Ahora también, se permite más algoritmos criptográficos aprovechando SubtleCrypto cuando es posible y recurriendo a una implementación de .NET cuando no se puede usar SubtleCrypto. Los siguientes algoritmos son compatibles con WebAssembly: SHA1, SHA256, SHA384, SHA512, HMACSHA1, HMACSHA256, HMACSHA384 y HMACSHA512.
Los elementos personalizados de Blazor ya no son experimentales
Microsoft.AspNetCore.Components.CustomElements anteriormente experimental para crear elementos personalizados basados en estándares con Blazor ya no es experimental y ahora forma parte de la versión .NET 7.
Para crear un elemento personalizado con Blazor, debemos registrar un componente raíz de Blazor como un elemento personalizado como este:
options.RootComponents.RegisterCustomElement<Counter>("my-counter")
Luego puede usar este elemento personalizado con cualquier otro marco web:
<my-counter increment-amount="10"></my-counter>
Componente experimental QuickGrid para Blazor
QuickGrid es un nuevo componente experimental para Blazor para mostrar de manera rápida y eficiente datos en forma tabular. QuickGrid proporciona un componente de cuadrícula de datos simple para las necesidades más comunes, así como una arquitectura de referencia y una línea base de rendimiento para cualquiera que construya componentes de cuadrícula de datos de Blazor. Cómo usarlo:
dotnet add package Microsoft.AspNetCore.Components.QuickGrid --prerelease
<QuickGrid Items="@people">
<PropertyColumn Property="@(p => p.PersonId)" Sortable="true" />
<PropertyColumn Property="@(p => p.Name)" Sortable="true" />
<PropertyColumn Property="@(p => p.BirthDate)" Format="yyyy-MM-dd" Sortable="true" />
</QuickGrid>
@code {
record Person(int PersonId, string Name, DateOnly BirthDate);
IQueryable<Person> people = new[]
{
new Person(10895, "Jean Martin", new DateOnly(1985, 3, 16)),
new Person(10944, "António Langa", new DateOnly(1991, 12, 1)),
new Person(11203, "Julie Smith", new DateOnly(1958, 10, 10)),
new Person(11205, "Nur Sari", new DateOnly(1922, 4, 27)),
new Person(11898, "Jose Hernandez", new DateOnly(2011, 5, 3)),
new Person(12130, "Kenji Sato", new DateOnly(2004, 1, 9)),
}.AsQueryable();
}
Puede ver ejemplos de QuickGrid en acción en el sitio de demostración de QuickGrid para Blazor, que incluye ejemplos de: trabajar con orígenes de datos remotos, agregar columnas simples y con plantillas, ordenar datos, filtrar datos, paginar datos, virtualizar datos y aplicar estilos y temas.
QuickGrid está altamente optimizado y utiliza técnicas avanzadas para lograr un rendimiento de representación óptimo. QuickGrid es actualmente experimental, lo que significa que no es compatible oficialmente y no está comprometido a enviarse como parte de .NET 7 o cualquier versión futura de .NET en este momento.
gRPC JSON transcodificando parámetros de varios segmentos
La transcodificación gRPC JSON es una característica nueva en .NET 7 para convertir las API gRPC en API RESTful. Una novedad es la compatibilidad con parámetros de varios segmentos en las rutas de transcodificación gRPC JSON. Esta característica lleva la transcodificación de ASP.NET Core a la par con otras soluciones de transcodificación en el ecosistema gRPC. Ahora es posible configurar las API de gRPC para vincular propiedades a parámetros de varios segmentos.
Soporte de MapGroup para más métodos de extensión
Los grupos de rutas se introdujeron en la Preview 4. Permiten definir grupos de puntos finales con un prefijo de ruta común y un conjunto común de convenciones. WithTags(), WithDescription(), ExcludeFromDescription() y WithSummary() anteriormente solo tenían como objetivo RouteHandlerBuilder en lugar de la interfaz IEndpointConventionBuilder, lo que los hacía incompatibles con el RouteGroupBuilder devuelto por MapGroup().
A partir de estas versiones, estos métodos de extensión ahora tienen como objetivo IEndpointConventionBuilder, lo que los hace compatibles con los grupos de rutas. Esto fue posible gracias a un cambio en EndpointDataSource, pero la conclusión clave es que ahora puede llamar a WithOpenApi() y AddFilter() en grupos de rutas.
var todos = app.MapGroup("/todos")
.WithTags("todos", "some_other_tag")
.WithOpenApi()
.AddRouteHandlerFilter((endpointContext, next) =>
{
var logger = endpointContext.ApplicationServices.GetRequiredService<ILogger<Program>>();
return invocationContext =>
{
logger.LogInformation($"Received request for: {invocationContext.HttpContext.Request.Path}");
return next(invocationContext);
};
});
todos.MapGet("/{id}", GetTodo);
todos.MapGet("/", GetAllTodos).WithTags("get_all_todos");
todos.MapPost("/", CreateTodo).WithOpenApi(operation =>
{
operation.Summary = "Create a todo.";
return operation;
});
Conclusiones
Cuántas novedades en ASP.Net Core 7 para investigar. Y ni pensar en todas las que se pueden ver en el roadmap de esta versión. En próximos veremos que otras novedades tenemos esperando a el lanzamiento oficial en noviembre.