Novedades de ASP.NET Core 7.0

En este artículo, se resaltan los cambios más importantes de ASP.NET Core 7.0, con vínculos a la documentación pertinente.

Middleware de limitación de velocidad en ASP.NET Core

El middleware Microsoft.AspNetCore.RateLimiting proporciona middleware de limitación de velocidad. Las aplicaciones configuran directivas de limitación de velocidad y, a continuación, colocan las directivas en los puntos de conexión. Para obtener más información, consulte Middleware de limitación de velocidad en ASP.NET Core.

La autenticación usa un esquema único como DefaultScheme

Como parte del trabajo para simplificar la autenticación, cuando solo hay un único esquema de autenticación registrado, se usa automáticamente como DefaultScheme y no es necesario especificarlo. Para obtener más información, consulte DefaultScheme.

MVC y Razor Pages

Compatibilidad con modelos que aceptan valores NULL en vistas de MVC y Razor Pages

Se admiten modelos de vista o página que aceptan valores NULL para mejorar la experiencia al usar la comprobación de estado NULL con aplicaciones ASP.NET Core:

@model Product?

Enlazar con IParsable<T>.TryParse en controladores de API y MVC

La API IParsable<TSelf>.TryParse admite el enlace de valores de parámetros de acción del controlador. Para obtener más información, consultes Enlace con IParsable<T>.TryParse.

En versiones de ASP.NET Core anteriores a 7, la cookie validación del consentimiento usa el valor cookieyes para indicar el consentimiento. Ahora puede especificar el valor que representa el consentimiento. Por ejemplo, podría usar true en lugar de yes.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.Configure<CookiePolicyOptions>(options =>
{
    options.CheckConsentNeeded = context => true;
    options.MinimumSameSitePolicy = SameSiteMode.None;
    options.ConsentCookieValue = "true";
});

var app = builder.Build();

Para obtener más información, consulte Personalización del valor de consentimiento de cookie.

Controladores de API

Enlace de parámetros con DI en controladores de API

El enlace de parámetros para las acciones de controladores de API enlaza parámetros mediante la inserción de dependencias cuando el tipo está configurado como servicio. Esto significa que ya no es necesario aplicar explícitamente el atributo [FromServices] a un parámetro. En el código siguiente, ambas acciones devuelven la hora:

[Route("[controller]")]
[ApiController]
public class MyController : ControllerBase
{
    public ActionResult GetWithAttribute([FromServices] IDateTime dateTime) 
                                                        => Ok(dateTime.Now);

    [Route("noAttribute")]
    public ActionResult Get(IDateTime dateTime) => Ok(dateTime.Now);
}

En casos aislados, la inserción automática de dependencias puede interrumpir las aplicaciones que tienen un tipo en la DI que también se acepta en el método de las acciones de controladores de API. No es habitual tener un tipo en la DI y como argumento en una acción del controlador de API. Para deshabilitar el enlace automático de los parámetros, establezca DisableImplicitFromServicesParameters.

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();
builder.Services.AddSingleton<IDateTime, SystemDateTime>();

builder.Services.Configure<ApiBehaviorOptions>(options =>
{
    options.DisableImplicitFromServicesParameters = true;
});

var app = builder.Build();

app.MapControllers();

app.Run();

En ASP.NET Core 7.0, los tipos de inserción de dependencias se comprueban al iniciar la aplicación con IServiceProviderIsService a fin de determinar si un argumento de una acción del controlador de API procede de la inserción de dependencias o de otros orígenes.

El mecanismo nuevo para deducir el origen del enlace de los parámetros de la acción del controlador de API utiliza estas reglas:

  1. Nunca se sobrescribe una propiedad BindingInfo.BindingSource.
  2. BindingSource.Services se asigna a un parámetro de tipo complejo, registrado en el contenedor de la inserción de dependencias.
  3. BindingSource.Body se asigna a un parámetro de tipo complejo, no registrado en el contenedor de la inserción de dependencias.
  4. BindingSource.Path se asigna a un parámetro con un nombre que aparece como un valor de ruta en cualquier plantilla de ruta.
  5. Todos los demás parámetros son BindingSource.Query.

Nombres de propiedad JSON en errores de validación

De forma predeterminada, cuando se produce un error de validación, la validación del modelo genera un ModelStateDictionary con el nombre de propiedad como la clave de error. Algunas aplicaciones, como las aplicaciones de una sola página, se benefician del uso de nombres de propiedad JSON para errores de validación generados a partir de las API web. El código siguiente configura la validación para usar SystemTextJsonValidationMetadataProvider con el fin de emplear nombres de propiedad JSON:

using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new SystemTextJsonValidationMetadataProvider());
});

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

El código siguiente configura la validación para usar NewtonsoftJsonValidationMetadataProvider con el fin de emplear nombres de propiedad JSON al usar Json.NET:

using Microsoft.AspNetCore.Mvc.NewtonsoftJson;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers(options =>
{
    options.ModelMetadataDetailsProviders.Add(new NewtonsoftJsonValidationMetadataProvider());
}).AddNewtonsoftJson();

var app = builder.Build();

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Para obtener más información, vea Usar nombres de propiedad JSON en errores de validación.

API mínimas

Filtros en las aplicaciones de API mínimas

Los filtros de API mínimas permiten a los desarrolladores implementar lógica de negocios que admita:

  • La ejecución de código antes y después del controlador de ruta.
  • La inspección y modificación de parámetros proporcionados durante una invocación del controlador de ruta.
  • La interceptación del comportamiento de respuesta de un controlador de ruta.

