Compartir a través de


Novedades de ASP.NET Core en .NET 10

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

Para ver los cambios importantes, consulte Cambios importantes en .NET.

Blazor

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

Ejemplos de seguridad nuevos y actualizados Blazor Web App

Hemos agregado y actualizado los Blazor Web App ejemplos de seguridad vinculados en los siguientes artículos:

Todas nuestras soluciones de ejemplo OIDC y Entra ahora incluyen un proyecto de API web independiente (MinimalApiJwt) para demostrar cómo configurar y llamar a una API web externa de forma segura. La llamada a las API web se demuestra con un controlador de tokens y un cliente HTTP con nombre para un proveedor de identidad OIDC o paquetes web/API Identity de Microsoft para Microsoft Entra ID.

Las soluciones de ejemplo se configuran en el código de C# en sus archivos Program. Para configurar las soluciones desde archivos de configuración de la aplicación (por ejemplo, appsettings.json), consulte la nueva sección Configuración de suministro con el proveedor de configuración JSON (configuración de la aplicación) de los artículos OIDC o Entra.

Nuestro artículo de Entra y las aplicaciones de ejemplo también incluyen nuevas instrucciones sobre los siguientes enfoques:

parámetro QuickGridRowClass

Aplique una clase de hoja de estilos a una fila de la cuadrícula basada en el elemento de fila mediante el nuevo parámetro RowClass. En el ejemplo siguiente, se llama al método GetRowCssClass en cada fila para aplicar condicionalmente una clase de hoja de estilos basada en el elemento de fila:

<QuickGrid ... RowClass="GetRowCssClass">
    ...
</QuickGrid>

@code {
    private string GetRowCssClass(MyGridItem item) =>
        item.IsArchived ? "row-archived" : null;
}

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

Blazor script como recurso web estático

En versiones anteriores de .NET, el script de Blazor se sirve desde un recurso incrustado en el marco compartido de ASP.NET Core. En .NET 10 o posterior, el script de Blazor se sirve como un recurso web estático con compresión automática y huella digital.

El Blazor marco incluye el script (blazor.web.js o blazor.server.js) si el proyecto contiene al menos un Razor archivo componente (.razor). Si la aplicación requiere el Blazor script, pero no contiene al menos un componente, agregue la siguiente propiedad de MSBuild al archivo de proyecto de la aplicación para forzar la inclusión de scripts incondicionales:

<RequiresAspNetWebAssets>true</RequiresAspNetWebAssets>

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

Resaltados de la plantilla de ruta

El atributo [Route] ahora admite el resaltado de sintaxis de ruta para ayudar a visualizar la estructura de la plantilla de ruta:

El patrón de plantilla de ruta de un atributo de ruta para el valor de contador muestra el resaltado de sintaxis

Anteriormente, NavigationManager.NavigateTo se desplazaba a la parte superior de la página para las navegaciones de la misma página. Este comportamiento se ha cambiado en .NET 10 para que el explorador ya no se desplácese hasta la parte superior de la página al navegar a la misma página. Esto significa que el viewport ya no se restablece al hacer actualizaciones en la dirección de la página actual, como cambiar la cadena de búsqueda o el fragmento.

Componente de interfaz de usuario de reconexión agregado a la plantilla de proyecto de Blazor Web App

La plantilla de proyecto de Blazor Web App ahora incluye un componente de ReconnectModal, incluida la hoja de estilos intercalada y los archivos JavaScript, para mejorar el control del desarrollador sobre la interfaz de usuario de reconexión cuando el cliente pierde la conexión de WebSocket al servidor. El componente no inserta estilos mediante programación, lo que garantiza el cumplimiento de la configuración más estricta de la política de seguridad de contenido (CSP) para la política style-src. En versiones anteriores, el marco creó la interfaz de usuario de reconexión predeterminada de una manera que podría provocar infracciones de CSP. Tenga en cuenta que la interfaz de usuario de reconexión predeterminada todavía se usa como reserva cuando la aplicación no define la interfaz de usuario de reconexión, como mediante el componente ReconnectModal de la plantilla de proyecto o un componente personalizado similar.

Nuevas características de la interfaz de usuario de reconexión:

  • Aparte de indicar el estado de reconexión estableciendo una clase CSS específica en el elemento de interfaz de usuario de reconexión, el nuevo evento components-reconnect-state-changed se envía para los cambios de estado de reconexión.
  • El código puede diferenciar mejor las fases del proceso de reconexión con el nuevo estado de reconexión "retrying", indicado por la clase CSS y el nuevo evento.

Para obtener más información, vea las Instrucciones de ASP.NET Core BlazorSignalR.

Omitir la cadena de consulta y el fragmento al usar NavLinkMatch.All

El componente NavLink ahora omite la cadena de consulta y el fragmento al usar el valor de NavLinkMatch.All para el parámetro Match. Esto significa que el vínculo conserva la clase active si la ruta de acceso de la URL coincide, pero la cadena de consulta o el fragmento cambian. Para revertir al comportamiento original, use el Microsoft.AspNetCore.Components.Routing.NavLink.EnableMatchAllForQueryStringAndFragmentAppContext interruptor establecido en true.

También puede invalidar el método ShouldMatch en NavLink para personalizar el comportamiento coincidente:

public class CustomNavLink : NavLink
{
    protected override bool ShouldMatch(string currentUriAbsolute)
    {
        // Custom matching logic
    }
}

Para obtener más información, consulte navegación de ASP.NET CoreBlazor.

Cerrar las opciones de columna de QuickGrid

Ahora puede cerrar la interfaz de opciones de columna de QuickGrid utilizando el nuevo método HideColumnOptionsAsync.

En el ejemplo siguiente se usa el método HideColumnOptionsAsync para cerrar la interfaz de usuario de opciones de columna en cuanto se aplica el filtro de título:

<QuickGrid @ref="movieGrid" Items="movies">
    <PropertyColumn Property="@(m => m.Title)" Title="Title">
        <ColumnOptions>
            <input type="search" @bind="titleFilter" placeholder="Filter by title" 
                @bind:after="@(() => movieGrid.HideColumnOptionsAsync())" />
        </ColumnOptions>
    </PropertyColumn>
    <PropertyColumn Property="@(m => m.Genre)" Title="Genre" />
    <PropertyColumn Property="@(m => m.ReleaseYear)" Title="Release Year" />
</QuickGrid>

@code {
    private QuickGrid<Movie>? movieGrid;
    private string titleFilter = string.Empty;
    private IQueryable<Movie> movies = new List<Movie> { ... }.AsQueryable();
    private IQueryable<Movie> filteredMovies => 
        movies.Where(m => m.Title!.Contains(titleFilter));
}

Streaming de respuesta de HttpClient habilitado por defecto

En versiones anteriores Blazor, el streaming de respuesta para HttpClient las solicitudes era opcional. Ahora, el streaming de respuesta está habilitado de forma predeterminada.

Se trata de un cambio importante porque llamar a HttpContent.ReadAsStreamAsync para un HttpResponseMessage.Content (response.Content.ReadAsStreamAsync()) devuelve un BrowserHttpReadStream y ya no un MemoryStream. BrowserHttpReadStream no admite operaciones sincrónicas, como Stream.Read(Span<Byte>). Si su código utiliza operaciones sincrónicas, puede optar por no participar en la transmisión de respuesta o copiar el Stream en el MemoryStream usted mismo.

Para no participar en el streaming de respuesta globalmente, use cualquiera de los enfoques siguientes:

  • Agregue la <WasmEnableStreamingResponse> propiedad al archivo de proyecto con un valor de false:

    <WasmEnableStreamingResponse>false</WasmEnableStreamingResponse>
    
  • Establezca la variable de DOTNET_WASM_ENABLE_STREAMING_RESPONSE entorno en false o 0.

Para excluirse del streaming de respuesta para una solicitud individual, establezca SetBrowserResponseStreamingEnabled en false en HttpRequestMessage (requestMessage en el ejemplo siguiente):

requestMessage.SetBrowserResponseStreamingEnabled(false);

Para obtener más información, consulte HttpClient y HttpRequestMessage con las opciones de solicitud de la Fetch API (artículo Llamada a API web).

Huella digital del lado cliente

La versión de .NET 9 introdujo el fingerprinting del lado del servidor de los activos estáticos en Blazor Web Apps con la introducción de las convenciones de punto de conexión de enrutamiento para el mapeo de activos estáticos (MapStaticAssets), el ImportMap componente y la ComponentBase.Assets propiedad (@Assets["..."]) para resolver módulos JavaScript con huellas digitales. Para .NET 10, puede optar por la huella digital en el lado del cliente de los módulos de JavaScript para aplicaciones independientes Blazor WebAssembly.

En las aplicaciones independientes Blazor WebAssembly durante la compilación o publicación, el marco sobrescribe los marcadores de posición en index.html con valores calculados durante la compilación para generar una huella digital de los recursos estáticos. Se coloca una huella digital en el nombre del archivo del script blazor.webassembly.js.

El marcado siguiente debe estar presente en el wwwroot/index.html archivo para adoptar la característica de huella digital:

<head>
    ...
+   <script type="importmap"></script>
</head>

<body>
    ...
-   <script src="_framework/blazor.webassembly.js"></script>
+   <script src="_framework/blazor.webassembly#[.{fingerprint}].js"></script>
</body>

</html>

En el archivo de proyecto (.csproj), agregue la <OverrideHtmlAssetPlaceholders> propiedad establecida en true:

<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">

  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
+   <OverrideHtmlAssetPlaceholders>true</OverrideHtmlAssetPlaceholders>
  </PropertyGroup>
</Project>

Cualquier script de index.html con el marcador de huella digital es digitalizado por el marco. Por ejemplo, un archivo de script denominado scripts.js en la carpeta de la aplicación wwwroot/js se le aplica huella digital agregando #[.{fingerprint}] antes de la extensión de archivo (.js):

<script src="js/scripts#[.{fingerprint}].js"></script>

Para realizar huellas digitales de módulos adicionales JS en aplicaciones independientes Blazor WebAssembly , use la propiedad <StaticWebAssetFingerprintPattern> en el archivo de proyecto de la aplicación (.csproj).

En el ejemplo siguiente, se agrega una huella digital para todos los archivos proporcionados .mjs por el desarrollador en la aplicación:

<StaticWebAssetFingerprintPattern Include="JSModule" Pattern="*.mjs" 
  Expression="#[.{fingerprint}]!" />

Los archivos se colocan automáticamente en el mapa de importación:

  • Automáticamente para Blazor Web App CSR.
  • Al participar en la huella digital del módulo en aplicaciones independientes Blazor WebAssembly según las instrucciones anteriores.

Al resolver la importación para la interoperabilidad de JavaScript, el explorador usa el mapa de importación para resolver los archivos con huellas digitales.

Configura el entorno en las aplicaciones independientes Blazor WebAssembly

El Properties/launchSettings.json archivo ya no se usa para controlar el entorno en aplicaciones independientes Blazor WebAssembly .

A partir de .NET 10, establezca el entorno con la <WasmApplicationEnvironmentName> propiedad en el archivo de proyecto de la aplicación (.csproj).

En el ejemplo siguiente se establece el entorno de la aplicación en Staging:

<WasmApplicationEnvironmentName>Staging</WasmApplicationEnvironmentName>

Los entornos predeterminados son:

  • Development para compilación.
  • Production para publicar.

Para obtener más información, consulte entornos de ASP.NET CoreBlazor.

Archivo de configuración de arranque insertado

La configuración de inicio de Blazor, que antes de la versión de .NET 10 existía en un archivo denominado blazor.boot.json, se ha insertado en el script dotnet.js. Esto solo afecta a los desarrolladores que interactúan directamente con el blazor.boot.json archivo, como cuando los desarrolladores son:

  • Compruebe la integridad de los archivos de los activos publicados con el script de PowerShell de solución de problemas de integridad según las instrucciones que se indican en los errores de comprobación de integridad y almacenamiento en caché de ASP.NET Core Blazor WebAssembly.
  • Cambiar la extensión de nombre de archivo de los archivos DLL cuando no use el formato de archivo webcil predeterminado según las instrucciones del host e implemente ASP.NET Core Blazor WebAssembly.

