Novedades de ASP.NET Core en .NET 11

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

Este artículo se actualizará a medida que se publiquen nuevas versiones preliminares.

Blazor

En esta sección se describen las nuevas características de Blazor.

Nuevo DisplayName componente y compatibilidad con los atributos [Display] y [DisplayName]

El DisplayName componente se puede usar para mostrar los nombres de propiedad de los atributos de metadatos:

[Required, DisplayName("Production Date")]
public DateTime ProductionDate { get; set; }

Se admite el [Display] atributo en la propiedad de clase de modelo:

[Required, Display(Name = "Production Date")]
public DateTime ProductionDate { get; set; }

De los dos enfoques, se recomienda el [Display] atributo , lo que hace que haya propiedades adicionales disponibles. El [Display] atributo también permite asignar un tipo de recurso para la localización. Cuando ambos atributos están presentes, [Display] tiene prioridad sobre [DisplayName]. Si ninguno de los atributos está presente, el componente vuelve al nombre de la propiedad.

Use el DisplayName componente en etiquetas o encabezados de tabla:

<label>
    <DisplayName For="@(() => Model!.ProductionDate)" />
    <InputDate @bind-Value="Model!.ProductionDate" />
</label>

Blazor Formato de opciones de inicio de scripts web ahora compatible para Blazor Server y Blazor WebAssembly scripts

El objeto de opciones Blazor Web App script (blazor.web.js) pasado a Blazor.start() usa el siguiente formato desde la versión de .NET 8:

Blazor.start({
  ssr: { ... },
  circuit: { ... },
  webAssembly: { ... },
});

Ahora, los Blazor Server scripts (blazor.server.js) y Blazor WebAssembly (blazor.webassembly.js) pueden usar el mismo formato de opciones.

En el ejemplo siguiente se muestra el formato de opciones anteriores, que sigue siendo compatible:

Blazor.start({
  loadBootResource: function (...) {
      ...
    },
  });

El formato de las opciones recién admitidas para el ejemplo anterior:

Blazor.start({
  webAssembly: {
    loadBootResource: function (...) {
      ...
    },
  },
});

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

Nuevo componente de BasePath

Blazor Web Apps puede usar el nuevo BasePath componente (<BasePath />) para renderizar automáticamente la ruta base de la app (<base href>) etiqueta HTML. Para obtener más información, consulte ASP.NET Core Blazor ruta de acceso base de la aplicación.

Controlador de eventos en línea JS eliminado del componente NavMenu

El gestor de eventos en línea JS que activa la visualización de los enlaces de navegación ya no está presente en el NavMenu componente de la Blazor Web App plantilla del proyecto. Las aplicaciones generadas a partir de la plantilla de proyecto ahora usan un enfoque de módulo colocado JS para mostrar u ocultar la barra de navegación en la página representada. El nuevo enfoque mejora el cumplimiento de la directiva de seguridad de contenido (CSP) porque no requiere que el CSP incluya un hash no seguro para el elemento insertado JS.

Para migrar una aplicación existente a .NET 11, incluida la adopción del nuevo enfoque de módulo JS para el controlador de la barra de navegación, consulte Migrar de ASP.NET Core en .NET 10 a ASP.NET Core en .NET 11.

El nuevo RelativeToCurrentUri parámetro (valor predeterminado: false) para NavigationManager.NavigateTo y el componente NavLink permite navegar a los URIs relativos a la ruta de acceso de la página actual en lugar del URI base de la aplicación.

Tenga en cuenta los siguientes puntos de conexión anidados:

  • /docs
    • /getting-started
      • /installation
      • /configuration

Cuando el URI del navegador es /docs/getting-started/installation y quieres guiar al usuario a /docs/getting-started/configuration, NavigateTo("/configuration") redirige a /configuration la raíz de la app en lugar de la ruta relativa en /docs/getting-started/configuration. Establezca el RelativeToCurrentUri con NavigateTo o el NavLink componente para la navegación deseada:

Navigation.NavigateTo("/configuration", new NavigationOptions
{
    RelativeToCurrentUri = true
});
<NavLink href="configuration" RelativeToCurrentUri="true">Configuration</NavLink>

Persistir datos temporales entre peticiones HTTP durante renderizado estático en el lado del servidor (SSR estático)

Para conservar datos temporales entre solicitudes HTTP durante la representación estática del lado servidor (SSR estático), Blazor admite TempData. TempData es ideal para escenarios como mensajes flash después de envíos de formularios, pasar datos durante las redirecciones (patrón POST-Redirect-GET) y notificaciones únicas.

TempData está disponible cuando AddRazorComponents se llama en el archivo de Program la app y se proporciona como un valor en cascada junto con el [CascadingParameter] atributo.

[CascadingParameter]
public ITempData? TempData { get; set; }

Cuando se proporciona a un parámetro para lectura y escritura simple de un valor único, use el atributo [SupplyParameterFromTempData]:

[SupplyParameterFromTempData]
public string? Message { get; set; }

Para obtener más información, vea ASP.NET Core Blazor administración de estado del lado servidor.

Nueva Blazor plantilla de Web Worker (blazorwebworker)

La plantilla de proyecto .NET Web Worker, que contiene un cliente de Web Worker para delegar tareas de larga duración a un subproceso en segundo plano, ha pasado a llamarse la plantilla de proyecto Blazor Web Worker (blazorwebworker). El cambio de nombre hace que resulte más claro que la plantilla forma parte de la pila Blazor para su uso en las aplicaciones web Blazor WebAssembly y Blazor (para la representación del lado cliente, CSR).

Se han agregado dos funcionalidades a menudo solicitadas al objeto generado WebWorkerClient:

  • InvokeVoidAsync para llamadas de trabajo de tipo fire-and-forget que no devuelven ningún valor, reflejando la forma en IJSRuntime.
  • Compatibilidad con cancelación y tiempo de espera en creación de trabajos y invocaciones de trabajos, para que los autores de la llamada puedan pasar un CancellationToken y finalizar correctamente un trabajo bloqueado.

Los proyectos existentes creados con la plantilla anterior siguen funcionando. El cambio de nombre solo afecta al nombre de plantilla que se muestra en dotnet new list y en la lista de Visual Studio de Crear un nuevo proyecto plantillas.

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

Mejoras de virtualización

  • El Virtualize<TItem> componente ya no supone que todos los elementos tienen el mismo alto. Anteriormente, el componente deshabilitaba el anclaje de desplazamiento nativo del navegador (para evitar un bucle de reproducción infinito), lo que hacía que cualquier cambio de altura por encima de la ventanilla —expansión de elementos, actualizaciones de datos o contenido de carga diferida— provocara que los elementos visibles saltaran visiblemente en la pantalla. El Virtualize componente se adapta ahora a los tamaños de elemento medidos en tiempo de ejecución, lo que reduce el espaciado y el desplazamiento incorrectos cuando varían los alturas del elemento.

    Las actualizaciones usan un enfoque híbrido: anclaje de desplazamiento nativo de CSS en los exploradores que lo admiten para diseños no <table> con una reserva manual de compensación de desplazamiento basada en ResizeObserver para diseños <table> y Safari, donde el anclaje nativo calcula mal las posiciones en elementos <tr>.

    Las aplicaciones que usan el Virtualize componente reciben automáticamente las ventajas de estas actualizaciones. No se requieren cambios en la API de desarrollador.

    Estas actualizaciones incluyen una actualización del valor predeterminado de Virtualize<TItem>.OverscanCount, que se 3 en .NET 10 o versiones anteriores y ahora cambia a 15 en .NET 11 o posterior. El cambio en el valor predeterminado aumenta la precisión de los cálculos del promedio de altura de los elementos.

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

  • Use el nuevo AnchorMode parámetro para controlar cómo se comporta la ventanilla en los bordes de lista cuando se agregan elementos de forma dinámica:

    • None: Sin fijación de bordes. La ventanilla se mantiene en la posición de desplazamiento actual independientemente de los cambios en los elementos.
    • Start (predeterminado): fija la ventana de visualización al inicio de la lista. Por ejemplo, este comportamiento de anclaje es útil para la experiencia de usuario de una fuente de noticias.
    • End: Fija el área de visualización al final de la lista. Por ejemplo, este comportamiento de anclaje es útil para una experiencia de usuario de chat o registro.

    En el ejemplo siguiente, el contenido virtualizado se ancla al principio de la lista:

    <Virtualize AnchorMode="Start" ...>
        ...
    </Virtualize>
    

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

  • Cumplimiento de la directiva de seguridad de contenido (CSP)

    El componente Virtualize procesa atributos style dinámicos en línea en sus elementos espaciadores y de marcador de posición (por ejemplo, style="height: 478896px; flex-shrink: 0;"), porque las alturas de los espaciadores se calculan en tiempo de ejecución en función de la posición de desplazamiento, el número de elementos y el tamaño medio de los elementos, que cambian con cada interacción de desplazamiento. Estos están bloqueados por una directiva de seguridad de contenido (CSP) cuando style-src 'self' se establece, lo que rompe la virtualización por completo para las aplicaciones con directivas de CSP estrictas.

    Ahora se evitan las violaciones de CSP porque los componentes Virtualize:

    • Representar las alturas calculadas del espaciador y del marcador de posición como valores numéricos en los atributos data-blazor-virtualize-reserved-height.
    • Cuando sea necesario, represente el desplazamiento vertical del espaciador final como un valor numérico en un atributo data-blazor-virtualize-loop-breaker-transform para ocultar el espaciador.

Nueva plantilla de proyecto de biblioteca predeterminada de servicio para Blazor WebAssembly aplicaciones

La blazor-wasm-servicedefaults plantilla de proyecto crea una biblioteca predeterminada de servicio para Blazor WebAssembly aplicaciones con Aspire integración. Para obtener más información, vea Herramientas para ASP.NET Core Blazor.

Nuevo servidor de desarrollo para Blazor WebAssembly aplicaciones

Microsoft.AspNetCore.Components.Gateway es un host de ASP.NET Core ligero que reemplaza Microsoft.AspNetCore.Components.WebAssembly.DevServer para servir aplicaciones independientes de Blazor WebAssembly durante el desarrollo y la producción.

Para integrar Gateway en una aplicación independiente Blazor WebAssembly existente, agregue una referencia al paquete Microsoft.AspNetCore.Components.Gateway en el archivo de proyecto de la aplicación.

Nota:

Para obtener instrucciones sobre cómo agregar paquetes a aplicaciones .NET, consulta los artículos de Instalación y administración de paquetes en Flujo de trabajo de consumo de paquetes (documentación de NuGet). Confirme las versiones correctas del paquete en NuGet.org.

La aplicación no requiere código de enrutamiento personalizado y middleware. Los puntos de conexión alternativos proceden del manifiesto de recursos web estáticos que emite el SDK cuando la propiedad StaticWebAssetSpaFallbackEnabled está establecida en el archivo de proyecto de la aplicación, y está presente de forma predeterminada en las aplicaciones independientes Blazor WebAssembly creadas a partir de la plantilla de proyecto:

<StaticWebAssetSpaFallbackEnabled>true</StaticWebAssetSpaFallbackEnabled>

Antes del lanzamiento de .NET 11, la propiedad inspectUri del archivo Properties/launchSettings.json:

  • Permite que el IDE detecte que la aplicación es una aplicación Blazor.
  • Indica a la infraestructura de depuración de scripts que se conecte al explorador a través del proxy de depuración de Blazor.

La propiedad ya no es necesaria cuando se usa el nuevo servidor de desarrollo.

Abra el archivo Properties/launchSettings.json del proyecto de inicio. Quite la propiedad inspectUri de cada perfil de lanzamiento del nodo profiles del archivo:

- "inspectUri": "..."