Los filtros pueden ser útiles en los escenarios siguientes:

  • Validación de los parámetros de solicitud y el cuerpo que se envían a un punto de conexión.
  • Registro de información sobre la solicitud y la respuesta.
  • Validación de que una solicitud tiene como destino una versión de API compatible.

Para obtener más información, consulte Filtros en las aplicaciones de API mínimas.

Enlace de matrices y valores de cadena desde encabezados y cadenas de consulta

En ASP.NET 7, se admite el enlace de cadenas de consulta a una matriz de tipos primitivos, matrices de cadena y StringValues:

// Bind query string values to a primitive type array.
// GET  /tags?q=1&q=2&q=3
app.MapGet("/tags", (int[] q) =>
                      $"tag1: {q[0]} , tag2: {q[1]}, tag3: {q[2]}");

// Bind to a string array.
// GET /tags2?names=john&names=jack&names=jane
app.MapGet("/tags2", (string[] names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

// Bind to StringValues.
// GET /tags3?names=john&names=jack&names=jane
app.MapGet("/tags3", (StringValues names) =>
            $"tag1: {names[0]} , tag2: {names[1]}, tag3: {names[2]}");

El enlace de cadenas de consulta o valores de encabezado a una matriz de tipos complejos se admite cuando el tipo tiene TryParse implementado. Para obtener más información, consulte la sección sobre cómo enlazar matrices y valores de cadena desde encabezados y cadenas de consulta.

Para obtener más información, consulte Agregar resumen o descripción del punto de conexión.

Enlazar el cuerpo de la solicitud como Stream o PipeReader

El cuerpo de la solicitud puede enlazarse como Stream o PipeReader para admitir sin problemas los escenarios en los que el usuario tiene que procesar datos y:

  • Almacenar los datos en Blob Storage o ponerlos en cola en un proveedor de colas.
  • Procesar los datos almacenados con un proceso de trabajo o una función en la nube.

Por ejemplo, los datos pueden ponerse en cola en Azure Queue Storage o almacenarse en Azure Blob Storage.

Para obtener más información, consulte Enlazar el cuerpo de la solicitud como Stream o PipeReader.

Nuevas sobrecargas Results.Stream

Hemos introducido nuevas sobrecargas Results.Stream para dar cabida a escenarios que necesitan acceso al flujo de respuesta HTTP subyacente sin almacenamiento en búfer. Estas sobrecargas también mejoran los casos en los que una API transmite datos al flujo de respuesta HTTP, como desde Azure Blob Storage. En el ejemplo siguiente se usa ImageSharp para devolver un tamaño reducido de la imagen especificada:

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Processing;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapGet("/process-image/{strImage}", (string strImage, HttpContext http, CancellationToken token) =>
{
    http.Response.Headers.CacheControl = $"public,max-age={TimeSpan.FromHours(24).TotalSeconds}";
    return Results.Stream(stream => ResizeImageAsync(strImage, stream, token), "image/jpeg");
});

async Task ResizeImageAsync(string strImage, Stream stream, CancellationToken token)
{
    var strPath = $"wwwroot/img/{strImage}";
    using var image = await Image.LoadAsync(strPath, token);
    int width = image.Width / 2;
    int height = image.Height / 2;
    image.Mutate(x =>x.Resize(width, height));
    await image.SaveAsync(stream, JpegFormat.Instance, cancellationToken: token);
}

Para más información, consulte los ejemplos de Stream

Resultados con tipo para las API mínimas

En .NET 6, la interfaz IResult se introdujo para representar los valores devueltos de las API mínimas que no usan la compatibilidad implícita con JSON para serializar el objeto devuelto en la respuesta HTTP. La clase estática Results se usa para crear distintos objetos IResult que representan diferentes tipos de respuestas. Por ejemplo, establecer el código de estado de respuesta o redirigir a otra dirección URL. Sin embargo, los tipos de marco de trabajo de implementación IResult devueltos por estos métodos eran internos, lo que dificultaba comprobar el tipo específico IResult devuelto de los métodos en una prueba unitaria.

En .NET 7, los tipos que implementan IResult son públicos, lo que permite las aserciones de tipos al realizar pruebas. Por ejemplo:

[TestClass()]
public class WeatherApiTests
{
    [TestMethod()]
    public void MapWeatherApiTest()
    {
        var result = WeatherApi.GetAllWeathers();
        Assert.IsInstanceOfType(result, typeof(Ok<WeatherForecast[]>));
    }      
}

Mejora de la capacidad de prueba unitaria para controladores de ruta mínimos

Ahora, los tipos de implementación IResult están disponibles públicamente en el espacio de nombres Microsoft.AspNetCore.Http.HttpResults. Los tipos de implementación IResult pueden usarse para efectuar pruebas unitarias de controladores de ruta mínimos al usar métodos con nombre en lugar de lambdas.

El siguiente código usa la clase Ok<TValue>:

[Fact]
public async Task GetTodoReturnsTodoFromDatabase()
{
    // Arrange
    await using var context = new MockDb().CreateDbContext();

    context.Todos.Add(new Todo
    {
        Id = 1,
        Title = "Test title",
        Description = "Test description",
        IsDone = false
    });

    await context.SaveChangesAsync();

    // Act
    var result = await TodoEndpointsV1.GetTodo(1, context);

    //Assert
    Assert.IsType<Results<Ok<Todo>, NotFound>>(result);

    var okResult = (Ok<Todo>)result.Result;

    Assert.NotNull(okResult.Value);
    Assert.Equal(1, okResult.Value.Id);
}

Para más información, consulte IResult tipos de implementación.

Nuevas interfaces HttpResult

Las interfaces siguientes del espacio de nombres Microsoft.AspNetCore.Http proporcionan una manera de detectar el tipo IResult en tiempo de ejecución, que es un patrón común en las implementaciones de filtros:

Para obtener más información, consulte Interfaces IHttpResult.

Mejoras de OpenAPI para API mínimas

Paquete NuGet Microsoft.AspNetCore.OpenApi

El paquete Microsoft.AspNetCore.OpenApi permite interacciones con las especificaciones de OpenAPI para los puntos de conexión. El paquete actúa como vínculo entre los modelos de OpenAPI definidos en el paquete Microsoft.AspNetCore.OpenApi y los puntos de conexión definidos en las API mínimas. El paquete proporciona una API que examina los parámetros, las respuestas y los metadatos de un punto de conexión para construir un tipo de anotación de OpenAPI que se usa para describir un punto de conexión.

app.MapPost("/todoitems/{id}", async (int id, Todo todo, TodoDb db) =>
{
    todo.Id = id;
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
})
.WithOpenApi();

Llamada a WithOpenApi con parámetros

El método WithOpenApi acepta una función que se puede usar para modificar la anotación de OpenAPI. Por ejemplo, en el código siguiente, se agrega una descripción al primer parámetro del punto de conexión:

app.MapPost("/todo2/{id}", async (int id, Todo todo, TodoDb db) =>
{
    todo.Id = id;
    db.Todos.Add(todo);
    await db.SaveChangesAsync();

    return Results.Created($"/todoitems/{todo.Id}", todo);
})
.WithOpenApi(generatedOperation =>
{
    var parameter = generatedOperation.Parameters[0];
    parameter.Description = "The ID associated with the created Todo";
    return generatedOperation;
});

Proporcionar descripciones y resúmenes del punto de conexión

Las API mínimas ahora admiten la anotación de operaciones con descripciones y resúmenes para la generación de especificaciones de OpenAPI. Puede llamar a métodos de extensión WithDescription y WithSummary o usar atributos [EndpointDescription] y [EndpointSummary]).

Para más información, consulte Compatibilidad con OpenAPI en API mínimas: ASP.NET Core

Cargas de archivos mediante IFormFile e IFormFileCollection

Las API mínimas ahora admiten la carga de archivos con IFormFile y IFormFileCollection. El código siguiente usa IFormFile y IFormFileCollection para cargar el archivo:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapPost("/upload", async (IFormFile file) =>
{
    var tempFile = Path.GetTempFileName();
    app.Logger.LogInformation(tempFile);
    using var stream = File.OpenWrite(tempFile);
    await file.CopyToAsync(stream);
});

app.MapPost("/upload_many", async (IFormFileCollection myFiles) =>
{
    foreach (var file in myFiles)
    {
        var tempFile = Path.GetTempFileName();
        app.Logger.LogInformation(tempFile);
        using var stream = File.OpenWrite(tempFile);
        await file.CopyToAsync(stream);
    }
});

app.Run();

Las solicitudes de carga de archivos autenticadas se admiten mediante un encabezado de autorización, un certificado de cliente o un cookie encabezado.

No hay compatibilidad integrada con la antifalsificación. Sin embargo, se puede implementar mediante el servicioIAntiforgery.

El atributo [AsParameters] habilita el enlace de parámetros para las listas de argumentos.

El atributo [AsParameters] habilita el enlace de parámetros para las listas de argumentos. Para obtener más información, vea Enlace de parámetros para listas de argumentos con [AsParameters].

API mínimas y controladores de API

Nuevo servicio de detalles del problema

El servicio de detalles del problema implementa la interfaz IProblemDetailsService, que admite la creación de detalles del problema para las API HTTP.

Para más información, consulte Servicio de detalles del problema.

Grupos de rutas

El método de extensión MapGroup ayuda a organizar grupos de puntos de conexión con un prefijo común. Reduce el código repetitivo y permite personalizar grupos completos de puntos de conexión con una sola llamada a métodos como RequireAuthorization y WithMetadata, que agregan metadatos de punto de conexión.

Por ejemplo, el código siguiente crea dos grupos similares de puntos de conexión:

app.MapGroup("/public/todos")
    .MapTodosApi()
    .WithTags("Public");

app.MapGroup("/private/todos")
    .MapTodosApi()
    .WithTags("Private")
    .AddEndpointFilterFactory(QueryPrivateTodos)
    .RequireAuthorization();


EndpointFilterDelegate QueryPrivateTodos(EndpointFilterFactoryContext factoryContext, EndpointFilterDelegate next)
{
    var dbContextIndex = -1;

    foreach (var argument in factoryContext.MethodInfo.GetParameters())
    {
        if (argument.ParameterType == typeof(TodoDb))
        {
            dbContextIndex = argument.Position;
            break;
        }
    }

    // Skip filter if the method doesn't have a TodoDb parameter.
    if (dbContextIndex < 0)
    {
        return next;
    }

    return async invocationContext =>
    {
        var dbContext = invocationContext.GetArgument<TodoDb>(dbContextIndex);
        dbContext.IsPrivate = true;

        try
        {
            return await next(invocationContext);
        }
        finally
        {
            // This should only be relevant if you're pooling or otherwise reusing the DbContext instance.
            dbContext.IsPrivate = false;
        }
    };
}
public static RouteGroupBuilder MapTodosApi(this RouteGroupBuilder group)
{
    group.MapGet("/", GetAllTodos);
    group.MapGet("/{id}", GetTodo);
    group.MapPost("/", CreateTodo);
    group.MapPut("/{id}", UpdateTodo);
    group.MapDelete("/{id}", DeleteTodo);

    return group;
}

En este escenario, puede usar una dirección relativa para el encabezado Location en el resultado 201 Created:

public static async Task<Created<Todo>> CreateTodo(Todo todo, TodoDb database)
{
    await database.AddAsync(todo);
    await database.SaveChangesAsync();

    return TypedResults.Created($"{todo.Id}", todo);
}

El primer grupo de puntos de conexión solo coincidirá con las solicitudes con el prefijo /public/todos y que sean accesibles sin autenticación. El segundo grupo de puntos de conexión solo coincidirá con las solicitudes con el prefijo /private/todos y que requieran autenticación.

La QueryPrivateTodosfábrica de filtros de punto de conexión es una función local que modifica los parámetros TodoDb del controlador para permitir el acceso y almacenar datos privados de tareas pendientes.

Los grupos de rutas también admiten grupos anidados y patrones de prefijo complejos con parámetros y restricciones de ruta. En el ejemplo siguiente, y el controlador de rutas asignado al grupo user puede capturar los parámetros de ruta {org} y {group} definidos en los prefijos del grupo externo.

El prefijo también puede estar vacío. Esto puede ser útil para agregar metadatos de punto de conexión o filtros a un grupo de puntos de conexión sin cambiar el patrón de ruta.

var all = app.MapGroup("").WithOpenApi();
var org = all.MapGroup("{org}");
var user = org.MapGroup("{user}");
user.MapGet("", (string org, string user) => $"{org}/{user}");

La adición de filtros o metadatos a un grupo se comporta del mismo modo que la adición individual a cada punto de conexión antes de agregar filtros o metadatos adicionales que quizás se hayan agregado a un grupo interno o a un punto de conexión específico.

var outer = app.MapGroup("/outer");
var inner = outer.MapGroup("/inner");

inner.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/inner group filter");
    return next(context);
});