Actualmente, no hay ninguna estrategia de reemplazo documentada para los enfoques anteriores. Si necesita cualquiera de las estrategias anteriores, abra un nuevo problema de documentación que describa el escenario mediante el vínculo Abrir un problema de documentación en la parte inferior de cualquiera de los artículos.

Modelo declarativo para conservar el estado de componentes y servicios

Ahora puede especificar de forma declarativa el estado para conservar desde componentes y servicios mediante el atributo [PersistentState]. Las propiedades con este atributo se conservan automáticamente mediante el servicio PersistentComponentState durante la representación previa. El estado se recupera cuando el componente se renderiza interactivamente o se instancia el servicio.

En versiones anteriores Blazor, el estado del componente persistente durante la representación previa mediante el servicio PersistentComponentState implicaba una cantidad significativa de código, como se muestra en el ejemplo siguiente:

@page "/movies"
@implements IDisposable
@inject IMovieService MovieService
@inject PersistentComponentState ApplicationState

@if (MoviesList == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <QuickGrid Items="MoviesList.AsQueryable()">
        ...
    </QuickGrid>
}

@code {
    public List<Movie>? MoviesList { get; set; }
    private PersistingComponentStateSubscription? persistingSubscription;

    protected override async Task OnInitializedAsync()
    {
        if (!ApplicationState.TryTakeFromJson<List<Movie>>(nameof(MoviesList), 
            out var movies))
        {
            MoviesList = await MovieService.GetMoviesAsync();
        }
        else
        {
            MoviesList = movies;
        }

        persistingSubscription = ApplicationState.RegisterOnPersisting(() =>
        {
            ApplicationState.PersistAsJson(nameof(MoviesList), MoviesList);
            return Task.CompletedTask;
        });
    }

    public void Dispose() => persistingSubscription?.Dispose();
}

Este código ahora se puede simplificar mediante el nuevo modelo declarativo:

@page "/movies"
@inject IMovieService MovieService

@if (MoviesList == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <QuickGrid Items="MoviesList.AsQueryable()">
        ...
    </QuickGrid>
}

@code {
    [PersistentState]
    public List<Movie>? MoviesList { get; set; }

    protected override async Task OnInitializedAsync()
    {
        MoviesList ??= await MovieService.GetMoviesAsync();
    }
}

El estado se puede serializar para varios componentes del mismo tipo y puede establecer un estado declarativo en un servicio para usarlo en torno a la aplicación llamando a RegisterPersistentService en el generador de componentes Razor (AddRazorComponents) con un tipo de servicio y modo de representación personalizados. Para obtener más información, consulte Persistencia de estado pre-renderizado en ASP.NET CoreBlazor.

Nuevas características de interoperabilidad de JavaScript

Blazor agrega compatibilidad con las siguientes JS características de interoperabilidad:

  • Cree una instancia de un JS objeto mediante una función de constructor y obtenga el IJSObjectReference/IJSInProcessObjectReference identificador de .NET para hacer referencia a la instancia.
  • Lee o modifica el valor de una propiedad de objeto JS, tanto las propiedades de datos como las del descriptor de acceso.

Los siguientes métodos asincrónicos están disponibles en IJSRuntime y IJSObjectReference con el mismo comportamiento de ámbito que el método existente IJSRuntime.InvokeAsync :

  • InvokeConstructorAsync(string identifier, object?[]? args): invoca la función de constructor especificada JS de forma asincrónica. La función se invoca con el new operador . En el ejemplo siguiente, jsInterop.TestClass es una clase con una función de constructor y classRef es :IJSObjectReference

    var classRef = await JSRuntime.InvokeConstructorAsync("jsInterop.TestClass", "Blazor!");
    var text = await classRef.GetValueAsync<string>("text");
    var textLength = await classRef.InvokeAsync<int>("getTextLength");
    
  • GetValueAsync<TValue>(string identifier): lee el valor de la propiedad especificada JS de forma asincrónica. La propiedad no puede ser una propiedad de solo set. Se produce una excepción JSException si la propiedad no existe. En el ejemplo siguiente se devuelve un valor de una propiedad de datos:

    var valueFromDataPropertyAsync = await JSRuntime.GetValueAsync<int>(
      "jsInterop.testObject.num");
    
  • SetValueAsync<TValue>(string identifier, TValue value): actualiza el valor de la propiedad especificada JS de forma asincrónica. La propiedad no puede ser una propiedad de solo get. Si la propiedad no está definida en el objeto de destino, se crea la propiedad . JSException Se produce una excepción si la propiedad existe pero no se puede escribir o cuando no se puede agregar una nueva propiedad al objeto . En el ejemplo siguiente, se crea num en testObject con un valor de 30 si no existe:

    await JSRuntime.SetValueAsync("jsInterop.testObject.num", 30);
    

Hay disponibles sobrecargas para cada uno de los métodos anteriores que toman un argumento CancellationToken o un argumento de tiempo de espera TimeSpan.

Los siguientes métodos sincrónicos están disponibles en IJSInProcessRuntime y IJSInProcessObjectReference con el mismo comportamiento de ámbito que el método existente IJSInProcessObjectReference.Invoke :

  • InvokeConstructor(string identifier, object?[]? args): invoca la función de constructor especificada JS de forma sincrónica. La función se invoca con el new operador . En el ejemplo siguiente, jsInterop.TestClass es una clase con una función de constructor y classRef es :IJSInProcessObjectReference

    var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
    var classRef = inProcRuntime.InvokeConstructor("jsInterop.TestClass", "Blazor!");
    var text = classRef.GetValue<string>("text");
    var textLength = classRef.Invoke<int>("getTextLength");
    
  • GetValue<TValue>(string identifier): lee el valor de la propiedad especificada JS de forma sincrónica. La propiedad no puede ser una propiedad de solo set. Se produce una excepción JSException si la propiedad no existe. En el ejemplo siguiente se devuelve un valor de una propiedad de datos:

    var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
    var valueFromDataProperty = inProcRuntime.GetValue<int>(
      "jsInterop.testObject.num");
    
  • SetValue<TValue>(string identifier, TValue value): actualiza el valor de la propiedad especificada JS de forma sincrónica. La propiedad no puede ser una propiedad de solo get. Si la propiedad no está definida en el objeto de destino, se crea la propiedad . JSException Se produce una excepción si la propiedad existe pero no se puede escribir o cuando no se puede agregar una nueva propiedad al objeto . En el ejemplo siguiente, num se crea en testObject con un valor de 20 si no existe.

    var inProcRuntime = ((IJSInProcessRuntime)JSRuntime);
    inProcRuntime.SetValue("jsInterop.testObject.num", 20);
    

Para obtener más información, consulte las secciones siguientes del artículo Llamada a funciones de JavaScript desde métodos de .NET :

Blazor WebAssembly generación de perfiles de rendimiento y contadores de diagnóstico

Hay nuevos contadores de diagnóstico y generación de perfiles de rendimiento disponibles para Blazor WebAssembly aplicaciones. Para obtener más información, consulte los artículos siguientes:

Recursos estáticos precargados del marco Blazor

En Blazor Web App, los recursos estáticos del marco se precargan automáticamente mediante encabezados Link, lo que permite al explorador cargar previamente los recursos antes de que se capture y represente la página inicial. En las aplicaciones Blazor WebAssembly independientes, los recursos del marco están programados para la descarga y el almacenamiento en caché de alta prioridad al principio del procesamiento de páginas del explorador index.html.

Para obtener más información, consulte Archivos estáticos Blazor en ASP.NET Core.

Participación para evitar durante la NavigationException representación estática del lado servidor con NavigationManager.NavigateTo

Al llamar NavigationManager.NavigateTo durante la representación estática del lado servidor (SSR estático) se produce una NavigationExceptionexcepción , que interrumpe la ejecución antes de convertirse en una respuesta de redirección. Esto puede provocar confusión durante la depuración y es incoherente con el comportamiento de representación interactiva, donde el código después NavigateTo de continuar ejecutándose con normalidad.

En .NET 10, puede establecer la <BlazorDisableThrowNavigationException> propiedad true MSBuild en el archivo de proyecto de la aplicación para evitar que se produzca la excepción durante el SSR estático:

<PropertyGroup>
  <BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>

Con el conjunto de propiedades de MSBuild, la llamada durante NavigationManager.NavigateTo el SSR estático ya no produce una NavigationExceptionexcepción . En su lugar, se comporta de manera coherente con la representación interactiva y realiza la navegación sin provocar una excepción. Código después NavigationManager.NavigateTo de ejecutarse antes de que se produzca el redireccionamiento.

La plantilla de proyecto de .NET 10 Blazor Web App establece la propiedad true MSBuild en de forma predeterminada. Se recomienda que las aplicaciones que se actualicen a .NET 10 usen la nueva propiedad de MSBuild y eviten el comportamiento anterior.

Si se usa la propiedad MSBuild, se debe actualizar el código que dependía de NavigationException que se iniciase. En la interfaz de usuario predeterminada BlazorIdentity de la Blazor Web App plantilla de proyecto antes de la versión de .NET 10, IdentityRedirectManager produce una InvalidOperationException excepción después de llamar RedirectTo a para asegurarse de que el método no se invocó durante la representación interactiva. Esta excepción y los [DoesNotReturn] atributos ahora deben quitarse cuando se usa la propiedad MSBuild. Para obtener más información, consulte Migración de ASP.NET Core en .NET 9 a ASP.NET Core en .NET 10.

El router Blazor tiene un parámetro NotFoundPage

Blazor ahora proporciona una manera mejorada de mostrar una página "No encontrada" al navegar a una página inexistente. Puede especificar una página que se va a representar cuando NavigationManager.NotFound se invoque (que se describe en la sección siguiente) pasando un tipo de página al Router componente mediante el NotFoundPage parámetro . La característica admite el enrutamiento, funciona en el middleware de re-ejecución de páginas de códigos de estado y es compatible incluso con escenarios no-Blazor.

El NotFound fragmento de representación (<NotFound>...</NotFound>) no se admite en .NET 10 o posterior.

<Router AppAssembly="@typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>This content is ignored because NotFoundPage is defined.</NotFound>
</Router>

La Blazor plantilla de proyecto ahora incluye una NotFound.razor página de forma predeterminada. Esta página se renderiza automáticamente cada vez que se llama a NotFound en la aplicación, facilitando el manejo de las rutas que faltan con una experiencia de usuario coherente.

Para obtener más información, consulte navegación de ASP.NET CoreBlazor.

Respuestas no encontradas que usan NavigationManager para la SSR estática y la representación interactiva global

Ahora NavigationManager incluye un NotFound método para controlar escenarios en los que no se encuentra un recurso solicitado durante la representación estática del lado servidor (SSR estático) o la representación interactiva global:

  • Representación estática del lado servidor (SSR estático): la llamada NotFound establece el código de estado HTTP en 404.

  • Renderizado interactivo: indica al Blazor enrutador (Router componente) que renderice el contenido no encontrado.

  • Representación de streaming: si la navegación mejorada está activa, la representación de streaming representa contenido no encontrado sin volver a cargar la página. Cuando se bloquea la navegación mejorada, el marco redirige al contenido no encontrado con una actualización de página.

La representación en streaming solo puede representar componentes que tengan una ruta, como una asignación NotFoundPage (NotFoundPage="...") o una asignación de página de middleware de nueva ejecución de páginas de código de estado (UseStatusCodePagesWithReExecute). DefaultNotFound El contenido 404 ("Not found" texto sin formato) no tiene una ruta, por lo que no se puede usar durante la representación de streaming.

NotFound La representación de contenido usa lo siguiente, independientemente de si la respuesta se ha iniciado o no (en orden):

  • Si NotFoundEventArgs.Path se establece, renderice el contenido de la página asignada.
  • Si Router.NotFoundPage se establece, muestra la página asignada.
  • Una página de middleware para la reejecución de páginas de códigos de estado, si se ha configurado.
  • No hay ninguna acción si no se adopta ninguno de los enfoques anteriores.