Para obtener más información, consulte [Blazor] Sustituir DevServer por BlazorGateway para aplicaciones WASM autónomas (dotnet/aspnetcore #65982) (No realice comentarios sobre problemas y PR cerrados).

Pausa del circuito activada por el servidor

Esta característica se aplica a las aplicaciones del lado Blazor servidor.

Blazor ya admite la pausa y la reanudación ordenadas del circuito con Blazor.pauseCircuit() y Blazor.resumeCircuit(). .NET 11 introduce una capacidad simétrica de pausa y reanudación en el lado del servidor, mediante la cual el servidor puede solicitar que los clientes conectados inicien el flujo ordenado de pausa del circuito.

Circuit.RequestCircuitPauseAsync(CancellationToken) se usa para solicitar al cliente conectado que inicie el proceso de pausa ordenada del circuito. CancellationToken Cancela la solicitud antes de que el marco lo acepte. El método devuelve true si se aceptó la solicitud y se le pidió al cliente que comenzara a pausar.

Esta característica es útil en los escenarios siguientes:

  • Paradas programadas e implementaciones.
  • Purga de instancias.
  • Ventanas de mantenimiento de aplicaciones.

Para obtener más información y un ejemplo de implementación para los reinicios del servidor, vea ASP.NET Core Blazor administración de estado del lado servidor.

Salida de publicación Blazor WebAssembly más pequeña

Dos cambios en el recorte reducen el tamaño de las aplicaciones publicadas Blazor WebAssembly que no usan OpenTelemetry (OTEL) ni Recarga activa:

  • Los tipos ComponentsMetrics y ComponentsActivitySource ahora están controlados detrás de un atributo [FeatureSwitchDefinition], de modo que el recortador pueda eliminar las métricas y las rutas de llamadas de seguimiento de Renderer y tipos afines cuando System.Diagnostics.Metrics.Meter.IsSupported es false (el valor predeterminado para las aplicaciones recortadas) [browser][wasm] Implemente recorte de IL para OTEL (dotnet/aspnetcore #65901) (No realice comentarios sobre problemas y PR cerrados).
  • HotReloadManager expone ahora una propiedad IsSupported conmutada por una característica vinculada a System.Reflection.Metadata.MetadataUpdater.IsSupported, para que el recortador pueda eliminar las cachés de recarga en caliente y los registros de controladores de actualización de metadatos de todo el representador al publicarse [blazor][wasm] Corregir recorte de IL de recarga en caliente (dotnet/aspnetcore #65903) (No realice comentarios sobre problemas y PR cerrados).

Las aplicaciones que usan OTEL o Recarga activa no se ven afectadas por las actualizaciones anteriores.

QuickGrid mejoras

El componente QuickGrid recibe varias características nuevas en .NET 11.

Para obtener más información sobre las siguientes características, vea ASP.NET Core Blazor componente "QuickGrid".

Modos de paginación

Antes del lanzamiento de .NET 11, el estado de paginación y ordenación se administra en memoria dentro del componente QuickGrid sin cambiar la dirección URL, lo que se denomina navegación de estado interno. Se requiere un modo de representación interactiva.

Con la versión de .NET 11, QuickGrid admite navegación basada enURL.

La paginación y el estado de ordenación se conservan en la cadena de consulta url. Cuando los usuarios paginan o ordenan, la dirección URL se actualiza (por ejemplo: ?page=2&sort=Name&direction=asc). Esto permite el uso compartido de vínculos, el retroceso/reenvío del explorador y el SSR estático sin interactividad.

Los encabezados de columna ordenables y los controles de paginación se renderizan como elementos <a> con atributos href. StaticHtmlRenderer representa estos anclajes. En cada solicitud, el servidor lee la cadena de consulta para determinar el estado actual de la página y la ordenación, no se requiere ningún tiempo de ejecución de JavaScript.

Parámetros de cadena de consulta:

  • page: número de página que empieza en 1. La primera página omite el parámetro para las direcciones URL limpias.
  • sort: título de columna para ordenar la cuadrícula.
  • direction: ascendente (asc) o descendente (desc).

La columna sort se identifica por la propiedad Title de la columna. Las columnas sin un Title muestran un encabezado en el que no se puede <div> hacer clic.

QuickGrid lee la URL durante la inicialización y se suscribe a NavigationManager.LocationChanged, por lo que los botones Atrás/Adelante del navegador y la introducción directa de una URL funcionan. Cuando se quitan los parámetros de ordenación de la dirección URL, vuelve a la columna o dirección de ordenación predeterminada.

Los enlaces del paginador deshabilitados usan aria-disabled="true" y pointer-events: none en lugar del atributo HTML disabled, que no existe en los elementos <a>.

Nombres de parámetros de consulta

El nuevo parámetro QueryParameterNameOptions del componente QuickGrid controla los nombres de los parámetros de la cadena de consulta que mantienen el estado de la cuadrícula en la URL. La QueryParameterNameOptions clase tiene tres propiedades settables:

  • Sort: nombre del parámetro de cadena de consulta que contiene la columna de ordenación. El valor por defecto es sort.
  • Direction: nombre del parámetro de cadena de consulta que contiene la dirección de ordenación. El valor por defecto es direction.
  • Page: nombre del parámetro de cadena de consulta que contiene el número de página. El valor por defecto es page.

El constructor acepta un argumento de prefijo opcional que se antepone a los tres nombres predeterminados. El prefijo debe incluir cualquier carácter separador que desee que aparezca entre el prefijo y el nombre. En el ejemplo siguiente, los parámetros de cadena de consulta se denominan products_sort, products_directiony products_page:

@using Microsoft.AspNetCore.Components.QuickGrid

<QuickGrid ... 
    QueryParameterNameOptions="@(new QueryParameterNameOptions("products_"))">
    ...
</QuickGrid>

Para controlar los nombres individualmente, establezca las propiedades de la clase . Las propiedades establecidas explícitamente tienen prioridad sobre un prefijo pasado al constructor, por lo que se pueden combinar los dos enfoques:

@using Microsoft.AspNetCore.Components.QuickGrid

<QuickGrid ... QueryParameterNameOptions="@queryParameterNames">
    ...
</QuickGrid>

@code {
    private QueryParameterNameOptions queryParameterNames = new()
    {
        Sort = "orderBy",
        Direction = "orderDir",
        Page = "p"
    };
}

Varias cuadrículas en la misma página

Varios QuickGrid componentes de la misma página requieren nombres de parámetros de consulta únicos para evitar conflictos de cadenas de consulta. Asigne el parámetro QueryParameterNameOptions a todas las cuadrículas salvo una.

Cada QuickGrid debe tener su propia instancia de PaginationState. Varias cuadrículas no deben compartir un PaginationState si usan nombres de parámetros de consulta diferentes: la última cuadrícula que se renderiza sobrescribe el nombre del parámetro de consulta en el estado compartido, lo que provoca que Paginator lea del parámetro incorrecto.

En versiones anteriores a .NET 11, los siguientes componentes de QuickGrid funcionaban implícitamente:

<QuickGrid ... Pagination="@pagination1">
    ...
</QuickGrid>

<QuickGrid ... Pagination="@pagination2">
    ...
</QuickGrid>

Con la versión de .NET 11, los siguientes QuickGrid componentes requieren nombres de parámetros de consulta únicos. El primero QuickGrid usa los nombres predeterminados, mientras que el segundo usa un cities_ prefijo:

<QuickGrid ... Pagination="@pagination1">
    ...
</QuickGrid>

<QuickGrid ... Pagination="@pagination2" 
    QueryParameterNameOptions="@(new QueryParameterNameOptions("cities_"))">
    ...
</QuickGrid>

Cadena de consulta de ejemplo para los componentes anteriores QuickGrid :

?page=2&sort=Name&direction=asc&cities_page=3&cities_sort=Population&cities_direction=desc

Ordenar por columna

Añada Sortable="true" a un PropertyColumn. Con la navegación basada en direcciones URL, al seleccionar un encabezado se desplaza a una dirección URL con parámetros actualizados sort y direction . Con la navegación de estado interno, seleccionar un encabezado desencadena @onclick, que llama a SortByColumnAsync. En ambos casos, SortByColumnAsync navega a través NavigationManager.NavigateTo(GetSortQueryStringUrl(...))de , por lo que la dirección URL siempre refleja el estado de ordenación.

Identificación de ordenación basada en títulos

El criterio de ordenación en la URL usa la propiedad Title de la columna como identificador. El parámetro de consulta sort se establece en column.Title (ejemplo para el título de columna Name: ?sort=Name&direction=asc). Cuando cambia una URL, QuickGrid vuelve a asociar el valor sort a una columna ejecutando _columns.FirstOrDefault(c => c.Title == sort.ColumnTitle). Si no coincide ningún título de columna, se omite la ordenación y la cuadrícula vuelve a su ordenación predeterminada.

Cambiar el nombre del Title de una columna es un cambio importante de la URL. Las URL compartidas o guardadas en marcadores que contienen el título antiguo en el parámetro sort dejan de coincidir, y la cuadrícula recurre silenciosamente a la ordenación predeterminada en lugar de ordenar por la columna prevista. Para PropertyColumn, el Title valor predeterminado es el nombre de la propiedad (por ejemplo: Property="@(p => p.FirstName)" genera Title="First Name"), por lo que cambiar el nombre de la propiedad o cambiar explícitamente el Title parámetro interrumpen las direcciones URL existentes.

Paginador

Paginator inyecta NavigationManager, se suscribe a LocationChanged y lee el índice de página de la cadena de consulta en cada cambio de ubicación. GoToPageAsync navega a la dirección URL de destino en lugar de mutar PaginationStatedirectamente. El estado se actualiza mediante el flujo de callbacks de LocationChanged.

GetPageUrl devuelve una URL con el número de página que empieza en 1. El índice de página 0 (página 1) omite completamente el parámetro de consulta.

Cambio importante en CSS

Cuando se habilita la navegación basada en direcciones URL, los selectores que tienen como destino button.col-title también deben tener como destino a.col-titleynav button/nav button:disabled requieren .nav a/nav a[aria-disabled="true"] La hoja de estilos integrada QuickGrid proporciona ambas de forma predeterminada.

Cómo deshabilitar la navegación basada en direcciones URL

Para deshabilitar la navegación basada en URL, establezca el modificador AppContext de la característica en false:

AppContext.SetSwitch(
    "Microsoft.AspNetCore.Components.QuickGrid.EnableUrlBasedQuickGridNavigationAndSorting",
    false);

Esto restaura los elementos <button> con controladores @onclick. Se requiere un modo de representación interactiva.

El conmutador solo controla el elemento HTML renderizado (<a> frente a <button>). Incluso cuando está deshabilitado, QuickGrid sigue leyendo y escribiendo el estado en la cadena de consulta de dirección URL internamente. SortByColumnAsync y Paginator.GoToPageAsync navegan mediante NavigationManager.NavigateTo independientemente del indicador.

Evento al hacer clic en una fila (OnRowClick)

El QuickGrid componente ahora admite eventos de clic de fila a través del nuevo OnRowClick parámetro. Cuando se establece, la cuadrícula aplica automáticamente el estilo adecuado (puntero de cursor) e invoca la devolución de llamada con el elemento en el que se hace clic:

@using Microsoft.AspNetCore.Components.QuickGrid
@inject NavigationManager NavigationManager

<QuickGrid Items="@people.AsQueryable()" 
    OnRowClick="@((Person args) => HandleRowClick(args))">
    <PropertyColumn Property="@(p => p.Name)" />
    <PropertyColumn Property="@(p => p.Email)" />
</QuickGrid>

@code {
    private List<Person> people = new()
    {
        new(1, "Alice Smith", "alice@example.com", "Engineering"),
        new(2, "Bob Johnson", "bob@example.com", "Marketing"),
        new(3, "Carol Williams", "carol@example.com", "Engineering"),
    };

    private void HandleRowClick(Person person)
    {
        NavigationManager.NavigateTo($"/person/{person.Id}");
    }

    private record Person(int Id, string Name, string Email, string Department);
}

La funcionalidad incluye estilos CSS integrados que aplican un cursor de puntero a las filas en las que se puede hacer clic mediante la clase CSS row-clickable, lo que proporciona una indicación visual clara a los usuarios.

La representación previa del lado cliente en una Blazor Web App mantiene la cultura del servidor

De forma predeterminada, la representación previa del lado cliente en el servidor (proyecto .Client en una Blazor Web App) conserva CurrentCulture y CurrentUICulture del servidor en estado del componente y los aplica en el cliente antes de que se carguen los ensamblados satélite.

Las aplicaciones que requieren que el cliente elija una cultura independientemente del servidor pueden omitirse con WebAssemblyComponentsOptions.UseCultureFromServer en el archivo Blazor Web App de Program:

builder.Services.AddRazorComponents()
    .AddInteractiveWebAssemblyComponents(options =>
    {
        options.UseCultureFromServer = false;
    });

Mantener los datos de sesión entre solicitudes HTTP durante la representación estática del lado del servidor (SSR estática)

La persistencia de datos de sesión lee y escribe valores de sesión HTTP basados en cookie durante la representación estática del lado del servidor (SSR estática), lo que resulta útil en casos como los identificadores de carro de la compra o el progreso de formularios de varios pasos. A diferencia de la persistencia de datos temporales (ITempData), los valores de sesión no se borran después de la lectura. Los valores se conservan a lo largo de varias solicitudes durante toda la sesión.

La configuración del almacenamiento de sesión requiere agregar servicios llamando a AddSession y configurar la canalización de solicitudes con UseSession:

builder.Services.AddDistributedMemoryCache();
builder.Services.AddSession();
builder.Services.AddRazorComponents();

var app = builder.Build();

app.UseSession();

Cuando se proporciona a un parámetro, use el [SupplyParameterFromSession] atributo sin o con una clave (cadena):

[SupplyParameterFromSession]
public string? Message { get; set; }

[SupplyParameterFromSession(Name = "flash_message")]
public string? FlashMessage { get; set; }

Para obtener más información, vea ASP.NET Core Blazor administración de estado del lado servidor.

Método de extensión GetUriWithFragment

Un nuevo GetUriWithFragment método de extensión permite NavigationManager construir fácilmente URI con fragmentos hash. Este método auxiliar ofrece una forma eficiente, sin asignaciones de memoria, de agregar fragmentos de hash al URI actual. En el ejemplo siguiente se muestran dos casos de uso:

  • Llamada en línea que salta a la Sección 1 (id="section-1") de la página renderizada.
  • Llamada al método que recibe un identificador de sección (sectionId) y navega a la sección de la página.
@inject NavigationManager Navigation

<a href="@Navigation.GetUriWithFragment("section-1")">
    Jump to Section 1
</a>

@code {
    private void NavigateToSection(string sectionId)
    {
        var uri = Navigation.GetUriWithFragment(sectionId);
        Navigation.NavigateTo(uri);
    }
}

El método usa string.Create para obtener un rendimiento óptimo y funciona correctamente con URI base no raíz (por ejemplo, cuando se usa <base href="/app/">).

Componente EnvironmentView

Blazor ahora incluye un componente integrado EnvironmentView para la representación condicional en función del entorno de hospedaje. Este componente proporciona una manera coherente de representar contenido en función del entorno actual en los modelos de hospedaje del lado servidor y del lado cliente.

El EnvironmentView componente acepta parámetros Include y Exclude para especificar nombres de entorno. El componente realiza comparaciones sin distinguir entre mayúsculas y minúsculas y sigue la misma semántica que la de MVC EnvironmentTagHelper.

@using Microsoft.AspNetCore.Components.Web

<EnvironmentView Include="Development">
    <div class="alert alert-warning">
        Debug mode enabled
    </div>
</EnvironmentView>

<EnvironmentView Include="Development,Staging">
    <p>Pre-production environment</p>
</EnvironmentView>

<EnvironmentView Exclude="Production">
    <p>@DateTime.Now</p>
</EnvironmentView>

Compatibilidad con el espacio de nombres MathML

Blazor ahora admite elementos MathML en la representación interactiva. Los elementos MathML, como <math>, <mrow>, <mi>y <mn>, se crean con el espacio de nombres correcto (http://www.w3.org/1998/Math/MathML) mediante document.createElementNS(), similar a cómo se controlan los elementos SVG:

<math>
    <mrow>
        <mi>x</mi>
        <mo>=</mo>
        <mfrac>
            <mrow>
                <mo>−</mo>
                <mi>b</mi>
                <mo>±</mo>
                <msqrt>
                    <mrow>
                        <msup><mi>b</mi><mn>2</mn></msup>
                        <mo>−</mo>
                        <mn>4</mn>
                        <mi>a</mi>
                        <mi>c</mi>
                    </mrow>
                </msqrt>
            </mrow>
            <mrow>
                <mn>2</mn>
                <mi>a</mi>
            </mrow>
        </mfrac>
    </mrow>
</math>

Esta corrección garantiza que el contenido mathML se represente correctamente en los exploradores cuando se agrega dinámicamente a través Blazordel representador, resolviendo problemas en los que los elementos MathML se crearon previamente como elementos HTML normales sin el espacio de nombres adecuado.

InvokeVoidAsync() analizador

Se ha agregado un nuevo analizador Blazor (BL0010) que recomienda usar InvokeVoidAsync en lugar de InvokeAsync<object> al llamar a funciones de JavaScript que no devuelven valores. Este analizador ayuda a los desarrolladores a escribir código JSInterop más eficaz.

Código problemático:

// ⚠️ BL0010: Use InvokeVoidAsync for JavaScript functions that don't return a value
await JSRuntime.InvokeAsync<object>("console.log", "Hello");

Código recomendado:

// ✅ Correct: Use InvokeVoidAsync
await JSRuntime.InvokeVoidAsync("console.log", "Hello");

El analizador ayuda a detectar problemas de rendimiento cuando InvokeAsync se usa innecesariamente con object o se ignoran los valores devueltos, y orienta a los desarrolladores hacia el método InvokeVoidAsync más adecuado.

IComponentPropertyActivator

Blazor suministra ahora IComponentPropertyActivator para personalizar cómo se rellenan las propiedades de [Inject] en los componentes. Esto habilita escenarios avanzados como:

  • Proporcionar contexto adicional para la resolución de propiedades.
  • Compatibilidad con contenedores de DI personalizados que necesitan interceptar la inserción de propiedades.
  • Escenarios avanzados que requieren personalización de la inyección de propiedades.
public interface IComponentPropertyActivator
{
    Action<IServiceProvider, IComponent> GetActivator(
        [DynamicallyAccessedMembers(Component)] Type componentType);
}

La implementación predeterminada almacena en caché los activadores por tipo de componente, admite servicios con claves a través de [Inject(Key = "...")], se integra con Recarga activa para la invalidación de caché e incluye anotaciones de recorte adecuadas para la compatibilidad con AOT.

SignalR ConfigureConnection para componentes de Interactive Server

Blazor ahora proporciona acceso a la configuración de las opciones subyacentes de conexión de SignalR al usar componentes de servidor interactivo a través de la nueva propiedad ConfigureConnection en ServerComponentsEndpointOptions. Esto permite configurar propiedades de HttpConnectionDispatcherOptions a las que antes solo se podía acceder mediante soluciones alternativas.

app.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(options =>
    {
        options.ConfigureConnection = dispatcherOptions =>
        {
            dispatcherOptions.CloseOnAuthenticationExpiration = true;
            dispatcherOptions.AllowStatefulReconnects = true;
            dispatcherOptions.ApplicationMaxBufferSize = 1024 * 1024;
        };
    });

Esto proporciona una API limpia y segura para tipos para configurar las opciones de conexión de SignalR sin necesidad de inspeccionar los metadatos de puntos de conexión.

Compatibilidad de IHostedService en Blazor WebAssembly

Blazor WebAssembly ahora admite IHostedService la ejecución de servicios en segundo plano en el explorador. Esto aporta paridad de características con Blazor Server y permite escenarios como la actualización periódica de datos, las actualizaciones en tiempo real y el procesamiento en segundo plano.

public class DataRefreshService : IHostedService
{
    private Timer? _timer;
    
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _timer = new Timer(RefreshData, null, TimeSpan.Zero, TimeSpan.FromMinutes(5));
        return Task.CompletedTask;
    }

    private void RefreshData(object? state)
    {
        // Refresh data periodically
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _timer?.Dispose();
        return Task.CompletedTask;
    }
}

// Registration
builder.Services.AddHostedService<DataRefreshService>();

Los servicios hospedados se inician cuando la aplicación se inicia y se detiene cuando se apaga, lo que proporciona un ciclo de vida limpio para las operaciones en segundo plano en Blazor WebAssembly las aplicaciones.

Configurar Blazor el comportamiento del cliente desde el servidor

Blazor Las aplicaciones ahora pueden configurar desde el servidor en C# el comportamiento de inicio del cliente al asignar componentes Razor en lugar de escribir código JavaScript Blazor.start a mano. WithBrowserOptions establece las opciones que el servidor serializa en la página representada y el Blazor script se aplica en el explorador, en los modos Servidor, WebAssembly y Representación automática. Las opciones abarcan el nivel de registro de cliente, la reconexión interactiva del servidor, si la navegación mejorada conserva el DOM y el nombre del entorno de ejecución de WebAssembly, la referencia cultural y las variables de entorno:

app.MapRazorComponents<App>()
    .WithBrowserOptions(options =>
    {
        options.InteractiveServer.ReconnectionDialogId = "reconnect-dialog";
        options.InteractiveServer.ReconnectionMaxRetries = 10;
        options.InteractiveServer.ReconnectionRetryInterval = TimeSpan.FromSeconds(1.5);
        options.InteractiveWebAssembly.EnvironmentVariables["OTEL_EXPORTER_OTLP_ENDPOINT"] =
            "https://localhost:4318";
        options.StaticServer.CircuitInactivityTimeout = TimeSpan.FromSeconds(1.5);
        options.StaticServer.PreserveDom = true;
        options.InteractiveWebAssembly.ApplicationCulture = "en-ca";
        options.InteractiveWebAssembly.EnvironmentName = "Staging";
        options.LogLevel = LogLevel.Warning;
    });

También puede establecer las opciones en un componente Razor con el componente ConfigureBrowser:

<ConfigureBrowser Options="RequestBrowserOptions" />

...
    
@code {
    private BrowserOptions RequestBrowserOptions => new()
    {
        LogLevel = LogLevel.Trace,
        StaticServer = { PreserveDom = true }
    };
}

Lea las opciones resueltas de HttpContext con GetBrowserOptions():

@BrowserOptions.GetBrowserOptions(HttpContext).LogLevel

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

No realice comentarios sobre problemas y PR cerrados. Abra un nuevo problema para proporcionar comentarios sobre esta API.

Variables de entorno en la configuración Blazor WebAssembly

Blazor WebAssembly Las aplicaciones ahora pueden acceder a las variables de entorno a través de IConfiguration. Esto permite la configuración en tiempo de ejecución sin volver a generar la aplicación, lo que facilita la implementación de la misma compilación en entornos diferentes.

En el ejemplo siguiente, las API_ENDPOINT variables de entorno y ENABLE_FEATURE_X se incluyen automáticamente en la configuración:

var builder = WebAssemblyHostBuilder.CreateDefault(args);

var apiEndpoint = builder.Configuration["API_ENDPOINT"];
var featureFlag = builder.Configuration["ENABLE_FEATURE_X"];

Las variables de entorno se cargan en el sistema de configuración junto con otros orígenes de configuración, como la configuración de la aplicación (appsettings.json), lo que proporciona una manera unificada de acceder a los valores de configuración independientemente de su origen.

Blazor WebAssembly métricas de componentes y trazabilidad

Blazor WebAssembly Las aplicaciones ahora proporcionan métricas y seguimientos específicos del componente cuando se ha habilitado la compatibilidad con las métricas en tiempo de ejecución.

Habilitación de la compatibilidad con contenedores en Blazor Web App la plantilla

La plantilla de proyecto Blazor Web App ahora admite la opción Enable container support en Visual Studio. Esto facilita la inclusión en contenedores de Blazor Web App e implementarlos en plataformas de orquestación de contenedores, como Kubernetes o Azure Container Apps.

Static SSR admite la validación del lado cliente

Blazor Los formularios con renderizado estático del lado del servidor (SSR estático) ahora reciben validación instantánea en el navegador sin necesidad de ida y vuelta al servidor, igual que la experiencia que ofrecen las aplicaciones interactivas Blazor y las aplicaciones MVC con validación no intrusiva. El modelo de .NET sigue siendo el único origen de verdad para las reglas de validación. El servidor genera los metadatos de las reglas de validación, que luego el código BlazorJS del lado del cliente se encarga de hacer cumplir.

La característica está habilitada de forma predeterminada para todos los formularios SSR estáticos que incluyen el DataAnnotationsValidator componente. Se admiten formularios mejorados y no mejorados.

La cobertura completa de funcionalidades se encuentra en la validación de formularios en ASP.NET CoreBlazor.

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

No realice comentarios sobre problemas y PR cerrados. Si tiene comentarios sobre esta característica, abra un nuevo problema en el repositorio dotnet/aspnetcore GitHub.

Compatibilidad con la validación asincrónica de formularios

Blazor Los formularios reciben compatibilidad con reglas de validación asincrónicas, como búsquedas de base de datos o llamadas API remotas. En cualquier modo de representación, la validación de envío de EditForm espera ahora correctamente validadores asincrónicos de un extremo a otro. En modos interactivos, los componentes del validador pueden registrar la validación asincrónica por campo a través de EditContext.RegisterAsyncFieldValidator. El marco de trabajo realiza su seguimiento, cancela las validaciones sustituidas y muestra el estado del progreso a través de IsValidationPending(field) y IsValidationFaulted(field).

El componente integrado DataAnnotationsValidator ejecuta las API asincrónicas (AsyncValidationAttribute y IAsyncValidatableObject), por lo que las reglas asincrónicas DataAnnotations declaradas en el modelo funcionan sin configuración adicional.

<EditForm EditContext="editContext" OnSubmit="HandleSubmit">
    <InputText @bind-Value="model.Username" />
    @if (editContext.IsValidationPending(() => model.Username))
    {
        <span>Checking availability...</span>
    }
    <ValidationMessage For="() => model.Username" />
    <button type="submit">Register</button>
</EditForm>

@code {
    [Inject] public UserService Users { get; set; } = default!;

    private readonly RegistrationModel model = new();
    private EditContext editContext = default!;
    private ValidationMessageStore messages = default!;

    protected override void OnInitialized()
    {
        editContext = new EditContext(model);
        messages = new ValidationMessageStore(editContext);
        editContext.OnFieldChanged += (_, e) =>
        {
            if (e.FieldIdentifier.FieldName == nameof(model.Username))
            {
                editContext.RegisterAsyncFieldValidator(e.FieldIdentifier,
                    token => CheckAsync(e.FieldIdentifier, model.Username, token));
            }
        };
    }

    private async Task CheckAsync(FieldIdentifier field, string value, CancellationToken ct)
    {
        messages.Clear(field);
        if (await Users.IsUsernameTakenAsync(value, ct))
        {
            messages.Add(field, "Username is taken.");
        }
        editContext.NotifyValidationStateChanged();
    }

    private async Task HandleSubmit() => await editContext.ValidateAsync();
}

La cobertura completa de funcionalidades se encuentra en la validación de formularios en ASP.NET CoreBlazor.

Para obtener más información, vea Agregar compatibilidad integrada para la validación de formularios asincrónicos en Blazor (dotnet/aspnetcore #66526).

No realice comentarios sobre problemas y PR cerrados. Si tiene comentarios sobre esta característica, abra un nuevo problema en el repositorio dotnet/aspnetcore GitHub.

Blazor y las API mínimas admiten la localización de errores

La validación de formularios Blazor y de los puntos de conexión de Minimal API recibe compatibilidad de primer nivel para la localización de mensajes de error y nombres de propiedad. La localización se activa automáticamente en cuanto hay un IStringLocalizerFactory disponible. De forma predeterminada, la localización registrada por AddLocalization usa archivos RESX específicos del lenguaje implementados como parte del ensamblado.

builder.Services.AddLocalization();
builder.Services.AddValidation();
[ValidatableType]
public class ContactModel
{
    // Values of ErrorMessage are used as localization keys.
    [Required(ErrorMessage = "RequiredError")]
    [EmailAddress(ErrorMessage = "EmailError")]
    [Display(Name = "ContactEmail")]
    public string? Email { get; set; }
}

Las aplicaciones también pueden registrar implementaciones personalizadas IStringLocalizerFactory para leer las cadenas localizadas de otros orígenes, como bases de datos o archivos JSON. Un tipo registrado por el usuario tiene prioridad sobre la localización predeterminada de RESX.

builder.Services.AddSingleton<IStringLocalizerFactory, DbStringLocalizerFactory>();
builder.Services.AddValidation();

Para resolver las claves de un archivo de recursos compartidos en lugar de los propios recursos del tipo validado, establezca ValidationOptions.LocalizerProvider:

builder.Services.AddValidation(options =>
{
    options.LocalizerProvider = (_, factory) => factory.Create(typeof(ValidationMessages));
});

Cuando un atributo no establece ErrorMessage, las claves de búsqueda convencionales se prueban de la mayoría a la menos específica, quitando la necesidad de especificar claves de localización en cada atributo de validación:

[ValidatableType]
public class ContactModel
{
    // Looks up 'ContactModel_Username_RequiredAttribute_Error', then
    // 'ContactModel_RequiredAttribute_Error', then 'RequiredAttribute_Error'.
    [Required]
    public string? Username { get; set; }
}

La cobertura completa de características está disponible en los siguientes artículos:

Para obtener más información, consulte Agregar compatibilidad con la localización en Microsoft. Extensions.Validation (dotnet/aspnetcore #66646).

No realice comentarios sobre problemas y PR cerrados. Si tiene comentarios sobre esta característica, abra un nuevo problema en el repositorio dotnet/aspnetcore GitHub.

Correcciones de TempData y de la persistencia de [SupplyParameterFromSession] para SSR en streaming

Cuando una página usa características respaldadas por sesión, donde un componente tiene un [SupplyParameterFromSession] parámetro (que crea una suscripción) o el proveedor tempData de almacenamiento de sesión está activo, la sesión cookie (.AspNetCore.Session) ahora se emite antes de que comience el streaming, incluso si no se escribe ningún valor en última instancia. Las páginas que no usan características respaldadas por sesión no se ven afectadas.

Para obtener más información, consulte Corrección de la persistencia de TempData y SupplyParameterFromSession en el caso de SSR con streaming (dotnet/aspnetcore #66832). (No realice comentarios sobre problemas y PR cerrados.)

Middleware antifalsificación (app.UseAntiforgery()) opcional en las Blazor Web Apps

La protección CSRF está habilitada de forma predeterminada a través del middleware de protección CSRF insertado automáticamente, por lo que una llamada explícita app.UseAntiforgery() en un Blazor Web App elemento suele ser innecesaria y solo debe agregarse para casos de uso específicos. La llamada ya no aparece en las aplicaciones creadas a partir de la plantilla de Blazor Web App proyecto.

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

Cobertura general de la nueva protección CSRF automática en ASP.NET Core:

Blazor Virtualize puede desplazarse hasta un elemento

El Virtualize<TItem> componente ahora puede abrirse en un elemento específico y desplazarse a cualquier elemento a petición. Dos nuevas API públicas hacen que esto sea posible:

  • InitialItemIndex coloca la lista en un elemento determinado en la primera representación interactiva, por lo que la lista se abre en ese elemento sin un flash del primer elemento.
  • ScrollToItemAsync(int itemIndex, CancellationToken cancellationToken = default) se desplaza hasta un elemento en cualquier momento después del primer renderizado y devuelve un Task que se completa cuando el destino se haya alineado con la parte superior del área visible.
<Virtualize TItem="Product" Items="products" InitialItemIndex="500" @ref="list">
    <div class="product">@context.Name</div>
</Virtualize>

<button @onclick="GoToTop">Back to top</button>

@code {
    private Virtualize<Product> list = default!;
    private List<Product> products = ProductCatalog.All;

    private async Task GoToTop() => await list.ScrollToItemAsync(0);
}

Los índices fuera de rango se limitan al rango válido. Si se inicia una segunda ScrollToItemAsync invocación mientras la primera sigue en curso, prevalece la última invocación. Llamar a ScrollToItemAsync antes del primer renderizado interactivo provoca InvalidOperationException; use InitialItemIndex para establecer la posición inicial en su lugar.

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

Pausa automática del circuito por inactividad de la pestaña

La pausa automática puede pausar un circuito cuando la pestaña del explorador se oculta, liberando la memoria del servidor y SignalR las conexiones que mantienen los usuarios inactivos. Es una función opcional incluida en el paquete Microsoft.AspNetCore.Components.Server.AutoPause. Después de agregar una referencia de paquete, habilite la característica llamando a AddAutoPause cuando se asigne el componente raíz de la aplicación:

app.MapRazorComponents<App>()
    .WithBrowserOptions(options => options.AddAutoPause(p => p.HiddenDelay = TimeSpan.FromSeconds(30)));

Una vez oculta la pestaña durante un período de retraso configurable (valor predeterminado: 2 minutos), el circuito se detiene. Si el usuario vuelve antes de que transcurre el retraso, no se produce la pausa.

Para obtener más información, vea ASP.NET Core Blazor administración de estado del lado servidor.

Mayor compatibilidad con AuthorizationPolicy y IAuthorizationRequirementData

A partir de .NET 11, puede aplicar atributos IAuthorizationRequirementData a los hubs y métodos de hub de SignalR, a los controladores y acciones de MVC, y a los componentes Blazor y AuthorizeRouteView de AuthorizeView, no solo a los puntos de conexión. En el caso de las aplicaciones que tienen como destino versiones anteriores a .NET 11, estos atributos solo se aplican en la API mínima y los puntos de conexión enrutados.

Para obtener más información, consulte Directivas de autorización personalizadas con "IAuthorizationRequirementData".

QuickGrid Las API de Virtualize se exponen

Las siguientes nuevas API del componente QuickGrid están disponibles cuando una cuadrícula se virtualiza (Virtualize se establece en true):

  • InitialItemIndex: desplaza la cuadrícula hasta el índice de fila de base cero dado en la primera representación interactiva. El valor se aplica una vez y se fija en el intervalo válido. Esto se reenvía al componente interno Virtualize. Para obtener más información, consulta Virtualización de componentes Razor de ASP.NET Core.
  • ScrollToItemAsync: desplaza la cuadrícula mediante programación hasta el índice de fila basado en cero indicado, alineándola en la parte superior. La última llamada prevalece, y el método lanza una InvalidOperationException cuando la virtualización está deshabilitada o la rejilla aún no se ha renderizado. Esto se reenvía al componente interno Virtualize. Para obtener más información, consulta Virtualización de componentes Razor de ASP.NET Core.
  • AnchorMode: controla cómo se comporta la ventanilla en los bordes de la lista cuando los elementos se agregan dinámicamente (valor predeterminado: Start). Se trata de una API experimental que requiere habilitar explícitamente la comprobación de diagnóstico ASP0030 y reenvía al componente interno Virtualize. Para obtener más información, consulta Virtualización de componentes Razor de ASP.NET Core.
  • ItemComparer: comparador utilizado para detectar si los elementos se añadieron al principio o al final entre cargas de datos sucesivas, lo que resulta útil para elementos tipados por clase proporcionados por un ItemsProvider. Se trata de una API experimental que requiere habilitar explícitamente la comprobación de diagnóstico ASP0030 y reenvía al componente interno Virtualize. Para obtener más información, consulta Virtualización de componentes Razor de ASP.NET Core.

Para obtener más información, vea ASP.NET Core Blazor componente "QuickGrid".

ValidatableTypeAttribute y SkipValidationAttribute ya no son experimentales

Los ValidatableTypeAttribute atributos y SkipValidationAttribute del Microsoft.Extensions.Validation paquete NuGet ya no son experimentales.

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

Almacenar en caché el resultado renderizado de un subárbol de componentes durante el SSR estático

El nuevo componente CacheView almacena en caché la salida renderizada de un subárbol de componentes Razor durante el renderizado estático del lado del servidor (SSR estático). Cuando se produce un acierto de caché, el marcado almacenado en caché se reutiliza sin instanciar ni ejecutar el ciclo de vida de los componentes secundarios incluidos en la salida en caché.

CacheView resulta útil para secciones costosas, principalmente estáticas de una página que no requieren que se almacene en caché toda la respuesta:

<CacheView VaryByQuery="category" ExpiresAfter="TimeSpan.FromMinutes(5)">
    <ProductList Category="@Category" />
</CacheView>

Para obtener más información, consulte ASP.NET Core Blazor componente CacheView.

Blazor Server los circuitos se actualizan después de actualizar la autenticación

Los componentes de Interactive Server ahora pueden recibir la actualización ClaimsPrincipal sin volver a conectar el circuito. El centro de componentes y el Blazor cliente habilitan la actualización de autenticación automáticamente, por lo que no se requiere ninguna configuración adicional.

Una vez que la conexión actualiza su autenticación, Blazor actualiza el estado de autenticación y genera AuthenticationStateChanged. Los componentes que consumen AuthenticationStateProvider, incluidos AuthorizeView, se vuelven a renderizar con la identidad y las declaraciones actualizadas. Este comportamiento es útil cuando los roles o permisos de un usuario cambian durante un circuito activo o cuando un componente debe recargar contenido específico del usuario después de que se actualicen las declaraciones. La interfaz de usuario puede reflejar el nuevo estado de autenticación sin forzar al usuario a volver a conectarse o volver a cargar la página.

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

No realice comentarios sobre problemas y PR cerrados. Si tiene comentarios sobre esta característica, abra un nuevo problema en el repositorio dotnet/aspnetcore GitHub.

Componentes experimentales Blazor de inteligencia artificial para interfaces de usuario agente

Las aplicaciones modernas de inteligencia artificial proporcionan cada vez más interacciones enriquecidas con los agentes. Es posible que una interfaz de usuario agente completa necesite transmitir el trabajo en curso, visualizar el razonamiento y el progreso del agente, solicitar aprobación antes de que las herramientas actúen, acepten la entrada multiplataforma y sincronicen el estado entre la aplicación y el agente. Los Blazor componentes de IA están diseñados para proporcionar los componentes básicos para crear estas experiencias con el modelo de componentes de Blazor.

El nuevo Microsoft.AspNetCore.Components.AI paquete NuGet incluye un conjunto inicial de componentes de Blazor IA para el chat en streaming, la representación de texto enriquecido y de herramientas, los flujos de aprobación humana y el estado de la interfaz de usuario tipado, compartido y predictivo.

Comenzar

Importante

El Microsoft.AspNetCore.Components.AI paquete es una versión preliminar, un paquete experimental a lo largo de .NET 11.

Agregue el paquete a una Blazor aplicación:

dotnet add package Microsoft.AspNetCore.Components.AI --prerelease

El chat básico y el modelo de bloque de Components.AI funcionan con cualquier IChatClient. Para conectar la Blazor aplicación a un agente remoto a través del Protocolo de interacción del usuario del agente (AG-UI), instale el AGUI.Client paquete:

dotnet add package AGUI.Client

AGUI.Client incluye una referencia transitiva a AGUI.Abstractions, que proporciona los tipos de eventos AG-UI usados en ejemplos posteriores. Registre un AGUIChatClient como IChatClient de la aplicación:

using AGUI.Client;
using Microsoft.Extensions.AI;

builder.Services.AddHttpClient<IChatClient>(httpClient =>
    new AGUIChatClient(new(httpClient, "https://api.example.com/agent")));

AGUIChatClient transmite eventos AG-UI como ChatResponseUpdate valores. Los componentes de IA Blazor muestran el contenido conversacional a partir de estas actualizaciones, mientras que las aplicaciones pueden utilizar la información adicional sobre eventos de AG-UI para crear interacciones agénticas más completas. Aunque se admite la funcionalidad básica de chat con cualquier IChatClient, se requiere AG-UI cuando un servidor remoto y el cliente Blazor deben intercambiar declaraciones de herramientas del frontend, eventos de herramientas del backend, interrupciones de aprobación, eventos de estado compartido o identificadores de conversación de AG-UI.

Microsoft Agent Framework (MAF) puede exponer un AIAgent mediante un punto de conexión AG-UI de ASP.NET Core. Para la configuración del lado servidor, consulte AG-UI integración con Agent Framework y su guía de introducción .NET.

Creación de una conversación básica de streaming

El primer paso de una interfaz de usuario agente suele ser una conversación básica que transmite respuestas y conserva el historial de mensajes a través de turnos. El soporte inicial para chat es independiente del proveedor y del protocolo. Las aplicaciones proporcionan un IChatClient a partir de Microsoft.Extensions.AI, y UIAgent convierte sus respuestas de transmisión en bloques de contenido observables.

ChatPage es un shell de chat completo que combina tres componentes de nivel inferior:

  • AgentBoundary crea y propaga en cascada el estado de la conversación.
  • MessageList renderiza cada turno a medida que se transmite y proporciona las interfaces predeterminadas de escritura, error y reintento.
  • MessageInput envía mensajes desde un campo de texto y desactiva la entrada mientras se recibe una respuesta en streaming.

El siguiente componente crea un UIAgent sobre un IChatClient proporcionado por la aplicación y muestra la conversación con ChatPage:

@rendermode InteractiveServer
@using Microsoft.AspNetCore.Components.AI
@using Microsoft.Extensions.AI
@implements IDisposable
@inject IChatClient ChatClient

<ChatPage Agent="agent" Placeholder="Type a message...">
    <WelcomeContent>
        <p>Ask the agent a question.</p>
    </WelcomeContent>
</ChatPage>

@code {
    private UIAgent agent = default!;

    protected override void OnInitialized()
    {
        agent = new UIAgent(ChatClient);
    }

    public void Dispose() => agent.Dispose();
}

Placeholder establece la sugerencia que se muestra en el campo de entrada de mensajes vacío. WelcomeContent proporciona el contenido que se muestra antes de enviar el primer mensaje.

Incluya los estilos del componente en el componente App (Components/App.razor):

<link rel="stylesheet" href="@Assets["_content/Microsoft.AspNetCore.Components.AI/ai-chat.css"]" />

Blazor Interfaz de chat de IA que muestra una conversación con un agente de planificación de viajes

Representar bloques de contenido

Un IChatClient transmite en flujo contenido destinado al modelo, como TextContent, RichTextContent y FunctionCallContent, en valores de ChatResponseUpdate. UIAgent convierte este contenido de la respuesta en objetos de ContentBlock orientados a la interfaz de usuario que conservan el estado de renderizado y pueden actualizarse in situ mientras la respuesta se transmite en streaming. Por ejemplo, tanto los fragmentos de texto sin formato como las instantáneas estructuradas de texto enriquecido se corresponden con un RichContentBlock.

Los tipos de bloques integrados incluyen:

  • RichContentBlock para texto transmitido y contenido enriquecido estructurado.
  • FunctionInvocationContentBlock para una llamada a una función del servidor y su resultado final.
  • UIActionBlock para una función que se ejecuta en la Blazor aplicación.
  • FunctionApprovalBlock para una invocación de función en espera de la aprobación del usuario.
  • ActivityContentBlock para el progreso definido por la aplicación que se actualiza en el mismo lugar.

ChatPage e MessageList incluyen la representación predeterminada para RichContentBlock y FunctionApprovalBlock. Agregue un BlockRenderer<TBlock> a ChatPage.MessageListContent para reemplazar esta representación predeterminada o representar otro tipo de bloque. Su contenido secundario recibe el bloque coincidente como context, incluidas sus propiedades actuales a medida que cambian durante el streaming.

En el ejemplo siguiente se reemplaza la representación predeterminada para el contenido conversacional:

<ChatPage Agent="agent">
    <MessageListContent>
        <BlockRenderer TBlock="RichContentBlock" Context="block">
            <div class="agent-response">@block.RawText</div>
        </BlockRenderer>
    </MessageListContent>
</ChatPage>

Usa el predicado del renderizador When para gestionar únicamente los bloques seleccionados de un tipo. Si coinciden varios representadores, el representador registrado más recientemente tiene prioridad. Las aplicaciones también pueden definir tipos personalizados ContentBlock y asignar contenido de respuesta a ellos con un ContentBlockHandler<TState>.

Renderizar texto enriquecido estructurado

La compatibilidad con texto enriquecido permite a un agente devolver un modelo de presentación estructurado en lugar de texto sin formato. RichTextContent es contenido de respuesta que contiene texto sin formato y RichTextNode valores para encabezados, párrafos, énfasis, vínculos, listas, bloques de código, tablas y otros elementos de presentación. UIAgent lo asigna al mismo RichContentBlock que se utiliza para el TextContent sin formato, pero utiliza el árbol de nodos proporcionado en lugar de crear párrafos simples.

Las aplicaciones pueden generar RichTextContent directamente o usar middleware IChatClient para transformar TextContent en flujo, por ejemplo, analizando Markdown en valores RichTextNode. El paquete no requiere ni incluye una implementación de Markdown determinada. Cada RichTextContent es una instantánea completa, por lo que reemplaza de forma atómica el contenido anterior del mismo mensaje a medida que avanza la transmisión. ChatPage y MessageList representan a continuación el contenido estructurado sin necesidad de un BlockRenderer personalizado.

Representar llamadas a herramientas del servidor

Un agente puede llamar a una herramienta que se ejecuta en su servidor mientras la Blazor aplicación representa la operación mediante la interfaz de usuario específica de la aplicación. Por ejemplo, el agente puede llamar a una herramienta meteorológica y la aplicación puede mostrar la ubicación solicitada inmediatamente, seguida de una tarjeta meteorológica cuando el servidor devuelve el resultado.

Las llamadas a herramientas del servidor se convierten en instancias de FunctionInvocationContentBlock, que asocian FunctionCallContent con su correspondiente FunctionResultContent final y exponen el nombre de la herramienta, los argumentos y el estado de finalización.

El generador de código fuente del paquete crea un manejador de bloques con tipado fuerte a partir de una clase anotada con ToolBlock, ToolParameter y ToolResult ([Blazor] Añadir renderización de herramientas del servidor de Components.AI (dotnet/aspnetcore #68327)):

[ToolBlock("get_weather")]
public partial class WeatherToolBlock : FunctionInvocationContentBlock
{
    [ToolParameter(Name = "location")]
    public string? Location { get; set; }

    [ToolResult]
    public WeatherInfo? Weather { get; set; }
}

Registre los controladores generados al construir el UIAgent:

var agent = new UIAgent(
    chatClient,
    options => options.AddGeneratedToolBlocks());

Representar el bloque generado en MessageListContent:

<ChatPage Agent="agent">
    <MessageListContent>
        <BlockRenderer TBlock="WeatherToolBlock">
            @if (context.HasResult)
            {
                <p>@context.Location: @context.Weather?.Temperature&deg;C</p>
            }
            else
            {
                <p>Checking the weather for @context.Location...</p>
            }
        </BlockRenderer>
    </MessageListContent>
</ChatPage>

A medida que se transmiten los argumentos de la llamada, el controlador generado actualiza Location mientras HasResult permanece false. Cuando llega el resultado, rellena Weather, establece HasResult en true y vuelve a representar el mismo bloque como la tarjeta meteorológica completada.

Un bloque de herramientas con tipo generado que representa un resultado meteorológico

Cuando MAF hospeda el agente remoto, las herramientas de back-end usan su canalización de herramientas normal y AG-UI transporta la llamada y el resultado al cliente. Consulte Representación de herramientas de back-end con AG-UI.

Ejecución de herramientas de front-end

Las herramientas de front-end se ejecutan en la aplicación cliente en lugar de en el servidor del agente. Por ejemplo, una Blazor aplicación puede exponer una herramienta que cambia el estado de la interfaz de usuario, lee una preferencia local o pide al usuario que escriba. Cree la herramienta con AIFunctionFactory desde Microsoft.Extensions.AIy regístrela con UIAgentOptions.RegisterUIAction ([Blazor] Agregue Components.AI representación de herramientas cliente (dotnet/aspnetcore #68325)).

RegisterUIAction comunica el AIFunction al agente. Cuando el agente lo solicita, UIAgent crea un UIActionBlock en lugar de ejecutar la función inmediatamente:

var setAccentColor = AIFunctionFactory.Create(
    async (string color) =>
    {
        await InvokeAsync(() =>
        {
            accentColor = color;
            StateHasChanged();
        });
        return $"Changed the accent color to {color}.";
    },
    name: "set_accent_color");

var agent = new UIAgent(
    chatClient,
    options => options.RegisterUIAction(setAccentColor))

Coloque un BlockRenderer<UIActionBlock> en MessageListContent para controlar la llamada de función solicitada por el agente. Por ejemplo, el representador puede usar un componente que invoca automáticamente la función y muestra su progreso:

<ChatPage Agent="agent">
    <MessageListContent>
        <BlockRenderer TBlock="UIActionBlock"
                       When='@(action => action.ToolName == "set_accent_color")'
                       Context="action">
            <AutoInvokeAction Action="action" />
        </BlockRenderer>
    </MessageListContent>
</ChatPage>

El AutoInvokeAction componente llama a InvokeAsync cuando recibe el bloque :

@if (Action.IsComplete)
{
    <span>Accent color updated</span>
}
else
{
    <span>Updating accent color...</span>
}

@code {
    [Parameter, EditorRequired]
    public UIActionBlock Action { get; set; } = default!;

    protected override async Task OnInitializedAsync()
    {
        if (!Action.IsComplete)
        {
            await Action.InvokeAsync();
        }
    }
}

La llamada a InvokeAsync ejecuta la función registrada con los argumentos proporcionados por el agente. La función se ejecuta allí donde se ejecuta la interfaz de usuario Blazor: en el circuito del servidor para Blazor Server o en el navegador para WebAssembly. Cuando se completa la función, UIAgent devuelve el resultado al agente y continúa la conversación. Utiliza el predicado del renderizador When para proporcionar un tratamiento diferente para cada action.ToolName. Un representador puede invocar automáticamente la acción, como se muestra aquí o presentar la interfaz de usuario que recopila primero la entrada o confirmación.

Requerir aprobación antes de que se ejecuten las herramientas

Una aplicación puede requerir que el usuario apruebe una llamada a una herramienta importante, como programar una reunión, antes de que el agente continúe. Las solicitudes de aprobación de herramientas pasan a ser instancias de FunctionApprovalBlock. La conversación se pausa hasta que la interfaz de usuario llama a Approve o Reject ([Blazor] Añadir flujos de aprobación humana de Components.AI (dotnet/aspnetcore #68329)):

<ChatPage Agent="agent">
    <MessageListContent>
        <BlockRenderer TBlock="FunctionApprovalBlock" Context="approval">
            <p>Allow <code>@approval.ToolName</code> to run?</p>
            <button @onclick="approval.Approve">Approve</button>
            <button @onclick="() => approval.Reject()">Reject</button>
        </BlockRenderer>
    </MessageListContent>
</ChatPage>

Una invocación de herramienta pendiente de aprobación humana

Para un agente de MAF, el servidor decide qué funciones requieren aprobación y AG-UI transporta la solicitud y la decisión. Véase Human-in-the-loop con AG-UI.

Mostrar actividades

Una actividad es un elemento de seguimiento del progreso definido por la aplicación que se actualiza en el mismo lugar mientras un agente realiza una tarea de larga duración. Por ejemplo, un agente de investigación puede mostrar que está buscando orígenes, comparando los resultados y completando la investigación sin agregar un mensaje independiente para cada actualización.

ActivityHandler<TBlock> es un punto de extensión independiente del protocolo que asigna las actualizaciones de progreso específicas del proveedor o de la aplicación a un ActivityContentBlock mutable ([Blazor] Añadir representación del estado de la interfaz de usuario generativa y agentiva (dotnet/aspnetcore n.º 68333)). TryCreateBlock inicializa y emite el bloque para la primera actualización coincidente. TryUpdateBlock modifica el mismo bloque a medida que llegan actualizaciones posteriores e indica cuándo la actividad ha finalizado. Registre el controlador con UIAgentOptions.AddBlockHandlery proporcione un BlockRenderer para el bloque específico de la aplicación. Las actividades no tienen una representación visual predeterminada.

Los eventos ACTIVITY_DELTA y ACTIVITY_SNAPSHOT de AG-UI son una posible fuente de estas actualizaciones. AGUIChatClient expone el evento original a través de ChatResponseUpdate.RawRepresentation. Por ejemplo, una aplicación puede encargarse de reemplazar instantáneas cuya carga útil incluye una propiedad definida por la aplicación complete:

using System.Text.Json;
using Microsoft.AspNetCore.Components.AI;
using AGUI.Abstractions;

public sealed class ResearchActivityBlock : ActivityContentBlock
{
    public string ActivityMessageId { get; set; } = "";
}

public sealed class ResearchActivityHandler
    : ActivityHandler<ResearchActivityBlock>
{
    protected override bool TryCreateBlock(
        BlockMappingContext context,
        ResearchActivityBlock state)
        => TryApplySnapshot(context, state, out _);

    protected override bool TryUpdateBlock(
        BlockMappingContext context,
        ResearchActivityBlock state,
        out bool isCompleted)
        => TryApplySnapshot(context, state, out isCompleted);

    private static bool TryApplySnapshot(
        BlockMappingContext context,
        ResearchActivityBlock state,
        out bool isCompleted)
    {
        isCompleted = false;

        if (context.Update.RawRepresentation is not ActivitySnapshotEvent snapshot ||
            (state.ActivityMessageId.Length > 0 &&
             (state.ActivityMessageId != snapshot.MessageId ||
              snapshot.Replace == false)))
        {
            return false;
        }

        state.ActivityMessageId = snapshot.MessageId;
        state.ActivityType = snapshot.ActivityType;
        state.Content = snapshot.Content;
        isCompleted =
            snapshot.Content.ValueKind == JsonValueKind.Object &&
            snapshot.Content.TryGetProperty("complete", out var complete) &&
            complete.ValueKind == JsonValueKind.True;
        context.MarkUpdateHandled();
        return true;
    }
}

El gestor almacena en el bloque el identificador del mensaje AG-UI para poder relacionarlo con instantáneas posteriores. Las aplicaciones que consumen ActivityDeltaEvent aplican, en su lugar, sus operaciones JSON Patch según el RFC 6902 al Content actual antes de regresar desde TryUpdateBlock. La aplicación define la carga útil de la actividad y la semántica de completado; Components.AI no incluye un gestor de actividad específico de AG-UI ni una implementación de JSON Patch.

Estado compartido

Las interfaces de usuario agente suelen mostrar un área de trabajo compartida junto con la conversación, como una receta, un documento, un formulario o un plan que el agente pueda actualizar. UIAgent<TState> expone estos datos como un estado de la interfaz de usuario tipado y observable, separado del contenido conversacional ([Blazor] Agregar renderizado del estado generativo y agéntico de la interfaz de usuario (dotnet/aspnetcore #68333)).

La aplicación configura un mapeador de estado para los valores ChatResponseUpdate producidos por su IChatClient. En una integración de AG-UI, el servidor del agente asigna explícitamente los resultados seleccionados de la herramienta a eventos STATE_SNAPSHOT o STATE_DELTA. AGUIChatClient expone entonces esos eventos a través de ChatResponseUpdate.RawRepresentation, donde la aplicación Blazor puede deserializarlos y llamar a SetState:

using System.Text.Json;
using AGUI.Abstractions;
using Microsoft.AspNetCore.Components.AI;

var agent = new UIAgent<RecipeState>(chatClient, options =>
{
    options.StateMapper = context =>
    {
        if (context.Update.RawRepresentation is StateSnapshotEvent snapshot &&
            snapshot.Snapshot.Deserialize<RecipeState>(
                JsonSerializerOptions.Web) is { } state)
        {
            context.SetState(state);
        }
    };
});

Lea el valor actual de agent.State.Value y suscríbase a agent.State.OnChanged cuando el componente circundante necesite volver a representar. Los asignadores de estado también pueden controlar aspectos específicos AIContent de la aplicación desde otras IChatClient implementaciones.

Estado del agente tipado representado como una ficha de receta

Para consultar la configuración correspondiente del servidor MAF, incluida la asignación de los resultados de la herramienta a instantáneas y deltas de estado, consulte Gestión de estado con AG-UI.

Mostrar el estado predictivo de la interfaz de usuario

El estado predictivo permite a una aplicación representar el cambio de estado propuesto de un agente mientras el modelo sigue generandolo sin reemplazar el estado confirmado. Por ejemplo, cuando un agente genera el contenido completo de un documento editado como argumento de una herramienta, la interfaz de usuario puede mostrar de forma progresiva el documento propuesto y una vista de diferencias. Cuando finaliza la generación, el usuario puede aceptar la propuesta completada o rechazarla y restaurar el documento confirmado.

Una integración con el servidor AG-UI puede asignar los argumentos transmitidos por una herramienta de escritura de estado a eventos de estado provisionales. Los argumentos de llamada a herramientas completados son la propuesta autoritativa. Al crear UIAgent<TState>, configure su asignador de estado para deserializar esos eventos y llamar a SetPredictiveState. AgentState<TState> a continuación, conserva el valor confirmado anterior para la reversión ([Blazor] Agregar actualizaciones de estado predictivo (dotnet/aspnetcore #68335)):

using System.Text.Json;
using AGUI.Abstractions;
using Microsoft.AspNetCore.Components.AI;

var agent = new UIAgent<DocumentState>(chatClient, options =>
{
    options.StateMapper = context =>
    {
        if (context.Update.RawRepresentation is StateSnapshotEvent snapshot &&
            snapshot.Snapshot.Deserialize<DocumentState>(
                JsonSerializerOptions.Web) is { } predictedState)
        {
            context.SetPredictiveState(predictedState);
        }
    };

    options.RegisterUIAction(AIFunctionFactory.Create(
        ConfirmChanges,
        name: "confirm_changes",
        description: "Confirm the proposed document changes."));
});

El ejemplo también registra una acción de interfaz de usuario confirm_changes. Cuando aparece la acción, un representador de bloques personalizado muestra los controles accept y reject. El renderizador añade la elección del usuario como argumento accepted y llama a UIActionBlock.InvokeAsync, que ejecuta la función de devolución de llamada registrada:

private string ConfirmChanges(bool accepted)
{
    if (accepted)
    {
        agent.State.AcceptPredictiveState();
    }
    else
    {
        agent.State.RejectPredictiveState();
    }

    return accepted
        ? "The user accepted the changes."
        : "The user rejected the changes.";
}

El valor provisional está disponible inmediatamente en agent.State.Value, y HasPendingPredictiveState indica que no se ha confirmado. La llamada de retorno confirma la propuesta completada o restaura la línea de base, y su valor de retorno comunica la decisión al agente en una ejecución posterior. Si la generación falla, se cancela o finaliza sin una decisión, el valor provisional se revierte automáticamente. La extracción y asignación del lado servidor de los argumentos de herramienta transmitidos se deben configurar explícitamente; consulte Administración de estado con AG-UI.

Una propuesta de envío rápido con acciones de aceptación y rechazo

Conservar y restaurar conversaciones

Un IConversationThread almacena los turnos completados para que una interfaz de usuario pueda reconstruir la conversación después de que se reinicie el componente o la aplicación. Un hilo también puede conservar los metadatos del protocolo, como el threadId y el runId anterior utilizados para continuar una conversación de AG-UI propiedad del servidor.

Pase el hilo al crear UIAgent y, a continuación, llame a UIAgent.RestoreAsync o AgentContext.RestoreAsync para reproducir explícitamente las actualizaciones almacenadas en bloques de contenido y en el estado tipado ([Blazor] Añadir el agente compartido y el estado de la interfaz de usuario (dotnet/aspnetcore #68334)):

var agent = new UIAgent(
    chatClient,
    options => options.Thread = conversationThread);

var restoredBlocks = await agent.RestoreAsync();

Pasar un hilo a UIAgent permite que los nuevos turnos completados se conserven, pero no restaura automáticamente los turnos anteriores. Para los agentes de AG-UI alojados en MAF, consulte continuidad de la conversación en AG-UI.

Blazor Hybrid

En esta sección se describen las nuevas características de Blazor Hybrid.

Las notas de la versión se mostrarán en esta sección a medida que las funciones en vista previa estén disponibles.

SignalR

En esta sección se describen las nuevas características de SignalR.

SignalR actualización de autenticación

SignalR las conexiones pueden actualizar la autenticación sin quitar la conexión cuando expire el token de acceso. El servidor expone el punto de conexión /refresh además de /negotiate e informa de la duración del token en la respuesta de negociación. Un cliente vuelve a autenticarse antes de que expire el token, por lo que una conexión con el concentrador que antes se cerraba cuando su token de portador caducaba puede permanecer abierta.

Habilite la característica por centro en el servidor. El servidor puede inspeccionar o rechazar una identidad actualizada devolviendo un valor de OnAuthenticationRefresh:

using System.Security.Claims;

app.MapHub<ChatHub>("/chat", options =>
{
    options.EnableAuthenticationRefresh = true;
    options.CloseOnAuthenticationExpiration = true;

    // Optional: inspect the refreshed identity and decide whether to accept it.
    options.OnAuthenticationRefresh = context =>
    {
        var previousSubject = context.PreviousUser.FindFirstValue("sub")
            ?? context.PreviousUser.FindFirstValue(ClaimTypes.NameIdentifier);
        var newSubject = context.NewUser.FindFirstValue("sub")
            ?? context.NewUser.FindFirstValue(ClaimTypes.NameIdentifier);

        return Task.FromResult(
            previousSubject is not null &&
            string.Equals(previousSubject, newSubject, StringComparison.Ordinal));
    };
});

Un centro puede reaccionar a una identidad actualizada invalidando OnAuthenticationRefreshedAsync:

public class ChatHub : Hub
{
    public override Task OnAuthenticationRefreshedAsync()
    {
        // The connection's User has been updated with the refreshed token.
        return Task.CompletedTask;
    }
}

La actualización automática está activada de forma predeterminada en el cliente de .NET y se puede configurar con WithAuthenticationRefresh. Las notificaciones de actualización son eventos en HubConnection, y RefreshAuthenticationAsync solicita una actualización inmediata después de que la aplicación obtenga nuevos claims:

await using var connection = new HubConnectionBuilder()
    .WithUrl("https://example.com/chat")
    .WithAuthenticationRefresh(options =>
    {
        // EnableAutoRefresh is true by default.
        options.RefreshBeforeExpiration = TimeSpan.FromMinutes(1);
    })
    .Build();

connection.AuthenticationRefreshed += context => Task.CompletedTask;
connection.AuthenticationRefreshFailed += context => Task.CompletedTask;

await connection.StartAsync();

// Refresh immediately after acquiring a token with updated claims.
await connection.RefreshAuthenticationAsync();

Cancelación de las invocaciones del centro desde el cliente

El cliente SignalR puede cancelar una invocación normal, sin streaming, de un método del concentrador. Anteriormente solo se podían cancelar las invocaciones de streaming desde el cliente. Ahora, cuando se pasa un CancellationToken a InvokeAsync y se cancela, el cliente envía un mensaje de cancelación y el parámetro CancellationToken del método del concentrador se activa en el servidor.

// Client — canceling the token cancels the server-side invocation.
using var cts = new CancellationTokenSource();
var work = connection.InvokeAsync("LongRunningWork", cts.Token);
// ...
cts.Cancel();
// Hub — accept a CancellationToken to observe client cancellation.
public class WorkHub : Hub
{
    public async Task LongRunningWork(CancellationToken cancellationToken)
    {
        await Task.Delay(TimeSpan.FromMinutes(5), cancellationToken);
    }
}

SignalR.NET cliente admite la actualización de autenticación después de redireccionamientos

El cliente de .NET SignalR amplía la actualización de autenticación SignalR para que funcione cuando negocia los redireccionamientos a otro servidor, que aporta @MoChilia. Este cambio de cliente permite la compatibilidad con el redireccionamiento de servidores como Azure SignalR Service, que aún no ha habilitado la característica.

El cliente conserva el proveedor de tokens de aplicación en toda la redirección, adopta un token de transporte actualizado de la respuesta y conserva tokenLifetimeSeconds para que la actualización automática permanezca programada después de que expire el token original.

¡Gracias @MoChilia por esta contribución!

SignalR El cliente de TypeScript admite la actualización de autenticación

El SignalR cliente de TypeScript admite la actualización de un token de acceso sin volver a conectarse. Puede programar una actualización en función del tiempo de vida del token que indica el servidor o actualizarlo inmediatamente después de que la aplicación obtenga claims actualizados.

Configure la actualización automática mediante withAuthenticationRefresh. Registre controladores de éxito y de error en la conexión creada, y llame a refreshAuthentication para solicitar una actualización manual:

const connection = new signalR.HubConnectionBuilder()
  .withUrl("/clock", { accessTokenFactory: getAccessToken })
  .withAuthenticationRefresh({
    enableAutoRefresh: true,
    refreshBeforeExpirationInMilliseconds: 120_000,
  })
  .build();

connection.onAuthenticationRefreshed((context) => {
  console.log(`New token lifetime: ${context.newTokenLifetimeInSeconds}`);
});

connection.onAuthenticationRefreshFailed((context) => {
  console.error(context.error);
});

await connection.start();

// Refresh immediately after acquiring a token with updated claims.
await connection.refreshAuthentication();

API mínimas

En esta sección se describen las nuevas características de las API mínimas.

Los filtros de punto de conexión observan errores de enlace de parámetros

Cuando un endpoint de API mínima tiene configurados filtros o factorías de filtros, la canalización de filtros ahora se ejecuta incluso si falla la vinculación de parámetros. Los filtros pueden leer HttpContext.Response.StatusCode == 400 y sustituir su propio cuerpo de respuesta.

En el entorno Development, establezca RouteHandlerOptions.ThrowOnBadRequest = false para que el framework devuelva un 400 que el filtro pueda detectar en lugar de enviar BadHttpRequestException a la página de excepciones para desarrolladores. Esto ya es el valor predeterminado en entornos no-Development.

¡Gracias @marcominerva por esta contribución!

Tipos de unión de C#

ASP.NET Core admite tipos de unión de C# (referencia del lenguaje C#), que son nuevos en .NET 11, en cualquier lugar donde se use System.Text.Json: cuerpos JSON de solicitud y respuesta en las API mínimas y MVC, SignalR de JsonHubProtocol, interoperabilidad con JavaScript de Blazor, estado persistente del componente y parámetros de componentes prerrepresentados.

public union UnionIntString(int, string);

app.MapGet("/value", () => new UnionIntString(42));

Los tipos de unión no son compatibles con orígenes de enlace que no son de cuerpo, como valores de ruta, cadenas de consulta, encabezados y campos de formulario.

Para OpenAPI, se describe un punto de conexión que devuelve una unión con un anyOf esquema que enumera cada tipo de caso. A diferencia de los tipos polimórficos, los casos de unión no tienen un discriminador $type, por lo que cada caso reutiliza su componente independiente (por ejemplo, #/components/schemas/Dog) en lugar de una versión duplicada con prefijo. ApiExplorer detecta un tipo unión mediante JsonTypeInfoKind.Union, por lo que el esquema también se transmite a Swashbuckle y NSwag. Cuando varios casos se serialicen con la misma estructura JSON, desambíguelos con un clasificador [JsonUnion]. SignalR uniones requieren el protocolo de concentrador JSON; MessagePack y Newtonsoft.Json los protocolos no admiten uniones.

Para obtener ejemplos e información adicional que se aplican a las Blazor aplicaciones, consulte la sección Parámetros de componentes del artículo Información general de componentes y la sección Paso de parámetros del artículo Componentes representados dinámicamente ASP.NET Core Razor componentes.

Validación asincrónica para las API mínimas

La validación mínima de API ahora admite validadores asincrónicos de un extremo a otro (dotnet/aspnetcore #66487, dotnet/aspnetcore #67183). La versión preliminar 5 envió los bloques de creación para la validación asincrónica de formularios en Blazor. La versión preliminar 6 agrega nuevas API asincrónicas DataAnnotations en las bibliotecas base (AsyncValidationAttribute y IAsyncValidatableObject) y Microsoft.Extensions.Validation ahora las ejecuta cuando un punto de conexión valida una solicitud.

La manera más sencilla de agregar una regla asincrónica es un atributo de validación personalizado. Herede de AsyncValidationAttribute e implemente IsValidAsync para consultar una base de datos o llamar a una API remota sin bloquear un hilo. El IsValid sincrónico también es abstracto; lanza una excepción desde él solo si el atributo se valida de forma asíncrona:

using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.DependencyInjection;

public sealed class UniqueEmailAttribute : AsyncValidationAttribute
{
    // Synchronous IsValid. This attribute validates asynchronously only.
    protected override ValidationResult? IsValid(object? value, ValidationContext context) =>
        throw new InvalidOperationException("Validate this attribute with IsValidAsync.");

    protected override async Task<ValidationResult?> IsValidAsync(
        object? value, ValidationContext context, CancellationToken cancellationToken)
    {
        var users = context.GetRequiredService<IUserService>();
        
        if (value is string email && await users.EmailExistsAsync(email, cancellationToken))
        {
            return new ValidationResult("That email is already registered.");
        }

        return ValidationResult.Success;
    }
}

Se aplica [UniqueEmail] a una propiedad como cualquier atributo de validación integrado.

Para la validación que abarca varias propiedades o todo el objeto, implemente IAsyncValidatableObject y devuelva resultados como .IAsyncEnumerable<ValidationResult> Dado que IAsyncValidatableObject extiende IValidatableObject, también implementa el método sincrónico Validate . Cuando un tipo solo se valida de forma asincrónica, inicie desde Validate para que su validación no se omita silenciosamente por las API sincrónicas:

using System.ComponentModel.DataAnnotations;
using System.Runtime.CompilerServices;

public class ReservationRequest : IAsyncValidatableObject
{
    [Required]
    public string Email { get; set; } = "";

    public DateOnly Date { get; set; }

    // Synchronous IValidatableObject. This type validates asynchronously only.
    public IEnumerable<ValidationResult> Validate(ValidationContext context) =>
        throw new InvalidOperationException("Validate this type with ValidateAsync.");

    public async IAsyncEnumerable<ValidationResult> ValidateAsync(
        ValidationContext context,
        [EnumeratorCancellation] CancellationToken cancellationToken = default)
    {
        var rooms = context.GetRequiredService<IRoomService>();

        if (!await rooms.HasAvailabilityAsync(Date, cancellationToken))
        {
            yield return new ValidationResult(
                "No rooms are available on that date.", [nameof(Date)]);
        }
    }
}

Registre la validación y el framework valida la solicitud antes de ejecutar el endpoint:

builder.Services.AddValidation();

app.MapPost("/reservations", (ReservationRequest request) =>
    Results.Ok(request));

Los validadores se ejecutan de forma concurrente siempre que es posible: los atributos asíncronos del mismo miembro se inician a la vez, los elementos de la colección se validan en paralelo y el framework conserva el orden existente entre la validación de miembro, de tipo y IValidatableObject.

Puntos de conexión de cortocircuito con un atributo

El nuevo atributo [ShortCircuit] marca un punto de conexión para que se ejecute inmediatamente después del enrutamiento, saltándose el resto de la canalización de middleware. Este es el formato de atributo de la convención de punto de conexión existente ShortCircuit() , por lo que se puede aplicar directamente a los controladores y acciones de MVC.

La evaluación de cortocircuito es útil para los puntos de conexión que no necesitan autenticación, CORS u otros componentes de middleware, por ejemplo, una comprobación de estado o una respuesta robots.txt, y evita el coste de ejecutar ese middleware. El punto de conexión sigue funcionando y genera su respuesta. Pase un código de estado opcional, como [ShortCircuit(404)], para establecer el código de estado de respuesta.

[ApiController]
[Route("robots.txt")]
[ShortCircuit]
public class RobotsController : ControllerBase
{
    [HttpGet]
    public IActionResult Get() => Content("User-agent: *\nDisallow:", "text/plain");
}

El mismo atributo funciona en puntos de conexión de API mínimos y la convención existente ShortCircuit() sigue funcionando sin cambios:

app.MapGet("/health", [ShortCircuit] () => "Healthy");

Gracias @Porozhniakov por contribuir a esta característica.

La localización de validación está integrada

Microsoft.Extensions.Validation localiza los mensajes de validación y los nombres visibles sin necesidad de un paquete independiente. Llamar a AddLocalization para registrar un IStringLocalizerFactory, seguido de AddValidation, activa la localización automáticamente. El generador de origen de validación emite la búsqueda de localización en el ensamblado.

builder.Services.AddLocalization();
builder.Services.AddValidation();
[ValidatableType]
public class CustomerModel
{
    [Display(Name = "CustomerName")]          // resource key for the display name
    [Required(ErrorMessage = "NameRequired")] // resource key for the message
    public string? Name { get; set; }
}

Un valor explícito ErrorMessage , como NameRequired en el ejemplo anterior, es la primera clave de recurso que intenta la localización. Cuando un atributo no especifica ErrorMessage, el proceso de localización recurre en su lugar a las convenciones integradas de nombres de recursos, de la más específica a la menos específica:

  1. {DeclaringType}_{MemberName}_{AttributeType}_Error
  2. {DeclaringType}_{AttributeType}_Error
  3. {AttributeType}_Error

Por ejemplo, un atributo [Required] en CustomerModel.Name se resuelve con respecto a CustomerModel_Name_RequiredAttribute_Error, CustomerModel_RequiredAttribute_Error o el recurso compartido RequiredAttribute_Error. Si no se resuelve ningún recurso, la validación vuelve al mensaje integrado del atributo. Use ValidationOptions.LocalizerProvider para resolver las claves de un archivo de recursos compartidos en su lugar:

builder.Services.AddValidation(options =>
{
    options.LocalizerProvider = (_, factory) => factory.Create(typeof(ValidationMessages));
});

Los atributos que ya se localizan (ErrorMessageResourceType, [Display(ResourceType = ...)]) omiten completamente la canalización. Un atributo personalizado que necesita sustituir sus propios valores en la plantilla de mensaje puede implementar IValidationMessageFormatter:

public sealed class DivisibleByAttribute : ValidationAttribute, IValidationMessageFormatter
{
    public int Divisor { get; init; }

    public string FormatMessage(CultureInfo culture, string template, string displayName)
        => string.Format(culture, template, displayName, Divisor); // {0} = name, {1} = divisor
}

Las mismas reglas de localización se aplican a la validación de las API mínimas y Blazor, por lo que un mensaje se localiza de forma idéntica siempre que se use el modelo.

Los atributos de validación ya no son experimentales

ValidatableTypeAttribute y SkipValidationAttribute ya no están marcados como experimentales. Si ha suprimido ASP0029 para usar cualquiera de los atributos, quite la supresión.

OpenAPI

En esta sección se describen las nuevas características de OpenAPI.

Descripción de las respuestas de archivos binarios

ASP.NET Core 11 presenta compatibilidad con la generación de descripciones de OpenAPI para operaciones que devuelven respuestas de archivos binarios. Este soporte asigna el FileContentResult tipo de resultado a un esquema de OpenAPI con type: string y format: binary.

Use el método de extensión Produces<T> con T de FileContentResult para especificar el tipo de respuesta y el tipo de contenido.

app.MapPost("/filecontentresult", () =>
{
    var content = "This endpoint returns a FileContentResult!"u8.ToArray();
    return TypedResults.File(content);
})
.Produces<FileContentResult>(contentType: MediaTypeNames.Application.Octet);

El documento de OpenAPI generado describe la respuesta del punto de conexión como:

responses:
  '200':
    description: OK
    content:
      application/octet-stream:
        schema:
          $ref: '#/components/schemas/FileContentResult'

FileContentResult se define en components/schemas como:

components:
  schemas:
    FileContentResult:
      type: string
      format: binary

Compatibilidad con OpenAPI 3.2.0 (cambio importante)

Microsoft.AspNetCore.OpenApi ahora admite OpenAPI 3.2.0 a través de una dependencia actualizada de Microsoft.OpenApi 3.3.1. Esta actualización incluye cambios importantes de la biblioteca subyacente. Para obtener más información, consulte el Microsoft. Guía de actualización de OpenApi.

Para generar un documento de OpenAPI 3.2.0, especifique la versión al llamar a AddOpenApi:

builder.Services.AddOpenApi(options =>
{
    options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_2;
});

Las actualizaciones posteriores aprovechan las nuevas funcionalidades de la especificación 3.2.0, como la compatibilidad del esquema de elementos con eventos de streaming.

¡Gracias @baywet por esta contribución!

CONSULTA HTTP en documentos openAPI generados

La generación de documentos openAPI ahora reconoce HTTP QUERY como un tipo de operación conocido. QUERY es un método seguro y idempotente propuesto que permite a los clientes enviar un cuerpo de solicitud al describir una búsqueda, útil cuando una consulta es demasiado grande o demasiado estructurada para caber en una dirección URL. El enrutamiento ya acepta cadenas arbitrarias de verbos mediante MapMethods, y OpenAPI 3.2 añade un campo query al objeto Path Item para que esto pueda describirse en el documento OpenAPI.

Tenga en cuenta que query solo es válido en un documento OpenAPI 3.2, por lo que establezca OpenApiVersion en OpenApiOptions. En versiones anteriores de OpenAPI, la operación query se genera dentro de una extensión de especificación x-oai-additionalOperations en el objeto Path Item.

using Microsoft.OpenApi;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddOpenApi(options =>
{
    options.OpenApiVersion = OpenApiSpecVersion.OpenApi3_2;
});

var app = builder.Build();

app.MapOpenApi();

app.MapMethods("/search", ["QUERY"], (SearchRequest request) =>
    SearchService.Run(request));

app.Run();

En un documento de OpenAPI 3.2, la operación QUERY se describe en línea como un elemento relacionado de get, posty otras operaciones estándar:

"paths": {
  "/search": {
    "query": {
      "requestBody": { ... },
      "responses": { "200": { ... } }
    }
  }
}

En los documentos OpenAPI 3.0 y 3.1, la misma operación se representa mediante la extensión x-oai-additionalOperations del elemento Path Item:

"paths": {
  "/search": {
    "x-oai-additionalOperations": {
      "QUERY": {
        "requestBody": { ... },
        "responses": { "200": { ... } }
      }
    }
  }
}

¡Gracias @kilifu por esta contribución!

Los tipos de resultados de flujo de archivos aparecen en documentos de OpenAPI

FileStreamResult, FileContentHttpResulty FileStreamHttpResult ahora se describen como esquemas de cadena binaria en documentos OpenAPI generados, por lo que los clientes ven formas de respuesta precisas para los puntos de conexión que transmiten archivos. Anote el punto de conexión con .Produces<FileContentHttpResult>(contentType: "application/pdf") (o el tipo equivalente FileStreamHttpResult/FileStreamResult ) para que OpenAPI vea el tipo de resultado y emita el esquema binario.

¡Gracias @marcominerva por esta contribución!

Los esquemas openAPI coinciden mejor con ASP.NET Core comportamiento

La generación de OpenAPI ahora controla varios casos de esquema con más precisión. Los parámetros de enumeración que no son de cuerpo mantienen los nombres de miembros de enumeración de C# originales incluso cuando las opciones de JSON de HTTP configuran una directiva de nomenclatura JsonStringEnumConverter, ya que la consulta, la ruta, el encabezado y el enlace de formulario usan Enum.TryParse en lugar de serialización JSON. Los identificadores de referencia de esquema de matriz ahora usan nombres de componente válidos, como stringArray y TodoArray en lugar de nombres con sintaxis de matriz.

builder.Services.ConfigureHttpJsonOptions(options =>
{
    options.SerializerOptions.Converters.Add(
        new JsonStringEnumConverter(JsonNamingPolicy.KebabCaseLower));
});

app.MapGet("/orders", (OrderStatus status) => Results.Ok(status));

Con esta configuración, un esquema de cuerpo todavía puede describir OrderStatus.PendingReview como pending-review, mientras que el esquema de parámetros de consulta describe el valor aceptado como PendingReview.

Los puntos de conexión de API mínimos pueden admitir varias llamadas al método de extensión Produces para el mismo código de estado; por ejemplo, para especificar que una respuesta de 200 puede llegar como application/json o text/plain con esquemas diferentes. La misma compatibilidad se aplica a los controladores MVC a través de varios [ProducesResponseType] atributos.

En versiones anteriores, el marco contrayó cada código de estado en un solo tipo de respuesta y quitó el resto de forma silenciosa, lo que hace imposible describir los puntos de conexión que sirven a varios tipos de contenido. Microsoft.AspNetCore.Mvc.ApiExplorer ahora conserva todos los tipos de respuesta declarados con ordenación determinista y el documento OpenAPI generado emite entradas de contenido independientes por tipo de medio, o un esquema anyOf cuando varios tipos comparten el mismo tipo de contenido.

Gracias @marcominerva por la contribución de referencia del esquema de matriz.

OpenAPI 3.2 de forma predeterminada

Los documentos de OpenAPI generados ahora tienen como destino OpenAPI 3.2 de forma predeterminada. Los documentos se siguen generando como antes. Establezca la versión del documento explícitamente si necesita tener como destino una versión anterior para herramientas que aún no admite OpenAPI 3.2.

Para tener como destino una versión anterior, especifíquela al llamar a AddOpenApi:

builder.Services.AddOpenApi(options =>
{
    options.OpenApiVersion = Microsoft.OpenApi.OpenApiSpecVersion.OpenApi3_1;
});

Compatibilidad con eventos de Server-Sent en OpenAPI 3.2

Los puntos de conexión que devuelven SseItem<T> se describen en el documento OpenAPI generado con el esquema de OpenAPI 3.2 itemSchema para las respuestas text/event-stream. El itemSchema describe la forma de la carga útil de cada evento de un flujo en lugar de recurrir a un esquema string simple.

app.MapGet("/todos/stream", (CancellationToken ct) =>
    TypedResults.ServerSentEvents(GetTodosAsync(ct)))
   .WithName("StreamTodos");

static async IAsyncEnumerable<SseItem<Todo>> GetTodosAsync(
    [EnumeratorCancellation] CancellationToken ct = default)
{
    foreach (var todo in Todos.All)
    {
        yield return new SseItem<Todo>(todo) { EventId = todo.Id.ToString() };
        await Task.Delay(1000, ct);
    }
}

Devuelva la transmisión a través de TypedResults.ServerSentEvents. Un controlador que devuelve IAsyncEnumerable<SseItem<T>> directamente se serializa como JSON en lugar de SSE. Utilice la sobrecarga específica SseItem<T> sin eventType. Para usar un único nombre de evento para todo el flujo, pase simplemente IAsyncEnumerable<T> con eventType.

El documento 3.2 generado describe la carga útil del evento con itemSchema que hace referencia a #/components/schemas/Todo, además de los campos de cadena SSE estándar event y id:

responses:
  '200':
    description: OK
    content:
      text/event-stream:
        itemSchema:
          type: object
          required: [data]
          properties:
            data:
              $ref: '#/components/schemas/Todo'
            event: { type: string }
            id: { type: string }

Si la carga del evento es una unión discriminada (una característica en versión preliminar de C# 14), OpenAPI también emite los nombres de los casos de la unión como un enum en el campo event.

Selección de un entorno para la generación de documentos openAPI en tiempo de compilación

La generación de documentos OpenAPI en tiempo de compilación permite seleccionar el entorno de la aplicación mediante la propiedad MSBuild OpenApiGenerationEnvironment. La propiedad establece el entorno del host para el proceso de generación, lo que equivale a establecer la variable de entorno ASPNETCORE_ENVIRONMENT o DOTNET_ENVIRONMENT. Por lo tanto, las transformaciones de documento y configuración específicas del entorno pueden afectar al documento openAPI generado sin necesidad de establecer la variable de entorno antes de ejecutar dotnet build.

Establezca la propiedad en el archivo del proyecto:

<PropertyGroup>
  <OpenApiGenerationEnvironment>Development</OpenApiGenerationEnvironment>
</PropertyGroup>

Para obtener más información, consulte Generación de documentos openAPI.

¡Gracias @ldsenow por esta contribución!

OpenAPI refleja las API obsoletas

La generación de OpenAPI en ASP.NET Core asigna automáticamente [Obsolete] a deprecated: true para las operaciones, los tipos de esquema y las propiedades de esquema. Por lo tanto, los clientes de API y las herramientas de documentación pueden mostrar la misma información sobre obsolescencia que el código que llama a .NET, sin necesidad de un transformador OpenAPI personalizado.

app.MapGet("/catalog/{id}", GetCatalogItem);

#pragma warning disable CS0618 // This example intentionally declares and maps obsolete APIs.
app.MapGet("/catalog/legacy/{id}", GetLegacyCatalogItem);

[Obsolete("Use /catalog/{id}.")]
static LegacyCatalogItem GetLegacyCatalogItem(int id) =>
    new(id, $"Product {id}", $"SKU-{id:D4}");

static CatalogItem GetCatalogItem(int id) =>
    new(id, $"Product {id}", $"SKU-{id:D4}");

public sealed record CatalogItem(
    int Id,
    string Name,
    string StockKeepingUnit);

[Obsolete("Use CatalogItem.")]
public sealed record LegacyCatalogItem(
    int Id,
    string Name,
    [property: Obsolete("Use StockKeepingUnit.")] string Sku);

#pragma warning restore CS0618

El documento generado marca la operación obsoleta, su esquema de respuesta y la propiedad Sku como obsoletos:

{
  "paths": {
    "/catalog/legacy/{id}": {
      "get": {
        "deprecated": true
      }
    }
  },
  "components": {
    "schemas": {
      "LegacyCatalogItem": {
        "deprecated": true,
        "properties": {
          "sku": {
            "deprecated": true
          }
        }
      }
    }
  }
}

Un IOpenApiOperationTransformer o IOpenApiSchemaTransformer puede sobrescribir el valor generado de una API específica.

¡Gracias @fickleEfrit por esta contribución!

Autenticación y autorización

En esta sección se describen las nuevas características de autenticación y autorización.

TimeProvider compatibilidad en ASP.NET Core Identity

ASP.NET Core Identity ahora usa TimeProvider en lugar de DateTime y DateTimeOffset para todas las operaciones relacionadas con el tiempo. Este cambio hace Identity que los componentes se puedan probar más y proporcionan un mejor control a lo largo del tiempo en pruebas y escenarios especializados.

En el ejemplo siguiente se muestra cómo usar un objeto simulado TimeProvider para probar Identity funcionalidades:

// In tests
var fakeTimeProvider = new FakeTimeProvider(
    new DateTimeOffset(2024, 1, 1, 0, 0, 0, TimeSpan.Zero));

services.AddSingleton<TimeProvider>(fakeTimeProvider);
services.AddIdentity<IdentityUser, IdentityRole>();

// Identity will now use the fake time provider

Usando TimeProvider, puedes escribir más fácilmente pruebas deterministas para características sensibles Identity al tiempo como la expiración de tokens, duraciones de bloqueo y validación de sellos de seguridad.

Inferir nombre de visualización de clave de acceso a partir del autenticador

ASP.NET Core Identity ahora infiere automáticamente nombres de visualización amigables para las claves de acceso basándose en su AAGUID (Authenticator Attestation GUID). Los mapas integrados están incluidas para los autenticadores de contraseña más usados, incluidos Google Password Manager, iCloud Keychain, Windows Hello, 1Password y Bitwarden.

En el caso de los autenticadores conocidos, el nombre se asigna automáticamente sin preguntar al usuario. Para autenticadores desconocidos, el usuario se redirige a una página de cambio de nombre. Extiende los mapeos añadiendo entradas al PasskeyAuthenticators diccionario en el proyecto.

dotnet user-jwts admite aplicaciones basadas en archivos

La herramienta dotnet user-jwts crea JWT de desarrollo firmados para poder llamar a los puntos de conexión autenticados de una aplicación sin configurar un proveedor de identidad real. El create comando genera un token, almacena su clave de firma en los secretos de usuario de la aplicación e imprime el token para usarlo como token de portador. Ahora funciona con aplicaciones basadas en archivos (un único app.cs sin archivo de proyecto) a través de la nueva --file opción:

dotnet user-jwts create --file app.cs

Metadatos de autorización coherentes en toda la pila

Los metadatos de autorización se pueden expresar como IAuthorizeData, un atributo AuthorizationPolicy o IAuthorizationRequirementData. Los filtros de MVC, los métodos de hub de SignalR y de BlazorAuthorizeView y AuthorizeRouteView aplican las tres formas de manera coherente.

Una nueva sobrecarga AuthorizationPolicy.CombineAsync es la implementación compartida:

public class AuthorizationPolicy
{
    public static Task<AuthorizationPolicy?> CombineAsync(
        IAuthorizationPolicyProvider policyProvider,
        IEnumerable<object> metadata);
}

MVC, SignalRy Blazor usan esta sobrecarga internamente. Un atributo personalizado que implementa tanto IAuthorizeData como IAuthorizationRequirementData solo contribuye una vez a la decisión. La ruta heredada de MVC con EnableEndpointRouting = false permanece sin cambios.

La autenticación Negotiate utiliza la vinculación de canal TLS

La autenticación Negotiate en Kestrel usa el token de enlace de canal del extremo TLS para las conexiones HTTPS. El manejador de autenticación proporciona el token al proceso de intercambio Kerberos o NTLM subyacente y lo conserva durante la autenticación de varias rondas.

No se requieren cambios de configuración. Las conexiones no HTTPS y las conexiones HTTPS en las que un token de enlace de canal no está disponible siguen usando el comportamiento existente.

Compatibilidad con credenciales de sesión enlazadas a dispositivos experimentales

Importante

El Microsoft.AspNetCore.Authentication.DeviceBoundSessions paquete es experimental y permanece en versión preliminar a lo largo de .NET 11 y hasta que la especificación se estabiliza.

La especificación Credenciales de sesión enlazadas al dispositivo (DBSC) define un protocolo que enlaza la actualización de sesión a una clave privada que mantiene el explorador. La aplicación emite una sesión de corta duración cookiey el explorador debe proporcionar una prueba de posesión firmada para actualizarla. Una sesión cookie copiada puede permanecer utilizable hasta que expire, pero un atacante sin la clave del dispositivo no puede usarlo para ampliar la sesión.

ASP.NET Core agrega una implementación de DBSC experimental del lado servidor en el Microsoft.AspNetCore.Authentication.DeviceBoundSessions paquete. El componente de autenticación se superpone a un cookie esquema de autenticación existente y administra los puntos de conexión de registro y actualización, una actualización con ámbito de ruta cookie y la sesión de corta duración cookie.

Después de agregar el Microsoft.AspNetCore.Authentication.DeviceBoundSessions paquete, configure DBSC a través de un esquema de autenticación existente cookie :

builder.Services
    .AddAuthentication("Application")
    .AddCookie("Application")
    .AddDeviceBoundSession("Application", options =>
    {
        options.ShortLivedCookieExpiration = TimeSpan.FromMinutes(10);
    });

Actualmente, la compatibilidad con el explorador requiere una implementación de DBSC experimental. Para obtener más información, consulte la documentación de DBSC de Chrome.

Varios

En esta sección se describen varias características nuevas de .NET 11.

interfaz IOutputCachePolicyProvider

ASP.NET Core en .NET 11 proporciona la interfaz IOutputCachePolicyProvider para implementar lógica personalizada de selección de directivas de caché de salida. Con esta interfaz, las aplicaciones pueden determinar la directiva de almacenamiento en caché base predeterminada, comprobar la existencia de directivas con nombre y admitir escenarios avanzados en los que las directivas se deben resolver dinámicamente. Ejemplos incluyen cargar políticas desde fuentes de configuración externas, bases de datos o aplicar reglas de caché específicas de inquilino.

El código siguiente muestra la IOutputCachePolicyProvider interfaz :

public interface IOutputCachePolicyProvider
{
    IReadOnlyList<IOutputCachePolicy> GetBasePolicies();
    ValueTask<IOutputCachePolicy?> GetPolicyAsync(string policyName);
}

¡Gracias @lqlive por esta contribución!

Certificados de desarrollo de auto-confianza en WSL

La configuración del certificado de desarrollo ahora confía automáticamente en certificados en entornos WSL (Subsistema de Windows para Linux). Al ejecutar dotnet dev-certs https --trust en WSL, el certificado se instala automáticamente y se confía tanto en el entorno WSL como en Windows, lo que elimina la configuración de confianza manual.

# Automatically trusts certificates in both WSL and Windows
dotnet dev-certs https --trust

Esta mejora simplifica la experiencia de desarrollo al usar WSL, lo que elimina un punto de fricción común para los desarrolladores que trabajan en entornos de Linux en Windows.

¡Gracias @StickFun por esta contribución!

Seguimiento nativo de OpenTelemetry para ASP.NET Core

ASP.NET Core ahora agrega de forma nativa atributos de convención semántica de OpenTelemetry a la actividad del servidor HTTP, que se alinea con la especificación de intervalo de servidor HTTP de OpenTelemetry. Todos los atributos necesarios se incluyen de forma predeterminada y coinciden con los metadatos que anteriormente solo estaban disponibles a través de la OpenTelemetry.Instrumentation.AspNetCore biblioteca.

Para recopilar los datos de trazado integrados, suscríbete a la Microsoft.AspNetCore fuente de actividad en tu configuración de OpenTelemetry:

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing => tracing
        .AddSource("Microsoft.AspNetCore")
        .AddConsoleExporter());

No se necesita ninguna biblioteca de instrumentación adicional (por ejemplo OpenTelemetry.Instrumentation.AspNetCore, ). El marco ahora rellena directamente los atributos de convención semántica en la actividad de solicitud, como http.request.method, url.path, http.response.status_codey server.address.

Si no desea agregar atributos OpenTelemetry a la actividad, puede desactivarlo estableciendo el modificador Microsoft.AspNetCore.Hosting.SuppressActivityOpenTelemetryData AppContext en true.

Mejoras de rendimiento

KestrelEl analizador de peticiones HTTP/1.1 de , ahora utiliza una ruta de código sin lanzamiento para manejar solicitudes mal formadas. En lugar de añadir BadHttpRequestException a cada fallo de análisis analizador, el analizador devuelve una estructura de resultados que indica estados de éxito, incompleto o de error. En escenarios con muchas solicitudes con formato incorrecto, como el examen de puertos, el tráfico malintencionado o los clientes mal configurados, esto elimina la sobrecarga costosa de control de excepciones y mejora el rendimiento hasta 20-40%. No hay ningún impacto en el procesamiento de solicitudes válido.

El middleware de registro HTTP ahora agrupa sus ResponseBufferingStream instancias, reduciendo las asignaciones por solicitud cuando se habilita el registro de cuerpos de respuesta o interceptores.

Compresión de respuesta Zstandard y descompresión de solicitudes

ASP.NET Core ahora admite Zstandard (zstd) para la compresión de respuesta y la descompresión de solicitudes. Esto agrega compatibilidad con zstd al middleware existente de compresión de respuesta y descompresión de solicitud y habilita zstd de forma predeterminada.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddResponseCompression();
builder.Services.AddRequestDecompression();
builder.Services.Configure<ZstandardCompressionProviderOptions>(options =>
{
    options.CompressionOptions = new ZstandardCompressionOptions
    {
        Quality = 6 // 1-22, higher = better compression, slower
    };
});

¡Gracias @manandre por esta contribución!

HTTP/3 inicia el procesamiento de solicitudes anteriormente

Kestrel ahora inicia el procesamiento de solicitudes HTTP/3 sin esperar primero el flujo de control y el marco SETTINGS, lo que reduce la latencia de primera solicitud en nuevas conexiones.

La plantilla del servidor MCP se incluye con el SDK de .NET

El Protocolo de contexto de modelo (MCP) es un estándar abierto que las aplicaciones y agentes de IA, como las de Visual Studio, Visual Studio Code y GitHub Copilot, usan para detectar y llamar a herramientas, datos y servicios externos a través de una interfaz coherente. Un servidor MCP expone su propia funcionalidad, como herramientas personalizadas o acceso a un origen de datos, por lo que un host de IA puede invocarlo en nombre del usuario.

Use la mcpserver plantilla cuando quiera crear un servidor MCP de C# que integre el código o los servicios con herramientas con tecnología de IA. El proyecto generado usa el SDK oficial de C# para MCP e incluye una herramienta de ejemplo de trabajo, por lo que tiene un punto de partida ejecutable para ampliar con sus propias herramientas.

La plantilla de proyecto mcpserver, disponible anteriormente solo mediante la instalación de Microsoft.McpServer.ProjectTemplates, ahora se incluye como una plantilla agrupada en el SDK de .NET:

dotnet new mcpserver -o MyMcpServer

Mover la plantilla a ASP.NET Core hace que sea reconocible desde dotnet new list sin un paso de instalación independiente y alinea su mantenimiento con el resto de la pila web.

Para obtener más información, vea Crear un servidor de Protocolo de contexto de modelo (MCP) en C#.

Observabilidad del protocolo de enlace TLS en Kestrel

Dos cambios relacionados facilitan el diagnóstico y personalización de conexiones TLS en Kestrel.

ITlsHandshakeFeature expone ahora una propiedad Exception que contiene la excepción lanzada durante un protocolo de enlace TLS fallido, para que el middleware y el registro puedan registrar por qué falló una conexión en lugar de ver únicamente una simple IOException en la parte superior de la pila. La característica sigue funcionando aunque falle el protocolo de enlace: Kestrel captura una instantánea de los campos pertinentes de la SslStream subyacente antes de que se elimine.

La opción TlsClientHelloBytesCallback de HttpsConnectionAdapterOptions se rediseñó como middleware de conexión. La forma de devolución de llamada anterior ha quedado obsoleta; configure la inspección ClientHello a través de la nueva extensión ListenOptions.UseTlsClientHelloListener en su lugar. En el ejemplo siguiente se usan ambas características conjuntamente: el middleware de conexión lee ITlsHandshakeFeature.Exception después del protocolo de enlace, y UseTlsClientHelloListener inspecciona ClientHello antes de TLS:

var builder = WebApplication.CreateBuilder(args);

builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenAnyIP(5001, listenOptions =>
    {
        listenOptions.Use(next => async context =>
        {
            await next(context);

            var tlsHandshakeFeature = context.Features.Get<ITlsHandshakeFeature>();
            if (tlsHandshakeFeature?.Exception is { } ex)
            {
                Console.WriteLine($"[TLS Handshake Failed] ConnectionId={context.ConnectionId}, Exception={ex.GetType().Name}: {ex.Message}");
            }
        });

        // UseTlsClientHelloListener must be called before UseHttps()
        listenOptions.UseTlsClientHelloListener((connection, clientHelloBytes) =>
        {
            Console.WriteLine($"TLS Client Hello received on {connection.ConnectionId}, {clientHelloBytes.Length} bytes");
        });
        listenOptions.UseHttps();
    });
});

La compresión de respuesta siempre emite Vary: Accept-Encoding

El middleware de compresión de respuesta ahora agrega Vary: Accept-Encoding a cada respuesta cuando la compresión está habilitada, incluso cuando la respuesta en sí no está comprimida. Esto impide que las memorias caché compartidas y las REDES CDN sirvan una carga comprimida a un cliente que no solicitó una (o viceversa).

¡Gracias @pedrobsaila por esta contribución!

Sincronización en tiempo de ejecución habilitada para bibliotecas de marcos compartidos

Las bibliotecas de ASP.NET Core exclusivas del marco de trabajo compartido se compilan ahora con la característica runtime-async en net11.0+. Runtime-async permite que el entorno de ejecución, en lugar del compilador de C#, genere la máquina de estado para async/await, lo que puede reducir las asignaciones por espera y mejorar los diagnósticos. Se trata de un cambio interno de codegen sin ningún impacto en la API pública: las aplicaciones destinadas a net11.0 se benefician automáticamente cuando llaman a las bibliotecas de ASP.NET Core afectadas.

Se excluyen las bibliotecas que se distribuyen tanto como componentes del framework compartido como en paquetes NuGet independientes, ya que runtime-async no es compatible con WebAssembly y, de lo contrario, provocaría fallos en los consumidores de Wasm que usan esos paquetes.

Dado que runtime-async cambia cómo se genera async/await en una gran parte de la pila de ASP.NET Core, pruebe sus aplicaciones con esta versión preliminar y notifique un problema si observa un comportamiento inesperado, especialmente en las pilas de excepciones, el flujo de ExecutionContext/AsyncLocal o cualquier cosa que parezca una regresión con respecto a .NET 10.

El middleware de limitación de velocidad devuelve encabezados Retry-After precisos

Ahora FixedWindowRateLimiter informa de un valor de metadatos RetryAfter que refleja con precisión el siguiente límite de ventana. Las aplicaciones que propagan estos metadatos al encabezado de respuesta Retry-After en su función de devolución de llamada OnRejected ahora generan automáticamente intervalos de reintento correctos, sin necesidad de realizar cambios en el código.

Las correcciones adicionales en System.Threading.RateLimiting resuelven un problema por el que TokenBucketRateLimiter gestionaba incorrectamente las recargas parciales de tokens durante la adquisición de cero permisos, y mejoran el limitador de velocidad encadenado devuelto por CreateChained para propagar correctamente la duración de inactividad y el comportamiento de reposición de sus limitadores internos.

Para obtener una introducción general al middleware de limitación de velocidad, consulte Middleware de limitación de velocidad en ASP.NET Core.

¡Gracias @asbjornvad y @apoorvdarshan por estas contribuciones!

Kestrel aplica tiempos de espera a los encabezados del finalizador

Kestrel aplica ahora RequestHeadersTimeout a los encabezados del finalizador fragmentados HTTP/2 y HTTP/3 que no terminan de enviar el bloque de encabezados. El mismo tiempo de espera que protege los encabezados de solicitud inicial ahora también impide que las conexiones permanezcan abiertas indefinidamente mientras Kestrel espera a que se completen los marcos HEADERS del finalizador.

builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.RequestHeadersTimeout = TimeSpan.FromSeconds(10);
});

Acceso al token de enlace de canal TLS desde ITlsConnectionFeature

Las aplicaciones que utilizan TLS pueden leer el token de vinculación de canal de la conexión para defenderse contra ataques de relevo:

using System.Security.Authentication.ExtendedProtection;

app.Use(async (context, next) =>
{
    var tls = context.Features.Get<ITlsConnectionFeature>();
    if (tls is not null && tls.TryGetChannelBindingBytes(
            ChannelBindingKind.Endpoint,
            out ReadOnlyMemory<byte> cbt))
    {
        // Compare cbt against the token the client presented during authentication.
    }

    await next(context);
});

Kestrel devuelve el enlace de SslStream.TransportContext.GetChannelBinding. IIS y HTTP.sys lo devuelven a partir de la solicitud. En HTTP.sys, HttpSysOptions.HttpAuthenticationHardeningLevel controla la protección ampliada y la exposición de tokens de enlace de canal:

  • Legacy deshabilita la validación del enlace de canal y no expone el token.
  • Medium, el valor predeterminado, expone el token y lo valida cuando se proporciona, pero tolera su ausencia.
  • Strict requiere el token para las solicitudes autenticadas y rechaza las solicitudes sin una. También falla al iniciarse si el sistema operativo no puede aplicar la configuración, mientras que Legacy y Medium registran el error de configuración y continúan.

Cambios críticos

Utiliza los artículos en Cambios importantes en .NET para encontrar cambios importantes que podrían aplicarse al actualizar una aplicación a una versión más reciente de .NET.