outer.AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("/outer group filter");
    return next(context);
});

inner.MapGet("/", () => "Hi!").AddEndpointFilter((context, next) =>
{
    app.Logger.LogInformation("MapGet filter");
    return next(context);
});

En el ejemplo anterior, el filtro externo registrará la solicitud entrante antes que el filtro interno aunque se haya agregado en segundo lugar. Dado que los filtros se aplicaron a diferentes grupos, el orden en que se agregaron el uno con respecto al otro no es importante. El orden en que se agregan los filtros es importante si se aplican al mismo grupo o punto de conexión específico.

Una solicitud a /outer/inner/ registrará lo siguiente:

/outer group filter
/inner group filter
MapGet filter

gRPC

Transcodificación JSON

La transcodificación JSON de gRPC es una extensión para ASP.NET Core que crea API JSON de RESTful para servicios gRPC. La transcodificación JSON de gRPC permite:

  • Aplicaciones que llaman a servicios gRPC con conceptos conocidos de HTTP.
  • Aplicaciones gRPC de ASP.NET Core para admitir las API gRPC y RESTful JSON sin la funcionalidad de replicación.
  • Hay compatibilidad experimental para generar OpenAPI a partir de RESTAPI completas transcodificadas mediante la integración con Swashbuckle.

Para obtener más información, consulte Transcodificación JSON de gRPC en aplicaciones ASP.NET Core y Uso de OpenAPI con transcodificación JSON de gRPC en aplicaciones ASP.NET Core.