Middleware de nueva ejecución de páginas de código de estado con UseStatusCodePagesWithReExecute tiene prioridad para los problemas de enrutamiento de direcciones basados en explorador; por ejemplo, si se ha escrito una dirección URL incorrecta en la barra de direcciones del explorador o se selecciona un vínculo que no tiene ningún punto de conexión en la aplicación.

Puede usar el NavigationManager.OnNotFound evento para las notificaciones cuando NotFound se invoca.

Para obtener más información y ejemplos, consulte ASP.NET navegación principalBlazor.

Compatibilidad con respuestas no encontradas en aplicaciones sin Blazorenrutador

Las aplicaciones que implementan un enrutador personalizado pueden usar NotFound. Hay dos maneras de indicar al renderizador qué página debe representarse cuando se llama a NotFound.

El enfoque recomendado que funciona independientemente del estado de respuesta es llamar a UseStatusCodePagesWithReExecute. Cuando se llama a NotFound, el middleware procesa la ruta de acceso que se pasa al método.

app.UseStatusCodePagesWithReExecute(
    "/not-found", createScopeForStatusCodePages: true);

Si no quiere usar UseStatusCodePagesWithReExecute, la aplicación todavía puede admitir NotFound para las respuestas que ya se han iniciado. Suscríbase a OnNotFoundEvent en el enrutador y asigne la ruta de acceso de la página No encontrada para NotFoundEventArgs.Path, para informar al renderizador del contenido que se va a representar cuando se llama a NotFound.

CustomRouter.razor:

@using Microsoft.AspNetCore.Components
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Http
@implements IDisposable
@inject NavigationManager NavigationManager

@code {
    protected override void OnInitialized() =>
        NavigationManager.OnNotFound += OnNotFoundEvent;

    [CascadingParameter]
    public HttpContext? HttpContext { get; set; }

    private void OnNotFoundEvent(object sender, NotFoundEventArgs e)
    {
        // Only execute the logic if HTTP response has started
        // because setting NotFoundEventArgs.Path blocks re-execution
        if (HttpContext?.Response.HasStarted == false)
        {
            return;
        }

        e.Path = GetNotFoundRoutePath();
    }

    // Return the path of the Not Found page that you want to display
    private string GetNotFoundRoutePath()
    {
        ...
    }

    public void Dispose() => NavigationManager.OnNotFound -= OnNotFoundEvent;
}

Si usa ambos enfoques en la aplicación, la ruta especificada como "No Encontrada" en el controlador OnNotFoundEvent tiene prioridad sobre la ruta configurada en el middleware de reejecución.

Métricas y seguimiento

Esta versión presenta funcionalidades completas de métricas y seguimiento para Blazor las aplicaciones, lo que proporciona observabilidad detallada del ciclo de vida del componente, navegación, control de eventos y administración de circuitos.

Para obtener más información, consulta Procedimientos recomendados de rendimiento de Blazor en ASP.NET Core.

Compatibilidad con el empaquetador de JavaScript

BlazorLa salida de compilación no es compatible con los paquetes de JavaScript, como Gulp, Webpack y Rollup. Blazor ahora puede generar una salida compatible con paquetes de instalación durante la publicación al establecer la propiedad de MSBuild WasmBundlerFriendlyBootConfig en true.

Para obtener más información, consulte Hospedaje e implementación de ASP.NET Core Blazor.

Precarga de recursos estáticos de Blazor WebAssembly en Blazor Web Apps

Reemplazamos los encabezados <link> por un componente ResourcePreloader (<ResourcePreloader />) para precargar los recursos de WebAssembly en Blazor Web Apps. Esto permite que la configuración de la ruta de acceso base de la aplicación (<base href="..." />) identifique correctamente la raíz de la aplicación.

Al quitar el componente, se desactiva la característica si la aplicación usa una devolución de llamada loadBootResource para modificar las direcciones URL.

La Blazor Web App plantilla adopta la característica de forma predeterminada en .NET 10 y las aplicaciones que se actualizan a .NET 10 pueden implementar la característica colocando el ResourcePreloader componente después de la etiqueta de dirección URL base () en el <base> contenido principal del componente (AppApp.razor):

<head>
    ...
    <base href="/" />
+   <ResourcePreloader />
    ...
</head>

Para obtener más información, vea Hospedaje e implementación de Blazoraplicaciones del lado servidor de ASP.NET Core.

Validación mejorada de formularios

Blazor ahora ha mejorado las funcionalidades de validación de formularios, incluida la compatibilidad con la validación de propiedades de objetos anidados y elementos de colección.

Para crear un formulario validado, use un DataAnnotationsValidator componente dentro de un EditForm componente, igual que antes.

Para participar en la nueva característica de validación:

  1. Llame al método de extensión AddValidation en el archivo Program donde se registran los servicios.
  2. Declare los tipos de modelo de formulario en un archivo de clase de C#, no en un Razor componente (.razor).
  3. Anote el tipo de modelo de formulario raíz con el [ValidatableType] atributo .

Sin seguir los pasos anteriores, el comportamiento de validación sigue siendo el mismo que en las versiones anteriores de .NET.

En el ejemplo siguiente se muestran los pedidos de los clientes con la validación mejorada de formularios (detalles omitidos para mayor brevedad):

En Program.cs, llame a AddValidation en la colección de servicios para habilitar el nuevo comportamiento de validación:

builder.Services.AddValidation();

En la siguiente Order clase, el [ValidatableType] atributo es necesario en el tipo de modelo de nivel superior. Los otros tipos se detectan automáticamente. OrderItem y ShippingAddress no se muestran por motivos de brevedad, pero la validación anidada y de colecciones funciona de la misma manera en esos tipos si se muestran.

Order.cs:

[ValidatableType]
public class Order
{
    public Customer Customer { get; set; } = new();
    public List<OrderItem> OrderItems { get; set; } = [];
}

public class Customer
{
    [Required(ErrorMessage = "Name is required.")]
    public string? FullName { get; set; }

    [Required(ErrorMessage = "Email is required.")]
    public string? Email { get; set; }

    public ShippingAddress ShippingAddress { get; set; } = new();
}

En el siguiente componente OrderPage, el componente DataAnnotationsValidator está presente en el componente EditForm.

OrderPage.razor:

<EditForm Model="Model">
    <DataAnnotationsValidator />

    <h3>Customer Details</h3>
    <div class="mb-3">
        <label>
            Full Name
            <InputText @bind-Value="Model!.Customer.FullName" />
        </label>
        <ValidationMessage For="@(() => Model!.Customer.FullName)" />
    </div>

    @* ... form continues ... *@
</EditForm>

@code {
    public Order? Model { get; set; }

    protected override void OnInitialized() => Model ??= new();

    // ... code continues ...
}

El requisito de declarar los tipos de modelo fuera de los archivos de los componentes Razor (.razor componentes) se debe al hecho de que tanto la nueva característica de validación como el compilador Razor usan un generador de código fuente. Actualmente, la salida de un generador de origen no se puede usar como entrada para otro generador de origen.

La compatibilidad con la validación ahora incluye:

  • Ahora se admite la validación de objetos y colecciones complejos anidados.
    • Esto incluye las reglas de validación definidas por atributos de propiedad, atributos de clase y la IValidatableObject implementación.
    • El [SkipValidation] atributo puede excluir propiedades o tipos de validación.
  • La validación ahora usa una implementación basada en generador de origen en lugar de una implementación basada en reflexión para mejorar el rendimiento y la compatibilidad con la compilación anticipada (AOT).

El DataAnnotationsValidator componente ahora tiene el mismo orden de validación y comportamiento de cortocircuito que System.ComponentModel.DataAnnotations.Validator. Las reglas siguientes se aplican al validar una instancia de tipo T:

  1. Las propiedades miembro de T se validan, incluida la validación recursiva de objetos anidados de forma recursiva.
  2. Los atributos de nivel de tipo de T se validan.
  3. El IValidatableObject.Validate método se ejecuta, si T lo implementa.

Si uno de los pasos anteriores produce un error de validación, se omiten los pasos restantes.

Uso de modelos de validación de un ensamblado diferente

Puede validar formularios con modelos definidos en un ensamblado diferente, como una biblioteca o en el proyecto .Client de Blazor Web App, creando un método en la biblioteca o en el proyecto .Client que recibe una instancia de IServiceCollection como argumento y llama a AddValidation en él.

  • En la aplicación, ejecute el método y AddValidation.

Para obtener más información y un ejemplo, vea la validación de formularios en ASP.NET CoreBlazor.

Se ha quitado la memoria caché personalizada Blazor y la propiedad de MSBuild BlazorCacheBootResources

Ahora que el explorador toma huellas digitales y almacena en caché todos los archivos del lado del cliente de Blazor, el mecanismo de almacenamiento en caché personalizado de Blazor y la propiedad MSBuild BlazorCacheBootResources se han quitado del marco. Si el archivo de proyecto del proyecto del lado cliente contiene la propiedad MSBuild, quite la propiedad , ya que ya no tiene ningún efecto:

- <BlazorCacheBootResources>...</BlazorCacheBootResources>

Para obtener más información, consulte ASP.NET Core Blazor WebAssembly almacenamiento en caché y errores de comprobación de integridad.

Compatibilidad con la API de autenticación web (clave de paso) para ASP.NET Core Identity

La compatibilidad con la API de autenticación web (WebAuthn), conocida ampliamente como claves de acceso, es un método de autenticación moderno y resistente a suplantación de identidad que mejora la seguridad y la experiencia del usuario aprovechando la criptografía de clave pública y la autenticación basada en dispositivos. ASP.NET Core Identity ahora admite la autenticación de clave de acceso basada en los estándares WebAuthn y FIDO2. Esta característica permite a los usuarios iniciar sesión sin contraseñas, mediante métodos de autenticación seguros basados en dispositivos, como biometría o claves de seguridad.

La Blazor Web App plantilla de proyecto proporciona una funcionalidad de inicio de sesión y administración de claves de paso integradas.

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

Persistencia del estado del circuito

Durante la renderización del lado servidor, Blazor Web Apppueden ahora preservar el estado de sesión (circuito) de un usuario cuando la conexión al servidor se pierde durante un período prolongado o se pausa proactivamente, siempre y cuando no se desencadene una actualización completa de la página. Esto permite a los usuarios reanudar su sesión sin perder el trabajo no guardado en los escenarios siguientes:

  • Limitación de las pestañas del explorador
  • Usuarios de dispositivos móviles que cambian de aplicaciones
  • Interrupciones de red
  • Administración proactiva de recursos (pausar circuitos inactivos)
  • Navegación mejorada

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

Recarga en caliente para Blazor WebAssembly y .NET en WebAssembly

El SDK migró a un Hot Reload de uso general para escenarios de WebAssembly. Hay una nueva propiedad WasmEnableHotReload de MSBuild que está true de forma predeterminada en la configuración Debug (Configuration == "Debug") que habilita la recarga en caliente.

Para otras configuraciones con nombres de configuración personalizados, establezca el valor true en el archivo de proyecto de la aplicación para habilitar recarga activa:

<PropertyGroup>
  <WasmEnableHotReload>true</WasmEnableHotReload>
</PropertyGroup>

Para deshabilitar la recarga en caliente en la configuración Debug, establezca el valor a false:

<PropertyGroup>
  <WasmEnableHotReload>false</WasmEnableHotReload>
</PropertyGroup>

Se ha actualizado el registro de trabajo del servicio PWA para evitar problemas de almacenamiento en caché.

El registro del service worker en la Blazor plantilla de proyecto Aplicación Web Progresiva (PWA) ahora incluye la updateViaCache: 'none' opción, lo que impide problemas de caché durante las actualizaciones del service worker.

- navigator.serviceWorker.register('service-worker.js');
+ navigator.serviceWorker.register('service-worker.js', { updateViaCache: 'none' });