Comprobaciones de estado de gRPC en ASP.NET Core

El protocolo de comprobación de estado gRPC es un estándar para notificar el estado de las aplicaciones de servidor gRPC. Una aplicación expone comprobaciones de estado como un servicio gRPC. Normalmente se usan con un servicio de supervisión externa para comprobar el estado de una aplicación.

gRPC de ASP.NET Core tiene compatibilidad integrada añadida con las comprobaciones de estado de gRPC con el paquete Grpc.AspNetCore.HealthChecks. Los resultados de las comprobaciones de estado de .NET se notifican a los autores de la llamada.

Para más información, consulte Comprobaciones de estado de gRPC en ASP.NET Core.

Compatibilidad con credenciales de llamada mejorada

Las credenciales de llamada son la manera recomendada de configurar un cliente gRPC para enviar un token de autenticación al servidor. Los clientes de gRPC admiten dos características nuevas para facilitar el uso de las credenciales de llamada:

  • Compatibilidad de credenciales de llamada con conexiones de texto no cifrado. Anteriormente, una llamada a gRPC solo enviaba credenciales de llamada si la conexión estaba protegida con TLS. Una nueva configuración en GrpcChannelOptions, denominada UnsafeUseInsecureChannelCallCredentials, permite personalizar este comportamiento. Hay implicaciones de seguridad para no proteger una conexión con TLS.
  • Hay disponible un nuevo método denominado AddCallCredentials con la fábrica de cliente de gRPC. AddCallCredentials es una forma rápida de configurar las credenciales de llamada para un cliente gRPC y se integra bien con la inserción de dependencias (DI).

El código siguiente configura el generador de cliente gRPC para enviar metadatos Authorization:

builder.Services
    .AddGrpcClient<Greeter.GreeterClient>(o =>
    {
       o.Address = new Uri("https://localhost:5001");
    })
    .AddCallCredentials((context, metadata) =>
    {
       if (!string.IsNullOrEmpty(_token))
       {
          metadata.Add("Authorization", $"Bearer {_token}");
       }
       return Task.CompletedTask;
    });

Para más información, consulte Token de portador con la fábrica de cliente de gRPC.

SignalR

Resultados del cliente