La opción garantiza que:

  • El explorador no usa versiones almacenadas en caché del script de trabajo del servicio.
  • Las actualizaciones de trabajo del servicio se aplican de forma confiable sin que el almacenamiento en caché HTTP lo bloquee.
  • Las aplicaciones PWA pueden actualizar sus service workers de forma más predecible.

Esto soluciona los problemas de almacenamiento en caché que pueden impedir que las actualizaciones de trabajo del servicio se apliquen correctamente, lo que es especialmente importante para los PWA que dependen de los trabajadores del servicio para la funcionalidad sin conexión.

Se recomienda usar la opción configurada en none en todas las PWA, incluidas las que se dirigen a .NET 9 o versiones anteriores.

Extensibilidad de serialización para el estado de componente persistente

Implemente un serializador personalizado con PersistentComponentStateSerializer<T>. Sin un serializador personalizado registrado, la serialización vuelve a la serialización JSON existente.

El serializador personalizado se registra en el archivo de Program la aplicación. En el ejemplo siguiente, CustomUserSerializer se registra para el tipo TUser.

builder.Services.AddSingleton<PersistentComponentStateSerializer<TUser>, 
    CustomUserSerializer>();

El tipo se conserva y restaura automáticamente con el serializador personalizado:

[PersistentState] 
public User? CurrentUser { get; set; } = new();

OwningComponentBase ahora implementa IAsyncDisposable

OwningComponentBase ahora incluye compatibilidad con la eliminación asincrónica, lo que mejora la administración de recursos. Hay nuevos métodos DisposeAsync y DisposeAsyncCore, junto con un método Dispose actualizado, para gestionar la eliminación sincrónica y asincrónica del alcance del servicio.

Nuevo InputHidden componente para controlar campos de entrada ocultos en formularios

El nuevo InputHidden componente proporciona un campo de entrada oculto para almacenar valores de cadena.

En el ejemplo siguiente, se crea un campo de entrada oculto para la propiedad del Parameter formulario. Cuando se envía el formulario, se muestra el valor del campo oculto:

<EditForm Model="Parameter" OnValidSubmit="Submit" FormName="InputHidden Example">
    <InputHidden id="hidden" @bind-Value="Parameter" />
    <button type="submit">Submit</button>
</EditForm>

@if (submitted)
{
    <p>Hello @Parameter!</p>
}

@code {
    private bool submitted;

    [SupplyParameterFromForm] 
    public string Parameter { get; set; } = "stranger";

    private void Submit() => submitted = true;
}

Compatibilidad con el estado de componente persistente para la navegación mejorada

Blazor ahora admite el control del estado del componente persistente durante la navegación mejorada. Los componentes interactivos de la página pueden leer el estado persistente durante la navegación mejorada.

De forma predeterminada, el estado del componente persistente solo se carga mediante componentes interactivos cuando se cargan inicialmente en la página. Esto evita que se sobrescriba el estado importante, como los datos de un formulario web editado, si se producen eventos de navegación mejorados adicionales en la misma página después de cargar el componente.

Si los datos son de solo lectura y no cambian con frecuencia, opte por permitir las actualizaciones durante la navegación mejorada estableciendo AllowUpdates = true en el [PersistentState] atributo . Esto es útil para escenarios como mostrar datos almacenados en caché que son costosos de capturar, pero que no cambian a menudo. En el ejemplo siguiente se muestra el uso de para los datos de AllowUpdates previsión meteorológica:

[PersistentState(AllowUpdates = true)]
public WeatherForecast[]? Forecasts { get; set; }

protected override async Task OnInitializedAsync()
{
    Forecasts ??= await ForecastService.GetForecastAsync();
}

Para omitir el estado de restauración durante la representación previa, establezca en RestoreBehaviorSkipInitialValue:

[PersistentState(RestoreBehavior = RestoreBehavior.SkipInitialValue)]
public string NoPrerenderedData { get; set; }

Para omitir el estado de restauración durante la reconexión, establezca en RestoreBehaviorSkipLastSnapshot. Esto puede ser útil para garantizar datos nuevos después de la reconexión:

[PersistentState(RestoreBehavior = RestoreBehavior.SkipLastSnapshot)]
public int CounterNotRestoredOnReconnect { get; set; }

Llame PersistentComponentState.RegisterOnRestoring a para registrar una devolución de llamada para controlar imperativamente cómo se restaura el estado, de forma similar a cómo PersistentComponentState.RegisterOnPersisting proporciona control total de cómo se conserva el estado.

Blazor WebAssembly respeta la configuración actual de la cultura de la interfaz.

En .NET 9 o versiones anteriores, las aplicaciones independientes Blazor WebAssembly cargan recursos de globalización de la interfaz de usuario en función de CultureInfo.DefaultThreadCurrentCulture. Si desea cargar datos adicionales de globalización para la cultura de referencia de localización definida por CultureInfo.DefaultThreadCurrentUICulture, actualice la aplicación a .NET 10 o posterior.

Blazor Hybrid

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

Nuevo .NET MAUIBlazor Hybrid con un artículo y un ejemplo de Blazor Web App y ASP.NET Core Identity

Se ha agregado un nuevo artículo y una aplicación de ejemplo para .NET MAUIBlazor Hybrid y aplicación web mediante ASP.NET Core Identity.

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

SignalR

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

API mínimas

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

Tratar la cadena vacía en el envío del formulario como nula para los tipos de valor anulables

Cuando se utiliza el atributo [FromForm] con un objeto complejo en Minimal APIs, los valores de cadena vacíos de una publicación de formulario ahora se convierten en null, en lugar de provocar un error de análisis. Este comportamiento coincide con la lógica de procesamiento de entradas de formulario que no están asociadas a objetos complejos en las API mínimas.

using Microsoft.AspNetCore.Http;

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

app.MapPost("/todo", ([FromForm] Todo todo) => TypedResults.Ok(todo));

app.Run();

public class Todo
{
  public int Id { get; set; }
  public DateOnly? DueDate { get; set; } // Empty strings map to `null`
  public string Title { get; set; }
  public bool IsCompleted { get; set; }
}

Gracias a @nvmkpk por contribuir a este cambio!

Compatibilidad con la validación en las API mínimas

La compatibilidad con la validación en las API mínimas ya está disponible. Esta característica permite solicitar la validación de los datos enviados a los puntos de conexión de API. Habilitar la validación permite que el entorno de ejecución de ASP.NET Core realice las validaciones definidas en:

  • Query
  • Header
  • Cuerpo de la solicitud

Las validaciones se definen mediante atributos en el espacio de nombres DataAnnotations. Los desarrolladores personalizan el comportamiento del sistema de validación mediante:

  • Creación de implementaciones de atributos personalizados [Validation] .
  • Implementando la interfaz IValidatableObject para la lógica compleja de validación.

Si se produce un error en la validación, el tiempo de ejecución devuelve una respuesta de solicitud incorrecta 400 con detalles de los errores de validación.

Habilitación de la compatibilidad de validación integrada con las API mínimas

Habilite la compatibilidad de validación integrada con las API mínimas mediante una llamada al método de AddValidation extensión para registrar los servicios necesarios en el contenedor de servicios de la aplicación:

builder.Services.AddValidation();

La implementación detecta automáticamente los tipos definidos en controladores de API mínimos o como tipos base de tipos definidos en controladores de API mínimos. Un filtro de punto de conexión realiza la validación en estos tipos y se agrega para cada punto de conexión.

La validación se puede deshabilitar para puntos de conexión específicos mediante el DisableValidation método de extensión , como en el ejemplo siguiente:

app.MapPost("/products",
    ([EvenNumber(ErrorMessage = "Product ID must be even")] int productId, [Required] string name)
        => TypedResults.Ok(productId))
    .DisableValidation();

Note

Se han realizado varias mejoras y correcciones pequeñas en el generador de validación de API mínimas introducido en ASP.NET Core para .NET 10. Para admitir mejoras futuras, las API de resolución de validación subyacentes ahora se marcan como experimentales. Las API de nivel AddValidation superior y el filtro de validación integrado permanecen estables y no experimentales.

Validación con tipos de registro

Las API mínimas también admiten la validación con tipos de registro de C#. Los tipos de registro se pueden validar mediante atributos del System.ComponentModel.DataAnnotations espacio de nombres, de forma similar a las clases. Por ejemplo:

public record Product(
    [Required] string Name,
    [Range(1, 1000)] int Quantity);

Al usar tipos de registro como parámetros en los puntos de conexión de la API Minimal, los atributos de validación se aplican automáticamente de la misma manera que los tipos de clase:

app.MapPost("/products", (Product product) =>
{
    // Endpoint logic here
    return TypedResults.Ok(product);
});

Integración mínima de validación de API con IProblemDetailsService

Las respuestas de error de la lógica de validación para las API mínimas ahora se pueden personalizar mediante una IProblemDetailsService implementación proporcionada en la colección de servicios de aplicación (contenedor de inserción de dependencias). Esto permite respuestas de error más coherentes y específicas del usuario.

Compatibilidad con eventos de Server-Sent (SSE)

ASP.NET Core ahora admite la devolución de un resultado ServerSentEvents mediante la API TypedResults.ServerSentEvents . Esta característica se admite tanto en las API mínimas como en las aplicaciones basadas en controladores.

Server-Sent Eventos es una tecnología de inserción de servidor que permite a un servidor enviar una secuencia de mensajes de eventos a un cliente a través de una única conexión HTTP. En .NET, los mensajes de evento se representan como SseItem<T> objetos, que pueden contener un tipo de evento, un identificador y una carga de datos de tipo T.

La clase TypedResults tiene un nuevo método estático denominado ServerSentEvents que se puede usar para devolver un ServerSentEvents resultado. El primer parámetro de este método es un IAsyncEnumerable<SseItem<T>> que representa la secuencia de mensajes de evento que se enviarán al cliente.

En el ejemplo siguiente se muestra cómo usar la TypedResults.ServerSentEvents API para devolver un flujo de eventos de frecuencia cardíaca como objetos JSON al cliente:

app.MapGet("/json-item", (CancellationToken cancellationToken) =>
{
    async IAsyncEnumerable<HeartRateRecord> GetHeartRate(
        [EnumeratorCancellation] CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            var heartRate = Random.Shared.Next(60, 100);
            yield return HeartRateRecord.Create(heartRate);
            await Task.Delay(2000, cancellationToken);
        }
    }

    return TypedResults.ServerSentEvents(GetHeartRate(cancellationToken),
                                                  eventType: "heartRate");
});

Para obtener más información, consulte:

API de validación movidas a Microsoft.Extensions.Validation

Las API de validación se han movido al nuevo espacio de nombres Microsoft.Extensions.Validation y al paquete NuGet. Este cambio hace que las API se puedan usar fuera de ASP.NET escenarios HTTP principales. Las API públicas y el comportamiento permanecen sin cambios; solo el paquete y el espacio de nombres son diferentes. Los proyectos existentes no requieren cambios de código, ya que las referencias antiguas redirigen a la nueva implementación.

Validación mejorada para clases y registros

Los atributos de validación ahora se pueden aplicar a las clases y registros con un comportamiento coherente de generación y validación de código. Esta mejora mejora la flexibilidad al diseñar modelos mediante registros en aplicaciones ASP.NET Core.

Contribución comunitaria: Gracias a @marcominerva!

OpenAPI

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

Compatibilidad con OpenAPI 3.1

ASP.NET Core ha agregado compatibilidad para generar documentos de OpenAPI versión 3.1 en .NET 10. A pesar del aumento de la versión secundaria, OpenAPI 3.1 es una actualización significativa de la especificación de OpenAPI, en particular con compatibilidad completa con esquema JSON borrador 2020-12.

Algunos de los cambios que verá en el documento openAPI generado incluyen:

  • Los tipos anulables ya no tienen la propiedad nullable: true en el esquema.
  • En lugar de una propiedad nullable: true, tienen una palabra clave type cuyo valor es una matriz que incluye null como uno de los tipos.
  • Las propiedades o los parámetros definidos como C# int o long ahora aparecen en el documento OpenAPI generado sin el type: integer campo y tienen un pattern campo que limita el valor a dígitos. Esto sucede cuando la propiedad NumberHandling en JsonSerializerOptions está establecida en AllowReadingFromString, el valor predeterminado para aplicaciones web de ASP.NET Core. Para habilitar C# int y long para representarse en el documento OpenAPI como type: integer, establezca la propiedad NumberHandling a Strict.