El servidor ahora admite la solicitud de un resultado de un cliente. Esto requiere que el servidor use ISingleClientProxy.InvokeAsync y que el cliente devuelva un resultado de su controlador .On. Los concentradores fuertemente tipados también pueden devolver valores de métodos de interfaz.

Para más información, vea Resultados del cliente.

Inserción de dependencias para los métodos de concentrador SignalR

Ahora, los métodos de concentrador SignalR admiten la inserción de servicios a través de la inserción de dependencias (DI).

Los constructores de concentradores pueden aceptar servicios de inserción de dependencias como parámetros, que pueden almacenarse en propiedades de la clase para su uso en un método de concentrador. Para obtener más información, consulte Inserción de servicios en un concentrador.

Blazor

Control de los eventos de cambio de ubicación y el estado de navegación

En .NET 7, Blazor admite eventos de cambio de ubicación y mantenimiento del estado de navegación. Esto le permite advertir a los usuarios sobre el trabajo no guardado o la posibilidad de realizar acciones relacionadas cuando navegan por las páginas.

Para obtener más información, consulte las secciones siguientes del artículo Enrutamiento y navegación:

Plantillas de proyecto de Blazor vacías

Blazor tiene dos plantillas de proyecto nuevas para empezar desde una pizarra en blanco. Las nuevas plantillas de proyecto Aplicación Blazor Server vacía y Aplicación Blazor WebAssembly vacía son iguales a sus equivalentes no vacías, pero sin código de ejemplo. Estas plantillas vacías solo incluyen una página principal básica y hemos quitado el arranque para que pueda empezar con un marco CSS diferente.

Para más información, consulte los siguientes artículos.

Elementos Blazor personalizados

El paquete Microsoft.AspNetCore.Components.CustomElements habilita la creación de elementos DOM personalizados basados en estándares mediante Blazor.

Para más información, vea Componentes Razor de ASP.NET Core.

Modificadores de enlace (@bind:after, @bind:get, @bind:set)

Importante

Las características @bind:after/@bind:get/@bind:set reciben más actualizaciones en este momento. Para aprovechar las últimas actualizaciones, confirme que ha instalado el SDK más reciente.

No se admite el uso de un parámetro de devolución de llamada de eventos ([Parameter] public EventCallback<string> ValueChanged { get; set; }). En su lugar, pase un método de devolución de Action o Task a @bind:set/@bind:after.

Para obtener más información, vea los siguientes recursos:

En .NET 7, puede ejecutar lógica asincrónica después de que un evento de enlace se haya completado con el nuevo modificador @bind:after. En el ejemplo siguiente, el método asincrónico PerformSearch se ejecuta de forma automática después de detectar cualquier cambio en el texto de búsqueda:

<input @bind="searchText" @bind:after="PerformSearch" />

@code {
    private string searchText;

    private async Task PerformSearch()
    {
        ...
    }
}

En .NET 7, también es más fácil configurar el enlace para los parámetros de componente. Los componentes pueden admitir el enlace de datos bidireccional mediante la definición de un par de parámetros:

  • @bind:get: especifica el valor que se va a enlazar.
  • @bind:set: especifica una devolución de llamada para cuando cambia el valor.

Los modificadores @bind:get y @bind:set siempre se usan juntos.

Ejemplos:

@* Elements *@

<input type="text" @bind="text" @bind:after="() => { }" />

<input type="text" @bind:get="text" @bind:set="(value) => { }" />

<input type="text" @bind="text" @bind:after="AfterAsync" />

<input type="text" @bind:get="text" @bind:set="SetAsync" />

<input type="text" @bind="text" @bind:after="() => { }" />

<input type="text" @bind:get="text" @bind:set="(value) => { }" />

<input type="text" @bind="text" @bind:after="AfterAsync" />

<input type="text" @bind:get="text" @bind:set="SetAsync" />

@* Components *@

<InputText @bind-Value="text" @bind-Value:after="() => { }" />

<InputText @bind-Value:get="text" @bind-Value:set="(value) => { }" />

<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />

<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />

<InputText @bind-Value="text" @bind-Value:after="() => { }" />

<InputText @bind-Value:get="text" @bind-Value:set="(value) => { }" />

<InputText @bind-Value="text" @bind-Value:after="AfterAsync" />

<InputText @bind-Value:get="text" @bind-Value:set="SetAsync" />

@code {
    private string text = "";

    private void After(){}
    private void Set() {}
    private Task AfterAsync() { return Task.CompletedTask; }
    private Task SetAsync(string value) { return Task.CompletedTask; }
}

Para obtener más información sobre el componente InputText, consulte Formularios y componentes de entrada de ASP.NET Core Blazor.

Mejoras de Recarga activa

En .NET 7, la compatibilidad con Recarga activa incluye lo siguiente:

  • Los componentes restablecen sus parámetros a sus valores predeterminados cuando se quita un valor.
  • Blazor WebAssembly:
    • Adición de tipos nuevos.
    • Adición de clases anidadas.
    • Adición de métodos estáticos y de instancia a los tipos existentes.
    • Adición de campos y métodos estáticos a los tipos existentes.
    • Adición de expresiones lambda estáticas a los métodos existentes.
    • Adición de expresiones lambda que capturan this a los métodos existentes que ya capturaban this anteriormente.

Solicitudes de autenticación dinámica con MSAL en Blazor WebAssembly

Como novedad de .NET 7, Blazor WebAssembly admite la creación de solicitudes de autenticación dinámica en tiempo de ejecución con parámetros personalizados para controlar escenarios de autenticación avanzada.