Con esta característica, la versión predeterminada de OpenAPI para los documentos generados es3.1. La versión se puede cambiar estableciendo explícitamente la propiedad OpenApiVersion de OpenApiOptions en el configureOptions parámetro delegado de AddOpenApi:

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

Al generar el documento de OpenAPI en tiempo de compilación, se puede seleccionar la versión de OpenAPI estableciendo --openapi-version en el elemento OpenApiGenerateDocumentsOptions de MSBuild.

<PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <OpenApiGenerateDocuments>true</OpenApiGenerateDocuments>
    <!-- Configure build-time OpenAPI generation to produce an OpenAPI 3.1 document. -->
    <OpenApiGenerateDocumentsOptions>--openapi-version OpenApi3_1</OpenApiGenerateDocumentsOptions>
</PropertyGroup>

La compatibilidad con OpenAPI 3.1 se agregó principalmente en el siguiente PR.

Cambios importantes en OpenAPI 3.1

La compatibilidad con OpenAPI 3.1 requiere una actualización de la biblioteca de OpenAPI.NET subyacente a una nueva versión principal, 2.0. Esta nueva versión tiene algunos cambios importantes de la versión anterior. Los cambios disruptivos pueden afectar a las aplicaciones si tienen transformadores de documentos, operaciones o esquemas. Los cambios importantes en esta iteración incluyen lo siguiente:

  • Las entidades del documento openAPI, como las operaciones y los parámetros, se escriben como interfaces. Existen implementaciones concretas para las variantes insertadas y a las que se hace referencia de una entidad. Por ejemplo, un IOpenApiSchema puede ser un OpenApiSchema insertado o un OpenApiSchemaReference que apunte a un esquema definido en otro lugar del documento.
  • La propiedad Nullable se ha quitado del tipo OpenApiSchema. Para determinar si un tipo admite valores NULL, evalúe si la propiedad OpenApiSchema.Type establece JsonSchemaType.Null.

Uno de los cambios más significativos es que la clase OpenApiAny se ha quitado en favor de usar JsonNode directamente. Los transformadores que usan OpenApiAny deben actualizarse para usar JsonNode. La diferencia siguiente muestra los cambios en el transformador de esquema de .NET 9 a .NET 10:

options.AddSchemaTransformer((schema, context, cancellationToken) =>
{
    if (context.JsonTypeInfo.Type == typeof(WeatherForecast))
    {
-       schema.Example = new OpenApiObject
+       schema.Example = new JsonObject
        {
-           ["date"] = new OpenApiString(DateTime.Now.AddDays(1).ToString("yyyy-MM-dd")),
+           ["date"] = DateTime.Now.AddDays(1).ToString("yyyy-MM-dd"),
-           ["temperatureC"] = new OpenApiInteger(0),
+           ["temperatureC"] = 0,
-           ["temperatureF"] = new OpenApiInteger(32),
+           ["temperatureF"] = 32,
-           ["summary"] = new OpenApiString("Bracing"),
+           ["summary"] = "Bracing",
        };
    }
    return Task.CompletedTask;
});

Tenga en cuenta que estos cambios son necesarios incluso cuando solo se configura la versión de OpenAPI en 3.0.

OpenAPI en YAML

ASP.NET ahora admite el servicio del documento OpenAPI generado en formato YAML. YAML puede ser más conciso que JSON, lo que elimina llaves y comillas cuando se pueden deducir. YAML también admite cadenas de varias líneas, que pueden ser útiles para descripciones largas.

Para configurar una aplicación para que sirva el documento OpenAPI generado en formato YAML, especifique el punto de conexión en la llamada MapOpenApi con un sufijo ".yaml" o ".yml", como se muestra en el ejemplo siguiente:

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi("/openapi/{documentName}.yaml");
}

Compatibilidad con:

  • YAML solo está disponible actualmente para OpenAPI que se proporcionan desde el punto de conexión de OpenAPI.
  • La generación de documentos de OpenAPI en formato YAML en tiempo de compilación se agrega en una versión preliminar futura.

Consulte este PR que agregó soporte para mostrar el documento OpenAPI generado en formato YAML.

Descripción de la respuesta en ProducesResponseType para controladores de API

Ahora ProducesAttribute, ProducesResponseTypeAttributey ProducesDefaultResponseTypeAttribute aceptan un parámetro de cadena opcional, Description, que establece la descripción de la respuesta:

[HttpGet(Name = "GetWeatherForecast")]
[ProducesResponseType<IEnumerable<WeatherForecast>>(StatusCodes.Status200OK,
    Description = "The weather forecast for the next 5 days.")]
public IEnumerable<WeatherForecast> Get()
{

Datos de OpenAPI generados:

"responses": {
  "200": {
    "description": "The weather forecast for the next 5 days.",
    "content": {

Esta funcionalidad se admite en los controladores de API y en las API mínimas. Para las API mínimas, la Description propiedad se establece correctamente incluso cuando el tipo del atributo y el tipo de valor devuelto inferido no son una coincidencia exacta.

Contribución comunitaria (dotnet/aspnetcore n.º 58193) de Sander ten Brinke.

Poblar comentarios de documentos XML en un documento de OpenAPI

La generación de documentos OpenAPI de ASP.NET Core ahora incluirá los metadatos de los comentarios de documentación XML en las definiciones de métodos, clases y miembros dentro del documento OpenAPI. Debe habilitar los comentarios de documentos XML en el archivo de proyecto para usar esta característica. Para ello, agregue la siguiente propiedad al archivo del proyecto:

  <PropertyGroup>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>

En tiempo de compilación, el paquete OpenAPI aprovechará un generador de código fuente para detectar comentarios XML en el ensamblado de aplicación actual y cualquier referencia de proyecto y emitir código fuente para insertarlos en el documento a través de un transformador de documentos openAPI.

Tenga en cuenta que el proceso de compilación de C# no captura los comentarios de documentos XML colocados en expresiones lambda, por lo que para usar comentarios de documento XML para agregar metadatos a un punto de conexión de API mínima, debe definir el controlador de puntos de conexión como un método, colocar los comentarios del documento XML en el método y, a continuación, hacer referencia a ese método desde el MapXXX método . Por ejemplo, para usar comentarios XML de documentos para agregar metadatos a un punto de conexión de la API mínima definido originalmente como una expresión lambda:

app.MapGet("/hello", (string name) =>$"Hello, {name}!");

Cambie la llamada MapGet para hacer referencia a un método:

app.MapGet("/hello", Hello);

Defina el método Hello con comentarios de documentos XML:

static partial class Program
{
    /// <summary>
    /// Sends a greeting.
    /// </summary>
    /// <remarks>
    /// Greeting a person by their name.
    /// </remarks>
    /// <param name="name">The name of the person to greet.</param>
    /// <returns>A greeting.</returns>
    public static string Hello(string name)
    {
        return $"Hello, {name}!";
    }
}

En el ejemplo anterior, el método Hello se agrega a la clase Program, pero puede agregarlo a cualquier clase del proyecto.

En el ejemplo anterior se muestran los comentarios de documentos XML <summary>, <remarks>, y <param>. Para obtener más información sobre los comentarios de documentos XML, incluidas todas las etiquetas admitidas, consulte la documentación de C#.

Dado que la funcionalidad principal se proporciona a través de un generador de origen, se puede deshabilitar agregando el siguiente MSBuild al archivo de proyecto.

<ItemGroup>
  <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0-preview.2.*" GeneratePathProperty="true" />
</ItemGroup>

<Target Name="DisableCompileTimeOpenApiXmlGenerator" BeforeTargets="CoreCompile">
  <ItemGroup>
    <Analyzer Remove="$(PkgMicrosoft_AspNetCore_OpenApi)/analyzers/dotnet/cs/Microsoft.AspNetCore.OpenApi.SourceGenerators.dll" />
  </ItemGroup>
</Target>

El generador de origen procesa archivos XML incluidos en la propiedad AdditionalFiles. Para agregar (o quitar), los orígenes modifican la propiedad de la siguiente manera:

<Target Name="AddXmlSources" BeforeTargets="CoreCompile">
  <ItemGroup>
    <AdditionalFiles Include="$(PkgSome_Package)/lib/net10.0/Some.Package.xml" />
  </ItemGroup>
</Target>

Microsoft.AspNetCore.OpenApi agregado a la plantilla ASP.NET Core web API (AOT nativa)

La plantilla de proyecto ASP.NET Core Web API (AOT nativo) ahora incluye compatibilidad con la generación de documentos OpenAPI mediante el paquete de forma predeterminada. Esta compatibilidad está deshabilitada mediante la --no-openapi marca al crear un nuevo proyecto.

Contribución comunitaria (dotnet/aspnetcore #60337) por Sander ten Brinke.

Compatibilidad con IOpenApiDocumentProvider en el contenedor DI.

ASP.NET Core en .NET 10 admite IOpenApiDocumentProvider en el contenedor de inserción de dependencias (DI). Los desarrolladores pueden insertar IOpenApiDocumentProvider en sus aplicaciones y usarlos para acceder al documento openAPI. Este enfoque es útil para acceder a documentos de OpenAPI fuera del contexto de las solicitudes HTTP, como en servicios en segundo plano o middleware personalizado.

Anteriormente, la ejecución de la lógica de inicio de la aplicación sin iniciar un servidor HTTP se podía realizar mediante HostFactoryResolver con una implementación de no-op IServer . La nueva característica simplifica este proceso proporcionando una API optimizada inspirada en AspireIDistributedApplicationPublisher, que forma parte del marco de Aspire trabajo para el hospedaje y la publicación de aplicaciones distribuidas.

Para obtener más información, vea dotnet/aspnetcore #61463.

Mejoras en el generador de comentarios XML

La generación de comentarios XML controla tipos complejos en .NET 10 mejor que las versiones anteriores de .NET.

  • Genera comentarios XML precisos y completos para una gama más amplia de tipos.
  • Controla escenarios más complejos.
  • Omite correctamente el procesamiento de tipos complejos que provocan errores de compilación en versiones anteriores.

Estas mejoras cambian el modo de error para determinados escenarios de errores de compilación a metadatos que faltan.

Además, el procesamiento de comentarios de documentos XML ahora se puede configurar para acceder a los comentarios XML en otros ensamblados. Esto resulta útil para generar documentación para los tipos definidos fuera del ensamblado actual, como el ProblemDetails tipo en el Microsoft.AspNetCore.Http espacio de nombres.

Esta configuración se realiza con directivas en el archivo de compilación del proyecto. En el ejemplo siguiente se muestra cómo configurar el generador de comentarios XML para acceder a los comentarios XML de los tipos del Microsoft.AspNetCore.Http ensamblado, que incluye la ProblemDetails clase .

<Target Name="AddOpenApiDependencies" AfterTargets="ResolveReferences">
  <ItemGroup>
  <!-- Include XML documentation from Microsoft.AspNetCore.Http.Abstractions
    to get metadata for ProblemDetails -->
    <AdditionalFiles
          Include="@(ReferencePath->'
            %(RootDir)%(Directory)%(Filename).xml')"
          Condition="'%(ReferencePath.Filename)' ==
           'Microsoft.AspNetCore.Http.Abstractions'"
          KeepMetadata="Identity;HintPath" />
  </ItemGroup>
</Target>

Esperamos incluir comentarios XML de un conjunto seleccionado de ensamblados en el marco compartido en versiones preliminares futuras para evitar la necesidad de esta configuración en la mayoría de los casos.

Control unificado de identificadores de documentación en el generador de comentarios de OpenAPI XML

Los comentarios de documentación XML de los ensamblados referenciados se combinan correctamente incluso cuando sus identificadores de documentación incluyen sufijos de tipo de retorno. Como resultado, todos los comentarios XML válidos se incluyen de forma confiable en la documentación de OpenAPI generada, lo que mejora la precisión de la documentación y la integridad de las API mediante ensamblados a los que se hace referencia.

Los parámetros enum de datos del formulario utilizan el tipo enum real en OpenAPI

Los parámetros de datos de formulario en las acciones del controlador de MVC ahora generan metadatos de OpenAPI utilizando el tipo de enumeración real en lugar de usar cadenas por defecto.

Contribución comunitaria: Gracias a @ascott18!

Compatibilidad con la generación de OpenApiSchemas en transformadores

Los desarrolladores ahora pueden generar un esquema para un tipo de C# con la misma lógica que ASP.NET generación de documentos de OpenAPI core y agregarlo al documento de OpenAPI. A continuación, se puede hacer referencia al esquema desde otro lugar del documento openAPI.

El contexto pasado a los transformadores de documento, operación y esquema incluye un nuevo GetOrCreateSchemaAsync método que se puede usar para generar un esquema para un tipo. Este método también tiene un parámetro opcional ApiParameterDescription para especificar metadatos adicionales para el esquema generado.

Para admitir la adición del esquema al documento openAPI, se ha agregado una Document propiedad a los contextos de transformador de operación y esquema. Esto permite que cualquier transformador agregue un esquema al documento openAPI mediante el método del AddComponent documento.

Example

Para usar esta característica en un documento, operación o transformador de esquemas, cree el esquema mediante el GetOrCreateSchemaAsync método proporcionado en el contexto y agréguelo al documento OpenAPI mediante el AddComponent método del documento.

builder.Services.AddOpenApi(options =>
{
    options.AddOperationTransformer(async (operation, context, cancellationToken) =>
    {
        // Generate schema for error responses
        var errorSchema = await context.GetOrCreateSchemaAsync(typeof(ProblemDetails), null, cancellationToken);
        context.Document?.AddComponent("Error", errorSchema);

        operation.Responses ??= new OpenApiResponses();
        // Add a "4XX" response to the operation with the newly created schema
        operation.Responses["4XX"] = new OpenApiResponse
        {
            Description = "Bad Request",
            Content = new Dictionary<string, OpenApiMediaType>
            {
                ["application/problem+json"] = new OpenApiMediaType
                {
                    Schema = new OpenApiSchemaReference("Error", context.Document)
                }
            }
        };
    });
});

Transformadores de operación OpenAPI específicos para el punto final

Los transformadores de operación específicos del punto de conexión permiten la personalización específica de la documentación de OpenAPI para puntos de conexión de ruta individuales. Esta característica permite a los desarrolladores adaptar los metadatos y descripciones de Swagger/OpenAPI por acción o por ruta, lo que mejora la extensibilidad para escenarios avanzados de API.

Para obtener detalles de implementación y ejemplos de código, consulte Personalización de documentos de OpenAPI.

Actualización de Microsoft.OpenApi a 2.0.0

La Microsoft.OpenApi biblioteca que se usa para la generación de documentos de OpenAPI en ASP.NET Core se ha actualizado a la versión 2.0.0 (GA).

Cambios importantes en la versión 2.0.0

Los siguientes cambios importantes se introdujeron en las versiones preliminares y permanecen en la versión de disponibilidad general. Estos afectan principalmente a los usuarios que implementan transformadores de documentos, operaciones o esquemas:

Con la actualización a la versión de disponibilidad general, no se espera ningún cambio importante adicional en la generación de documentos de OpenAPI.

Mejoras en la generación de esquemas de OpenAPI

Modelar tipos que aceptan valores NULL mediante oneOf en el esquema de OpenAPI

La generación de esquemas OpenAPI para tipos anulables se ha mejorado usando el patrón oneOf en lugar de la propiedad anulable para tipos y colecciones complejos. La implementación:

  • Usa oneOf con null y el esquema de tipo real para tipos complejos que aceptan valores NULL en esquemas de solicitud y respuesta.
  • Detecta la nulabilidad de los parámetros, las propiedades y los tipos de valor devuelto mediante la reflexión y NullabilityInfoContext.
  • Quita los tipos NULL de esquemas componentes para evitar la duplicación.

Correcciones y mejoras en la resolución de referencias de esquemas

Esta versión mejora el control de esquemas JSON para la generación de documentos openAPI mediante la resolución correcta de referencias de esquema JSON relativas ($ref) en el documento de esquema raíz.

Incluir descripciones de propiedades como elementos del mismo nivel de $ref en el esquema de OpenAPI

Antes de .NET 10, ASP.NET Core descartaba descripciones en las propiedades definidas con $ref en el documento de OpenAPI generado porque OpenAPI v3.0 no permitía propiedades hermanas junto a $ref en las definiciones de los esquemas. OpenAPI 3.1 ahora le permite incluir descripciones junto con $ref. RC1 agrega compatibilidad con la inclusión de descripciones de propiedades como elementos hermanos de $ref en el esquema OpenAPI generado.

Esta fue una contribución comunitaria. ¡Gracias @desjoerd!

Adición de metadatos de comentarios XML en [AsParameters] tipos al esquema de OpenAPI

La generación de esquemas openAPI ahora procesa comentarios XML sobre las propiedades de las clases de [AsParameters] parámetros para extraer metadatos para la documentación.

Exclusión de métodos HTTP desconocidos de OpenAPI

La generación de esquemas de OpenAPI ahora excluye métodos HTTP desconocidos del documento openAPI generado. Los métodos de consulta, que son métodos HTTP estándar pero no reconocidos por OpenAPI, ahora se excluyen correctamente del documento de OpenAPI generado.

Esta fue una contribución comunitaria. ¡Gracias @martincostello!

Mejora de la descripción de los cuerpos de solicitud de parche JSON

La generación de esquemas OpenAPI para operaciones de parcheo JSON ahora aplica correctamente el tipo de contenido application/json-patch+json a los cuerpos que utilizan JSON Patch. Esto garantiza que el documento OpenAPI generado refleje con precisión el tipo de medio esperado para las operaciones de JSON Patch. Además, el cuerpo de la solicitud de revisión JSON tiene un esquema detallado que describe la estructura del documento de revisión JSON, incluidas las operaciones que se pueden realizar.

Esta fue una contribución comunitaria. ¡Gracias @martincostello!

Uso de cultura invariable para la generación de documentos OpenAPI

La generación de documentos de OpenAPI ahora utiliza la cultura invariable para formatear números y fechas en el documento OpenAPI generado. Esto garantiza que el documento generado sea coherente y no varía en función de la configuración cultural del servidor.

Esta fue una contribución comunitaria. ¡Gracias @martincostello!

Autenticación y autorización

Métricas de autenticación y autorización

Se han agregado métricas para determinados eventos de autenticación y autorización en ASP.NET Core. Con este cambio, ahora puede obtener métricas para los siguientes eventos:

  • Authentication:
    • Duración de la solicitud autenticada
    • Número de desafíos
    • Número de prohibiciones
    • Número de inicios de sesión
    • Número de cierres de sesión
  • Authorization:
    • Recuento de solicitudes que requieren autorización

En la imagen siguiente se muestra un ejemplo de la métrica Duración de la solicitud autenticada en el Aspire panel:

Duración de la solicitud autenticada en el Aspire panel

Para obtener más información, consulte métricas integradas de ASP.NET Core.

métricas de ASP.NET Core Identity

ASP.NET Core Identity La observabilidad se ha mejorado en .NET 10 con métricas. Las métricas son contadores, histogramas y medidores que proporcionan medidas de serie temporal del comportamiento del sistema o de la aplicación.

Por ejemplo, use las nuevas métricas de ASP.NET Core Identity para observar:

  • Administración de usuarios: nuevas creaciones de usuarios, cambios de contraseña y asignaciones de roles.
  • Control de inicios de sesión o sesión: intentos de inicio de sesión, inicios de sesión, cierres de sesión y usuarios que usan la autenticación en dos fases.

Las nuevas métricas están en el Microsoft.AspNetCore.Identity medidor:

  • aspnetcore.identity.user.create.duration
  • aspnetcore.identity.user.update.duration
  • aspnetcore.identity.user.delete.duration
  • aspnetcore.identity.user.check_password_attempts
  • aspnetcore.identity.user.generated_tokens
  • aspnetcore.identity.user.verify_token_attempts
  • aspnetcore.identity.sign_in.authenticate.duration
  • aspnetcore.identity.sign_in.check_password_attempts
  • aspnetcore.identity.sign_in.sign_ins
  • aspnetcore.identity.sign_in.sign_outs
  • aspnetcore.identity.sign_in.two_factor_clients_remembered
  • aspnetcore.identity.sign_in.two_factor_clients_forgotten

Para obtener más información sobre el uso de métricas en ASP.NET Core, consulte métricas de ASP.NET Core.

De forma predeterminada, las solicitudes no autenticadas y no autorizadas realizadas a puntos de conexión de API conocidos protegidos por cookie la autenticación ahora dan lugar a respuestas 401 y 403 en lugar de redirigir a un URI de inicio de sesión o acceso denegado.

Este cambio fue muy solicitado, ya que la redirección de solicitudes no autenticadas a una página de inicio de sesión no suele tener sentido para los puntos de conexión de API que normalmente se basan en códigos de estado 401 y 403 en lugar de redireccionamientos HTML para comunicar errores de autenticación.

Los puntos de conexión de API conocidos se identifican mediante la nueva IApiEndpointMetadata interfaz y los metadatos que implementan la nueva interfaz se han agregado automáticamente a lo siguiente:

  • [ApiController] Puntos de conexión
  • Puntos de conexión de API mínimos que leen cuerpos de solicitud JSON o escriben respuestas JSON
  • Puntos de conexión que utilizan tipos de valor devuelto TypedResults
  • SignalR Puntos de conexión

Cuando IApiEndpointMetadata está presente, el cookie controlador de autenticación ahora devuelve los códigos de estado HTTP adecuados (401 para las solicitudes no autenticadas, 403 para las solicitudes prohibidas) en lugar de redirigir.

Si desea evitar este nuevo comportamiento y redirigir siempre a los URI de inicio de sesión y acceso denegado para solicitudes no autenticadas o no autorizadas independientemente del punto de conexión de destino, puede invalidar los RedirectToLogin eventos y RedirectToAccessDenied de la siguiente manera:

builder.Services.AddAuthentication()
    .AddCookie(options =>
    {
        options.Events.OnRedirectToLogin = context =>
        {
            context.Response.Redirect(context.RedirectUri);
            return Task.CompletedTask;
        };

        options.Events.OnRedirectToAccessDenied = context =>
        {
            context.Response.Redirect(context.RedirectUri);
            return Task.CompletedTask;
        };
    });

Para obtener más información sobre este cambio disruptivo, consulte el anuncio de cambios importantes de ASP.NET Core.

Miscellaneous

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

Configuración de la supresión de diagnósticos del controlador de excepciones

Se ha agregado una nueva opción de configuración al middleware del controlador de excepciones de ASP.NET Core para controlar la salida de diagnóstico: ExceptionHandlerOptions.SuppressDiagnosticsCallback. Esta callback se pasa al contexto relacionado con la solicitud y la excepción, lo que le permite agregar lógica que determina si el middleware debe registrar excepciones y telemetría.

Esta configuración es útil cuando sabe que una excepción es transitoria o se ha controlado mediante el middleware del controlador de excepciones y no desea que los registros de errores se escriban en la plataforma de observabilidad.

El comportamiento predeterminado del middleware también ha cambiado: ya no escribe diagnósticos de excepciones para las excepciones controladas por IExceptionHandler. En función de los comentarios del usuario, el registro de excepciones controladas en el nivel de error a menudo resultaba indeseable cuando IExceptionHandler.TryHandleAsync se devolvía true.

Puede revertir al comportamiento anterior configurando SuppressDiagnosticsCallback:

app.UseExceptionHandler(new ExceptionHandlerOptions
{
    SuppressDiagnosticsCallback = context => false;
});

Para obtener más información sobre este cambio disruptivo, vea https://github.com/aspnet/Announcements/issues/524.

Compatibilidad con el dominio de Top-Level .localhost

El .localhost dominio de nivel superior (TLD) se define en RFC2606 y RFC6761 como reservados con fines de prueba y disponibles para que los usuarios usen localmente como harían con cualquier otro nombre de dominio. Esto significa que se permite usar un nombre como myapp.localhost localmente que se resuelve en la dirección de loopback IP, según lo esperado por estas RFC. Además, los exploradores perennes modernos ya resuelven automáticamente cualquier *.localhost nombre en la dirección de bucle invertido IP (127.0.0.1/::1), lo que los convierte en un alias para cualquier servicio que ya esté hospedado en localhost el equipo local, es decir, cualquier servicio que responda http://localhost:6789 también responderá a http://anything-here.localhost:6789, suponiendo que el servicio no realice ninguna comprobación o aplicación de nombre de host específica.

ASP.NET Core se ha actualizado en .NET 10 Preview 7 para admitir mejor el .localhost TLD, de modo que ahora se pueda usar fácilmente al crear y ejecutar aplicaciones de ASP.NET Core en el entorno de desarrollo local. Tener diferentes aplicaciones que se ejecutan localmente se pueden resolver a través de nombres diferentes permite una mejor separación de algunos recursos de sitio web asociados a nombres de dominio, por ejemplo, cookies y facilita la identificación de la aplicación que navega a través del nombre que se muestra en la barra de direcciones del explorador.

El servidor HTTP integrado de ASP.NET Core, Kestrel, ahora tratará correctamente cualquier nombre establecido mediante los mecanismos de configuración de puntos de *.localhost como dirección de bucle invertido local y, por tanto, se enlazará a él en lugar de a todas las direcciones externas (es decir, enlazar a 127.0.0.1/ en lugar de ::10.0.0.0/). Esto incluye la "applicationUrl" propiedad en los perfiles de inicio configurados en un archivo launchSettings.json, y la variable de entorno ASPNETCORE_URLS. Cuando esté configurado para escuchar en una .localhost dirección, Kestrel registrará un mensaje de información tanto para las .localhost direcciones comolocalhost para dejar claro que se pueden usar ambos nombres.

Aunque los exploradores web resuelven *.localhost automáticamente los nombres en la dirección de bucle invertido local, otras aplicaciones podrían tratar *.localhost los nombres como nombres de dominio normales e intentar resolverlos a través de su pila DNS correspondiente. Si la configuración de DNS no resuelve *.localhost nombres en una dirección, no se pueden conectar. Puede seguir usando el nombre normal localhost para abordar las aplicaciones cuando no está en un explorador web.

El certificado de desarrollo de ASP.NET Core HTTPS (incluido el dotnet dev-certs https comando) se ha actualizado para asegurarse de que el certificado es válido para su uso con el nombre de *.dev.localhost dominio. Después de instalar .NET 10 SDK Preview 7, confíe en el nuevo certificado de desarrollador ejecutando dotnet dev-certs https --trust en la línea de comandos para asegurarse de que el sistema está configurado para confiar en el nuevo certificado.

El certificado muestra el *.dev.localhost nombre como nombre alternativo del firmante (SAN) en lugar de *.localhost porque el uso de un certificado comodín para un nombre de dominio de nivel superior no es válido.

Las plantillas de proyecto para ASP.NET Core Empty () y web (Blazor Web Appblazor) se han actualizado con una nueva opción que cuando se especifica configura el proyecto creado para usar el .dev.localhost sufijo de nombre de dominio, combinándolo con el nombre del proyecto para permitir que la aplicación se examine en una dirección como https://myapp.dev.localhost:5036:

$ dotnet new web -n MyApp --localhost-tld
The template "ASP.NET Core Empty" was created successfully.

Processing post-creation actions...
Restoring D:\src\MyApp\MyApp.csproj:
Restore succeeded.

$ cd .\MyApp\
$ dotnet run --launch-profile https
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://myapp.dev.localhost:7099
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: https://localhost:7099/
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://myapp.dev.localhost:5036
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5036/
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: D:\src\local\10.0.1xx\MyApp

Compatibilidad con la deserialización de Json+PipeReader en MVC y LAS API mínimas

Relaciones Públicas: https://github.com/dotnet/aspnetcore/pull/62895

Ver https://github.com/dotnet/core/blob/main/release-notes/10.0/preview/preview7/libraries.md#pipereader-support-for-json-serializer

MVC, Minimal APIs y los HttpRequestJsonExtensions.ReadFromJsonAsync métodos se han actualizado para usar Json+PipeReader sin necesidad de cambios de código de parte de las aplicaciones.

Para la mayoría de las aplicaciones, la adición de esta compatibilidad no tiene ningún efecto en su comportamiento. Sin embargo, si la aplicación usa un personalizado JsonConverter, existe la posibilidad de que el convertidor no se controle Utf8JsonReader.HasValueSequence correctamente. Esto puede dar lugar a errores y datos que faltan, como ArgumentOutOfRangeException, al deserializar.

La solución rápida (especialmente si no posee el personalizado JsonConverter que se usa) es establecer el "Microsoft.AspNetCore.UseStreamBasedJsonParsing"AppContext modificador en "true". Debe ser una solución alternativa temporal y JsonConverter debe actualizarse para admitir HasValueSequence.

Para corregir JsonConverter las implementaciones, hay una corrección rápida que asigna una matriz de ReadOnlySequence y tendría el siguiente aspecto:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    var span = reader.HasValueSequence ? reader.ValueSequence.ToArray() : reader.ValueSpan;
    // previous code
}

También hay una corrección más complicada (pero eficaz), lo que implicaría tener una ruta de acceso de código independiente para el ReadOnlySequence control:

public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
    if (reader.HasValueSequence)
    {
        reader.ValueSequence;
        // ReadOnlySequence optimized path
    }
    else
    {
        reader.ValueSpan;
        // ReadOnlySpan optimized path
    }
}

Evicción automática del pool de memoria

Los grupos de memoria usados por Kestrel, IIS y HTTP.sys ahora expulsan automáticamente los bloques de memoria cuando la aplicación está inactiva o bajo menos carga. La característica se ejecuta automáticamente y no es necesario habilitarla ni configurarla manualmente.

¿Por qué importa la expulsión de memoria?

Anteriormente, la memoria asignada por el grupo permanecía reservada, incluso cuando no estaba en uso. Esta característica vuelve a liberar memoria en el sistema cuando la aplicación está inactiva durante un período de tiempo. Esta expulsión reduce el uso general de memoria y ayuda a las aplicaciones a mantener la capacidad de respuesta en distintas cargas de trabajo.

Utilice métricas de expulsión de memoria

Las métricas se han agregado al grupo de memoria predeterminado usado por nuestras implementaciones de servidor. Las nuevas métricas están bajo el nombre "Microsoft.AspNetCore.MemoryPool".

Para obtener información sobre las métricas y cómo usarlas, consulte métricas de ASP.NET Core.

Administración de grupos de memoria

Además de usar grupos de memoria de forma más eficaz al expulsar bloques de memoria innecesarios, .NET 10 mejora la experiencia de creación de grupos de memoria. Para ello, proporciona un IMemoryPoolFactory integrado y una MemoryPoolFactory implementación. Hace que la implementación esté disponible para la aplicación a través de la inserción de dependencias.

En el ejemplo de código siguiente se muestra un servicio en segundo plano sencillo que usa la implementación integrada del generador de grupos de memoria para crear grupos de memoria. Estos grupos se benefician de la característica de expulsión automática:

public class MyBackgroundService : BackgroundService
{
    private readonly MemoryPool<byte> _memoryPool;

    public MyBackgroundService(IMemoryPoolFactory<byte> factory)
    {
        _memoryPool = factory.Create();
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await Task.Delay(20, stoppingToken);
                // do work that needs memory
                var rented = _memoryPool.Rent(100);
                rented.Dispose();
            }
            catch (OperationCanceledException)
            {
                return;
            }
        }
    }
}

Para usar su propia factoría de grupos de memoria, cree una clase que implemente IMemoryPoolFactory y regístrela con la inyección de dependencias, como en el siguiente ejemplo. Los grupos de memoria creados de esta manera también se benefician de la característica de expulsión automática:

services.AddSingleton<IMemoryPoolFactory<byte>,
CustomMemoryPoolFactory>();

public class CustomMemoryPoolFactory : IMemoryPoolFactory<byte>
{
    public MemoryPool<byte> Create()
    {
        // Return a custom MemoryPool implementation
        // or the default, as is shown here.
        return MemoryPool<byte>.Shared;
    }
}

Descriptores de seguridad personalizables para HTTP.sys

Ahora puede especificar un descriptor de seguridad personalizado para las colas de solicitud HTTP.sys. La nueva propiedad RequestQueueSecurityDescriptor en HttpSysOptions permite un control más pormenorizado sobre los derechos de acceso de la cola de solicitudes. Este control pormenorizado le permite adaptar la seguridad a las necesidades de la aplicación.

Qué puede hacer con la nueva propiedad

Una cola de solicitudes en HTTP.sys es una estructura de nivel de kernel que almacena temporalmente las solicitudes HTTP entrantes hasta que la aplicación esté lista para procesarlas. Al personalizar el descriptor de seguridad, puede permitir o denegar el acceso de usuarios o grupos específicos a la cola de solicitudes. Esto es útil en escenarios en los cuales desea restringir o delegar la gestión de solicitudes HTTP.sys a nivel del sistema operativo.

Uso de la nueva propiedad

La RequestQueueSecurityDescriptor propiedad solo se aplica al crear una nueva cola de solicitudes. La propiedad no afecta a las colas de solicitudes existentes. Para usar esta propiedad, establézcala en una GenericSecurityDescriptor instancia al configurar el servidor de HTTP.sys.

Por ejemplo, el código siguiente permite a todos los usuarios autenticados, pero deniega a los invitados:

using System.Security.AccessControl;
using System.Security.Principal;
using Microsoft.AspNetCore.Server.HttpSys;

// Create a new security descriptor
var securityDescriptor = new CommonSecurityDescriptor(isContainer: false, isDS: false, sddlForm: string.Empty);

// Create a discretionary access control list (DACL)
var dacl = new DiscretionaryAcl(isContainer: false, isDS: false, capacity: 2);
dacl.AddAccess(
    AccessControlType.Allow,
    new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null),
    -1,
    InheritanceFlags.None,
    PropagationFlags.None
);
dacl.AddAccess(
    AccessControlType.Deny,
    new SecurityIdentifier(WellKnownSidType.BuiltinGuestsSid, null),
    -1,
    InheritanceFlags.None,
    PropagationFlags.None
);