Para más información, consulte los siguientes artículos.

Mejoras en la depuración de Blazor WebAssembly

La depuración de Blazor WebAssembly cuenta con las mejoras siguientes:

  • Compatibilidad con la configuración Solo mi código para mostrar u ocultar miembros de tipos que no pertenecen al código del usuario.
  • Compatibilidad con la inspección de matrices multidimensionales.
  • Ahora, la pila de llamadas muestra el nombre correcto para los métodos asincrónicos.
  • Evaluación de expresiones mejorada.
  • Control correcto de la palabra clave new en miembros derivados.
  • Compatibilidad con atributos relacionados con el depurador en System.Diagnostics.

Compatibilidad de System.Security.Cryptography en WebAssembly

.NET 6 admitía la familia SHA de algoritmos hash al ejecutarse en WebAssembly. .NET 7 permite más algoritmos criptográficos al aprovechar SubtleCrypto, siempre que sea posible, y recurrir a una implementación de .NET cuando no se puede usar SubtleCrypto. Los algoritmos siguientes se admiten en WebAssembly en .NET 7:

  • SHA1
  • SHA256
  • SHA384
  • SHA512
  • HMACSHA1
  • HMACSHA256
  • HMACSHA384
  • HMACSHA512
  • AES-CBC
  • PBKDF2
  • HKDF

Para obtener más información, consulte cómo los desarrolladores que tienen como destino browser-wasm pueden usar las API de cifrado web (dotnet/runtime #40074).

Inserción de servicios en atributos de validación personalizados

Ahora puede insertar servicios en atributos de validación personalizados. Blazor configura ValidationContext para que se pueda usar como proveedor de servicios.

Para obtener más información, consulte Validación de formularios de Blazor de ASP.NET Core.

Componentes Input* fuera de EditContext/EditForm

Ahora, los componentes de entrada integrados se admiten fuera de un formulario en el marcado de componentes Razor.

Para más información, vea Componentes de entrada de Blazor de ASP.NET Core.

Cambios en la plantilla de proyecto

Cuando .NET 6 se publicó el año pasado, el marcado HTML de la página _Host (Pages/_Host.chstml) se dividió entre la página _Host y una página _Layout nueva (Pages/_Layout.chstml) en la plantilla de proyecto Blazor Server de .NET 6.

En .NET 7, el marcado HTML se ha recombinado con la página _Host en las plantillas de proyecto.

Se realizaron varios cambios adicionales en las plantillas de proyecto de Blazor. No es posible enumerar todos los cambios de las plantillas en la documentación. Para migrar una aplicación a .NET 7 con el fin de adoptar todos los cambios, consulte la migración de ASP.NET Core 6.0 a 7.0.

Componente QuickGrid experimental

QuickGrid es un nuevo componente de la cuadrícula de datos que resulta conveniente para la mayoría de los requisitos comunes, además de como arquitectura de referencia y línea de base de rendimiento para cualquier usuario que cree componentes de cuadrícula de datos de Blazor.

Para obtener más información, consulte Componente QuickGrid Blazor de ASP.NET Core.

Demostración en directo: QuickGrid para una aplicación de ejemplo de Blazor

Mejoras de virtualización

Mejoras de virtualización en .NET 7:

  • El componente Virtualize admite el uso del propio documento como raíz de desplazamiento, como alternativa a tener algún otro elemento con overflow-y: scroll aplicado.
  • Si el componente Virtualize se coloca dentro de un elemento que requiere un nombre de etiqueta secundario específico, SpacerElement permite obtener o establecer el nombre de etiqueta del espaciador de virtualización.

Para obtener más información, consulte las secciones siguientes del artículo Virtualización:

Actualizaciones de MouseEventArgs

Se han agregado MovementX y MovementY a MouseEventArgs.

Para más información, vea Control de eventos de Blazor en ASP.NET Core.

Nueva página de carga de Blazor

La plantilla de proyecto Blazor WebAssembly tiene una nueva interfaz de usuario de carga que muestra el progreso de carga de la aplicación.

Para obtener más información, vea Inicio de Blazor de ASP.NET Core.

Diagnósticos mejorados para la autenticación en Blazor WebAssembly

Para ayudar a diagnosticar los problemas de autenticación en las aplicaciones de Blazor WebAssembly, está disponible el registro detallado.

Para obtener más información, consulte Registro de Blazor en ASP.NET Core.

Interoperabilidad de JavaScript en WebAssembly

La API de interoperabilidad de JavaScript [JSImport]/[JSExport] es un nuevo mecanismo de bajo nivel para usar .NET en Blazor WebAssembly y aplicaciones basadas en JavaScript. Con esta nueva funcionalidad de interoperabilidad de JavaScript, puede invocar código .NET desde JavaScript mediante el entorno de ejecución de WebAssembly de .NET y llamar a la funcionalidad de JavaScript desde .NET sin ninguna dependencia del modelo de componentes de interfaz de usuario Blazor.

Para obtener más información:

Registro condicional del proveedor de estado de autenticación

Antes del lanzamiento de .NET 7, AuthenticationStateProvider se registraba en el contenedor de servicios con AddScoped. Esto dificultaba la depuración de las aplicaciones, ya que forzaba a un orden específico de los registros de servicio cuando se proporcionaba una implementación personalizada. Debido a los cambios del marco interno a lo largo del tiempo, ya no es necesario registrar AuthenticationStateProvider con AddScoped.

En el código de desarrollador, realice el cambio siguiente en el registro del servicio del proveedor de estado de autenticación:

- builder.Services.AddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();
+ builder.Services.TryAddScoped<AuthenticationStateProvider, ExternalAuthStateProvider>();

En el ejemplo anterior, ExternalAuthStateProvider es la implementación del servicio del desarrollador.

Mejoras en las herramientas de compilación de WebAssembly de .NET

Nuevas características de la carga de trabajo wasm-tools para .NET 7 que ayudan a mejorar el rendimiento y el control de excepciones:

Para obtener más información, consulte Herramientas de creación de ASP.NET Core Blazor WebAssembly y compilación anticipada (AOT).

Blazor Hybrid

Direcciones URL externas

Se ha agregado una opción que permite abrir páginas web externas en el explorador.

Para más información, vea Enrutamiento y navegación de Blazor Hybrid de ASP.NET Core.

Seguridad

Hay disponibles nuevas instrucciones para los escenarios de seguridad de Blazor Hybrid. Para más información, consulte los siguientes artículos.

Rendimiento

Middleware de almacenamiento en caché de salida

El almacenamiento en caché de salida es un nuevo middleware que almacena las respuestas de una aplicación web y las distribuye desde una memoria caché en lugar de calcularlas cada vez. El almacenamiento en caché de salida difiere del almacenamiento en caché de resultados de las siguientes maneras:

  • El comportamiento de almacenamiento en caché se puede configurar en el servidor.
  • Las entradas de caché se pueden invalidar mediante programación.
  • El bloqueo de recursos mitiga el riesgo de fallo en cascada y colapso por activación simultánea de varios hilos de ejecución (thundering herd).
  • La revalidación de caché significa que el servidor puede devolver un código de estado HTTP 304 Not Modified en lugar de un cuerpo de respuesta almacenado en caché.
  • El medio de almacenamiento en caché es extensible.

Para más información, consulte Información general sobre el almacenamiento en caché en ASP.NET Core y Middleware de almacenamiento en caché de salida en ASP.NET Core.

Mejoras de HTTP/3

Esta versión:

  • Hace que HTTP/3 sea totalmente compatible con ASP.NET Core, ya no es experimental.
  • Mejora la compatibilidad de Kestrel con HTTP/3. Las dos áreas principales de mejora son la paridad de características con HTTP/1.1 y HTTP/2 y el rendimiento.
  • Proporciona compatibilidad completa para UseHttps(ListenOptions, X509Certificate2) con HTTP/3. Kestrel ofrece opciones avanzadas para configurar los certificados de conexión, como el enlace a la Indicación de nombre de servidor (SNI).
  • Agrega compatibilidad con HTTP/3 en HTTP.sys e IIS.

En el ejemplo siguiente se muestra cómo usar una devolución de llamada SNI para resolver las opciones de TLS:

using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenAnyIP(8080, listenOptions =>
    {
        listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
        listenOptions.UseHttps(new TlsHandshakeCallbackOptions
        {
            OnConnection = context =>
            {
                var options = new SslServerAuthenticationOptions
                {
                    ServerCertificate = 
                         MyResolveCertForHost(context.ClientHelloInfo.ServerName)
                };
                return new ValueTask<SslServerAuthenticationOptions>(options);
            },
        });
    });
});