// Assign the DACL to the security descriptor
securityDescriptor.DiscretionaryAcl = dacl;

// Configure HTTP.sys options
var builder = WebApplication.CreateBuilder();
builder.WebHost.UseHttpSys(options =>
{
    options.RequestQueueSecurityDescriptor = securityDescriptor;
});

Para más información, vea HTTP.sys web server implementation in ASP.NET Core (Implementaciones del servidor web de HTTP.sys en ASP.NET Core).

Mejor compatibilidad con las aplicaciones de prueba con instrucciones de nivel superior

.NET 10 ahora tiene mejor soporte para probar aplicaciones que usan instrucciones de nivel superior. Anteriormente, los desarrolladores tenían que agregar manualmente public partial class Program al archivo Program.cs para que el proyecto de prueba pudiera hacer referencia al Program class. public partial class Program era necesario porque la declaración de nivel superior de C# 9 generó un Program class, que fue declarado como interno.

En .NET 10, se usa un generador de origen para generar la declaración de public partial class Program si el programador no la ha declarado explícitamente. Además, se ha agregado un analizador para detectar cuándo public partial class Program se declara explícitamente y aconseja al desarrollador que lo quite.

Image

Las siguientes solicitudes de incorporación de cambios contribuyeron a esta característica.

Nueva implementación de parches JSON con System.Text.Json