Se ha realizado un trabajo significativo en .NET 7 para reducir las asignaciones HTTP/3. Puede ver algunas de esas mejoras en las siguientes PR de GitHub:

Mejoras en el rendimiento de HTTP/2

.NET 7 presenta un cambio de arquitectura significativo respecto a la forma en que Kestrel procesa las solicitudes HTTP/2. Las aplicaciones ASP.NET Core con conexiones HTTP/2 ocupadas experimentarán un uso de CPU reducido y un mayor rendimiento.

Anteriormente, la implementación de multiplexación HTTP/2 se basaba en un bloqueo que controlaba qué solicitud podía escribir en la conexión TCP subyacente. Una cola segura para subprocesos reemplaza al bloqueo de escritura. Ahora, en lugar de luchar por ver a qué subproceso se aplica el bloqueo de escritura, las solicitudes se ponen en cola y un consumidor dedicado las procesa. Los recursos de CPU anteriormente desaprovechados están disponibles para el resto de la aplicación.

Un lugar donde pueden observarse estas mejoras es gRPC, un marco popular de RPC que usa HTTP/2. Los puntos de referencia de gRPC y Kestrel muestran una mejora considerable:

Rendimiento de streaming del servidor gRPC

Se realizaron cambios en el código de escritura del marco HTTP/2 que mejoran el rendimiento cuando varias secuencias intentan escribir datos en una sola conexión HTTP/2. Ahora se envían tareas de LS al grupo de subprocesos y se libera más rápidamente un bloqueo de escritura que pueden adquirir otras secuencias para escribir sus datos. La reducción de los tiempos de espera puede producir mejoras de rendimiento significativas en los casos en los que existe contención para este bloqueo de escritura. Una prueba comparativa de gRPC con 70 secuencias en una sola conexión (con TLS) mostró una mejora aproximada del 15 % en las solicitudes por segundo (RPS) con este cambio.

Compatibilidad con Http/2 WebSockets

En .NET 7 incorpora Websockets mediante la compatibilidad con HTTP/2 para Kestrel, el cliente de JavaScript SignalR y SignalR con Blazor WebAssembly.

El uso de WebSockets mediante HTTP/2 aprovecha las nuevas características, como las siguientes:

  • Compresión de encabezados.
  • Multiplexación, que reduce el tiempo y los recursos necesarios al realizar varias solicitudes al servidor.

Estas características admitidas están disponibles en Kestrel en todas las plataformas habilitadas para HTTP/2. La negociación de versiones es automática en exploradores y Kestrel, por lo que no se necesitan API nuevas.