Parche JSON:

  • Es un formato estándar para describir los cambios que se aplicarán a un documento JSON.
  • Se define en RFC 6902 y se usa ampliamente en las API DE RESTful para realizar actualizaciones parciales en los recursos JSON.
  • Representa una secuencia de operaciones (por ejemplo, Agregar, Quitar, Reemplazar, Mover, Copiar, Probar) que se puede aplicar para modificar un documento JSON.

En las aplicaciones web, JSON Patch se utiliza comúnmente en una operación PATCH para realizar actualizaciones parciales de un recurso. En lugar de enviar todo el recurso para una actualización, los clientes pueden enviar un documento de revisión JSON que contenga solo los cambios. La aplicación de parches reduce el tamaño de la carga y mejora la eficiencia.

Esta versión presenta una nueva implementación de Microsoft.AspNetCore.JsonPatch basada en la System.Text.Json serialización. Esta característica:

  • Se alinea con las prácticas modernas de .NET aprovechando la System.Text.Json biblioteca, que está optimizada para .NET.
  • Proporciona un rendimiento mejorado y un uso reducido de memoria en comparación con la implementación basada en la herencia Newtonsoft.Json.

Los siguientes puntos de referencia comparan el rendimiento de la nueva System.Text.Json implementación con la implementación heredada Newtonsoft.Json .

Scenario Implementation Mean Memoria asignada
Pruebas comparativas de aplicaciones Newtonsoft.JsonPatch 271.924 μs 25 KB
System.Text.JsonPatch 1,584 μs 3 KB
Pruebas comparativas de deserialización Newtonsoft.JsonPatch 19.261 μs 43 KB
System.Text.JsonPatch 7.917 μs 7 KB

Estas pruebas comparativas resaltan importantes mejoras de rendimiento y reducen el uso de memoria con la nueva implementación.

Notes:

  • La nueva implementación no es un reemplazo directo para la implementación heredada. En concreto, la nueva implementación no admite tipos dinámicos, por ejemplo, ExpandoObject.
  • El estándar json Patch tiene riesgos de seguridad inherentes. Dado que estos riesgos son inherentes al estándar de revisión JSON, la nueva implementación no intenta mitigar los riesgos de seguridad inherentes. Es responsabilidad del desarrollador asegurarse de que el documento de revisión JSON es seguro para aplicarlo al objeto de destino. Para obtener más información, consulte la sección Mitigación de riesgos de seguridad .

Usage

Para habilitar la compatibilidad con los parches JSON con System.Text.Json, instale el paquete NuGet Microsoft.AspNetCore.JsonPatch.SystemTextJson.

dotnet add package Microsoft.AspNetCore.JsonPatch.SystemTextJson --prerelease

Este paquete proporciona una clase para representar un JsonPatchDocument<T> documento de revisión JSON para objetos de tipo T y lógica personalizada para serializar y deserializar documentos de revisión JSON mediante System.Text.Json. El método clave de la JsonPatchDocument<T> clase es ApplyTo, que aplica las operaciones de revisión a un objeto de destino de tipo T.

En los ejemplos siguientes se muestra cómo usar el ApplyTo método para aplicar un documento de revisión JSON a un objeto .

Ejemplo: Aplicación de un JsonPatchDocument

En el ejemplo siguiente se muestra:

  1. Las operaciones add, replace, y remove.
  2. Operaciones sobre propiedades anidadas.
  3. Agregar un nuevo elemento a una matriz.
  4. Uso de un convertidor de enumeración de cadenas JSON en un documento de revisión JSON.
// Original object
var person = new Person {
  FirstName = "John",
  LastName = "Doe",
  Email = "johndoe@gmail.com",
  PhoneNumbers = [new() {Number = "123-456-7890", Type = PhoneNumberType.Mobile}],
  Address = new Address
  {
    Street = "123 Main St",
    City = "Anytown",
    State = "TX"
  }
};

// Raw JSON Patch document
var jsonPatch = """
[
  { "op": "replace", "path": "/FirstName", "value": "Jane" },
  { "op": "remove", "path": "/Email"},
  { "op": "add", "path": "/Address/ZipCode", "value": "90210" },
  {
    "op": "add",
    "path": "/PhoneNumbers/-",
    "value": { "Number": "987-654-3210", "Type": "Work" }
  }
]
""";

// Deserialize the JSON Patch document
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);

// Apply the JSON Patch document
patchDoc!.ApplyTo(person);

// Output updated object
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));

// Output:
// {
//   "firstName": "Jane",
//   "lastName": "Doe",
//   "address": {
//     "street": "123 Main St",
//     "city": "Anytown",
//     "state": "TX",
//     "zipCode": "90210"
//   },
//   "phoneNumbers": [
//     {
//       "number": "123-456-7890",
//       "type": "Mobile"
//     },
//     {
//       "number": "987-654-3210",
//       "type": "Work"
//     }
//   ]
// }

El método ApplyTo generalmente sigue las convenciones y opciones de System.Text.Json para procesar el JsonPatchDocument, incluido el comportamiento controlado por las siguientes opciones:

  • NumberHandling: indica si las propiedades numéricas se leen de cadenas.
  • PropertyNameCaseInsensitive: Si los nombres de las propiedades distinguen entre mayúsculas y minúsculas.

Diferencias clave entre System.Text.Json y la nueva JsonPatchDocument<T> implementación:

  • El tipo en tiempo de ejecución del objeto destino, no el tipo declarado, determina qué propiedades ApplyTo parchea.
  • System.Text.Json la deserialización se basa en el tipo declarado para identificar las propiedades elegibles.

Ejemplo: Aplicación de un JsonPatchDocument con control de errores

Hay varios errores que pueden producirse al aplicar un documento de JSON Patch. Por ejemplo, es posible que el objeto de destino no tenga la propiedad especificada o que el valor especificado no sea compatible con el tipo de propiedad.

JSON Patch también admite la operación test. La test operación comprueba si un valor especificado es igual a la propiedad de destino y, si no es así, devuelve un error.

En el ejemplo siguiente se muestra cómo controlar estos errores correctamente.

Important

El objeto pasado al método ApplyTo se modifica en el lugar. Es responsabilidad del autor de la llamada descartar estos cambios si se produce un error en alguna operación.

// Original object
var person = new Person {
  FirstName = "John",
  LastName = "Doe",
  Email = "johndoe@gmail.com"
};

// Raw JSON Patch document
var jsonPatch = """
[
  { "op": "replace", "path": "/Email", "value": "janedoe@gmail.com"},
  { "op": "test", "path": "/FirstName", "value": "Jane" },
  { "op": "replace", "path": "/LastName", "value": "Smith" }
]
""";

// Deserialize the JSON Patch document
var patchDoc = JsonSerializer.Deserialize<JsonPatchDocument<Person>>(jsonPatch);

// Apply the JSON Patch document, catching any errors
Dictionary<string, string[]>? errors = null;
patchDoc!.ApplyTo(person, jsonPatchError =>
    {
        errors ??= new ();
        var key = jsonPatchError.AffectedObject.GetType().Name;
        if (!errors.ContainsKey(key))
        {
            errors.Add(key, new string[] { });
        }
        errors[key] = errors[key].Append(jsonPatchError.ErrorMessage).ToArray();
    });
if (errors != null)
{
    // Print the errors
    foreach (var error in errors)
    {
        Console.WriteLine($"Error in {error.Key}: {string.Join(", ", error.Value)}");
    }
}

// Output updated object
Console.WriteLine(JsonSerializer.Serialize(person, serializerOptions));

// Output:
// Error in Person: The current value 'John' at path 'FirstName' is not equal 
// to the test value 'Jane'.
// {
//   "firstName": "John",
//   "lastName": "Smith",              <<< Modified!
//   "email": "janedoe@gmail.com",     <<< Modified!
//   "phoneNumbers": []
// }

Mitigación de riesgos de seguridad

Al usar el Microsoft.AspNetCore.JsonPatch.SystemTextJson paquete, es fundamental comprender y mitigar los posibles riesgos de seguridad. En las secciones siguientes se describen los riesgos de seguridad identificados asociados a la revisión JSON y se proporcionan mitigaciones recomendadas para garantizar el uso seguro del paquete.

Important

Esta no es una lista exhaustiva de amenazas. Los desarrolladores de aplicaciones deben llevar a cabo sus propias revisiones del modelo de amenazas para determinar una lista completa específica de la aplicación y elaborar mitigaciones adecuadas según sea necesario. Por ejemplo, las aplicaciones que exponen colecciones a operaciones de revisión deben tener en cuenta la posibilidad de ataques de complejidad algorítmica si esas operaciones insertan o quitan elementos al principio de la colección.

Al ejecutar modelos de amenazas completos para sus propias aplicaciones y abordar las amenazas identificadas, mientras siguen las mitigaciones recomendadas a continuación, los consumidores de estos paquetes pueden integrar la funcionalidad JSON Patch en sus aplicaciones mientras minimizan los riesgos de seguridad.

Los consumidores de estos paquetes pueden integrar la funcionalidad JSON Patch en sus aplicaciones, mientras minimizan los riesgos de seguridad, entre los que se incluyen:

  • Ejecute modelos de amenazas completos para sus propias aplicaciones.
  • Abordar las amenazas identificadas.
  • Siga las mitigaciones recomendadas en las secciones siguientes.
Denegación de servicio (DoS) a través de la amplificación de memoria
  • Escenario: un cliente malintencionado envía una copy operación que duplica gráficos de objetos grandes varias veces, lo que conduce a un consumo excesivo de memoria.
  • Impacto: posibles condiciones deOf-Memory (OOM), lo que provoca interrupciones del servicio.
  • Mitigation:
    • Valide los documentos de parche JSON entrantes en cuanto a tamaño y estructura antes de llamar a ApplyTo.
    • La validación debe ser específica de la aplicación, pero una validación de ejemplo puede ser similar a la siguiente:
public void Validate(JsonPatchDocument<T> patch)
{
    // This is just an example. It's up to the developer to make sure that
    // this case is handled properly, based on the app's requirements.
    if (patch.Operations.Where(op=>op.OperationType == OperationType.Copy).Count()
        > MaxCopyOperationsCount)
    {
        throw new InvalidOperationException();
    }
}
Subversión de la lógica empresarial
  • Escenario: las operaciones de parcheo pueden manipular campos con restricciones implícitas (por ejemplo, marcas internas, identificadores o campos calculados), violando restricciones comerciales.
  • Impacto: problemas de integridad de datos y comportamiento de la aplicación no deseado.
  • Mitigation:
    • Use objetos POCO con propiedades definidas explícitamente que sean seguras para modificar.
    • Evite exponer propiedades confidenciales o críticas para la seguridad en el objeto de destino.
    • Si no se usa ningún objeto POCO, valide el objeto parcheado después de aplicar las operaciones para asegurarse de que no se infringen las reglas de negocio y los invariantes.
Autenticación y autorización
  • Escenario: clientes no autenticados o no autorizados envían solicitudes maliciosas de parche JSON.
  • Impacto: acceso no autorizado para modificar datos confidenciales o interrumpir el comportamiento de la aplicación.
  • Mitigation:
    • Proteja los puntos de conexión que aceptan solicitudes de revisión JSON con mecanismos de autenticación y autorización adecuados.
    • Restrinja el acceso a clientes o usuarios de confianza con los permisos adecuados.

Detección de si la dirección URL es local mediante RedirectHttpResult.IsLocalUrl

Use el nuevo método auxiliar de RedirectHttpResult.IsLocalUrl(url) para detectar si una dirección URL es local. Una dirección URL se considera local si se cumple lo siguiente:

Las direcciones URL que usan rutas de acceso virtuales"~/" también son locales.

IsLocalUrl es útil para validar las direcciones URL antes de redirigirlas a ellas para evitar ataques de redirección abierta.

if (RedirectHttpResult.IsLocalUrl(url))
{
    return Results.LocalRedirect(url);
}

¡Gracias @martincostello por esta contribución!

Cambios críticos

Use los artículos de Cambios importantes en .NET para buscar cambios importantes que se pueden aplicar al actualizar una aplicación a una versión más reciente de .NET.