Para más información, consulte Compatibilidad con Http/2 WebSockets.

Kestrel mejoras de rendimiento en máquinas de núcleo alto

Kestrel usa ConcurrentQueue<T> para muchos propósitos. Un propósito es programar operaciones de E/S en el transporte de socket predeterminado de Kestrel. La creación de particiones del ConcurrentQueue en función del socket asociado reduce la contención y aumenta el rendimiento en las máquinas con muchos núcleos de CPU.

La generación de perfiles en máquinas de núcleo alto en .NET 6 mostró una contención significativa en una de las otras instancias de KestrelConcurrentQueue, el PinnedMemoryPoolque usa Kestrel para almacenar en caché los búferes de bytes.

En .NET 7, el grupo de memoria de Kestrel se particiona de la misma manera que su cola de E/S, lo que conduce a una contención mucho menor y un mayor rendimiento en máquinas de núcleo alto. En las máquinas virtuales ARM64 de 80 núcleos, estamos viendo una mejora del 500 % en las respuestas por segundo (RPS) en la prueba comparativa de texto no cifrado TechEmpower. En las máquinas virtuales AMD de 48 núcleos, la mejora es casi del 100 % en nuestro banco de pruebas HTTPS JSON.

Evento ServerReady para medir el tiempo de inicio

Las aplicaciones que usan EventSource pueden medir el tiempo de inicio para comprender y optimizar el rendimiento de inicio. El nuevo evento ServerReady de Microsoft.AspNetCore.Hosting representa el punto en el que el servidor está listo para responder a las solicitudes.

Server

Nuevo evento ServerReady para medir el tiempo de inicio

Se ha agregado el evento ServerReady para medir el tiempo de inicio de las aplicaciones ASP.NET Core.

IIS

Instantáneas en IIS

La creación de instantáneas de los ensamblados de la aplicación en el módulo de ASP.NET Core (ANCM) para IIS ofrece una mejor experiencia de usuario final que detener la aplicación mediante la implementación de un archivo sin conexión de la aplicación.

Para más información, consulte Instantánea.

Varios

Kestrel mejoras completas de la cadena de certificados

HttpsConnectionAdapterOptions tiene una nueva propiedad ServerCertificateChain de tipo X509Certificate2Collection, la cual facilita la validación de cadenas de certificados mediante la especificación de una cadena completa, incluidos los certificados intermedios. Consulte: dotnet/aspnetcore#21513 para obtener más información.

dotnet watch

Salida de consola mejorada para dotnet watch

La salida de la consola de dotnet watch se ha mejorado para estar más acorde con el registro de ASP.NET Core y para destacar el uso de 😮emojis😍.

Este es un ejemplo del aspecto de la nueva salida:

salida para dotnet watch

Para más información, consulte esta solicitud de incorporación de cambios de GitHub.

Configuración de dotnet watch para reiniciarse siempre con las ediciones superficiales

Las ediciones superficiales son ediciones que no se pueden recargar de forma activa. Para configurar dotnet watch para que se reinicie siempre sin pedir confirmación para las ediciones superficiales, establezca la variable de entorno DOTNET_WATCH_RESTART_ON_RUDE_EDIT en true.

Modo oscuro de la página de excepciones para el desarrollador

Se ha agregado compatibilidad con el modo oscuro a la página de excepciones para el desarrollador, gracias a una contribución de Patrick Westerhoff. Para probar el modo oscuro en un explorador, establezca el modo como oscuro en la página herramientas de desarrollo. Por ejemplo, en Firefox:

Modo oscuro de FF de herramientas de F12

En Chrome:

Modo oscuro de Chrome de herramientas de F12

Opción de plantilla de proyecto para usar el método Program.Main en lugar de instrucciones de nivel superior

Las plantillas de .NET 7 incluyen una opción para no usar instrucciones de nivel superior y generar un método namespace y Main declarado en una clase Program.

Con la CLI de .NET, use la opción --use-program-main:

dotnet new web --use-program-main

Con Visual Studio, active la casilla No usar instrucciones de nivel superior durante la creación del proyecto:

casilla

Plantillas actualizadas de Angular y React

La plantilla de proyecto de Angular se ha actualizado a Angular 14. La plantilla de proyecto de React se ha actualizado a React 18.2.

Administre JSON Web Tokens en desarrollo con dotnet user-jwts

La nueva herramienta de línea de comandos de dotnet user-jwts puede crear y administrar JS ON Web Tokens locales específicos de la aplicación (JWT). Para obtener más información, vea Administrar JSON Web Tokens en desarrollo con dotnet user-jwts.

Compatibilidad con encabezados de solicitud adicionales en W3CLogger

Ahora puede especificar encabezados de solicitud adicionales para registrar al usar el registrador W3C llamando a AdditionalRequestHeaders() en W3CLoggerOptions:

services.AddW3CLogging(logging =>
{
    logging.AdditionalRequestHeaders.Add("x-forwarded-for");
    logging.AdditionalRequestHeaders.Add("x-client-ssl-protocol");
});

Para más información, vea Opciones de W3CLogger.

Descompresión de solicitudes

El nuevo Middleware de descompresión de solicitudes:

  • Permite que los puntos de conexión de API acepten solicitudes con contenido comprimido.
  • Usa el encabezado HTTP Content-Encoding para identificar y descomprimir automáticamente las solicitudes que contienen contenido comprimido.
  • Elimina la necesidad de escribir código para controlar solicitudes comprimidas.

Para obtener más información, consulte Middleware de descompresión de solicitudes.