Condividi tramite


Novità di ASP.NET Core 9.0

Questo articolo illustra le modifiche più significative in ASP.NET Core 9.0 con collegamenti alla documentazione pertinente.

Ottimizzazione della distribuzione di asset statici

MapStaticAssets le convenzioni degli endpoint di routing sono una nuova funzionalità che ottimizza la distribuzione di asset statici nelle app ASP.NET Core.

Per informazioni sulla distribuzione di asset statici per Blazor le app, vedere Blazor di base.

Seguendo le procedure consigliate di produzione per gestire gli asset statici, è necessaria una notevole quantità di lavoro e competenze tecniche. Senza ottimizzazioni come compressione, memorizzazione nella cache e impronte digitali:

  • Il browser deve effettuare richieste aggiuntive in ogni caricamento della pagina.
  • Più byte del necessario vengono trasferiti attraverso la rete.
  • A volte le versioni non aggiornate dei file vengono servite ai client.

La creazione di app Web con prestazioni elevate richiede l'ottimizzazione della distribuzione degli asset nel browser. Le possibili ottimizzazioni includono:

  • Servire un determinato asset una volta fino a quando il file non cambia o il browser cancella la cache. Impostare l'intestazione ETag .
  • Impedire al browser di usare asset obsoleti o non aggiornati dopo l'aggiornamento di un'app. Impostare l'intestazione Ultima modifica .
  • Configurare le intestazioni di memorizzazione nella cache appropriate.
  • Usare il middleware di memorizzazione nella cache.
  • Gestire le versioni compresse degli asset, quando possibile.
  • Usare una rete CDN per gestire gli asset più vicini all'utente.
  • Ridurre al minimo le dimensioni degli asset serviti al browser. Questa ottimizzazione non include la minificazione.

MapStaticAssets è una nuova funzionalità che ottimizza la distribuzione di asset statici in un'app. È progettato per funzionare con tutti i framework dell'interfaccia utente, tra cui Blazor, Razor Pages e MVC. In genere è un'eliminazione sostitutiva di UseStaticFiles:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();

app.UseRouting();

app.UseAuthorization();

+app.MapStaticAssets();
-app.UseStaticFiles();
app.MapRazorPages();

app.Run();

MapStaticAssets opera combinando processi di compilazione e di pubblicazione per raccogliere informazioni su tutte le risorse statiche in un'app. Queste informazioni vengono quindi utilizzate dalla libreria di runtime per gestire in modo efficiente questi file nel browser.

MapStaticAssets può sostituire UseStaticFiles nella maggior parte delle situazioni, tuttavia, è ottimizzato per gestire gli asset di cui l'app ha conoscenza in fase di compilazione e pubblicazione. Se l'app serve asset da altre posizioni, ad esempio risorse disco o incorporate, UseStaticFiles deve essere usata.

MapStaticAssets offre i vantaggi seguenti non trovati con UseStaticFiles:

  • Compressione del tempo di compilazione per tutti gli asset nell'app:
    • gzip durante lo sviluppo e gzip + brotli durante la pubblicazione.
    • Tutti gli asset vengono compressi con l'obiettivo di ridurre al minimo le dimensioni degli asset.
  • Basato sul ETagscontenuto: Etags per ogni risorsa è la stringa con codifica Base64 dell'hash SHA-256 del contenuto. In questo modo, il browser scarica nuovamente un file solo se il contenuto è stato modificato.

La tabella seguente mostra le dimensioni originali e compresse dei file e JS CSS nel modello Pages predefinito Razor :

file Originale Compresso % riduzione
bootstrap.min.css 163 17.5 89.26%
jquery.js 89.6 28 68.75%
bootstrap.min.js 78,5 20 74.52%
Totali 331.1 65.5 80.20%

La tabella seguente illustra le dimensioni originali e compresse usando la libreriaBlazorutente Fluent:

file Originale Compresso % riduzione
fluente.js 384 73 80.99%
fluent.css 94 11 88.30%
Totali 478 84 82.43%

Per un totale di 478 KB non compressi a 84 KB compressi.

La tabella seguente illustra le dimensioni originali e compresse usando la libreria dei componenti MudBlazorBlazor :

file Originale Compresso Riduzione
MudBlazor.min.css 541 37.5 93.07%
MudBlazor.min.js 47.4 9.2 80.59%
Totali 588.4 46,7 92.07%

L'ottimizzazione viene eseguita automaticamente quando si usa MapStaticAssets. Quando una libreria viene aggiunta o aggiornata, ad esempio con un nuovo codice JavaScript o CSS, gli asset vengono ottimizzati come parte della compilazione. L'ottimizzazione è particolarmente utile per gli ambienti mobili che possono avere una larghezza di banda inferiore o connessioni inaffidabili.

Per altre informazioni sulle nuove funzionalità di recapito dei file, vedere le risorse seguenti:

Abilitazione della compressione dinamica nel server rispetto all'uso MapStaticAssets

MapStaticAssets presenta i vantaggi seguenti rispetto alla compressione dinamica nel server:

  • È più semplice perché non esiste una configurazione specifica del server.
  • È più efficiente perché gli asset vengono compressi in fase di compilazione.
  • Consente allo sviluppatore di dedicare più tempo durante il processo di compilazione per garantire che le risorse siano le dimensioni minime.

Si consideri la tabella seguente che confronta la compressione MudBlazor con la compressione dinamica IIS e MapStaticAssets:

IIS gzip MapStaticAssets MapStaticAssets riduzione
≅ 90 37.5 59%

Blazor

In questa sezione vengono descritte le nuove funzionalità per Blazor.

.NET MAUI Blazor Hybrid e modello di soluzione app Web

Un nuovo modello di soluzione semplifica la creazione .NET MAUI di app client native e Blazor Web che condividono la stessa interfaccia utente. Questo modello illustra come creare app client che ottimizzano il riutilizzo del codice e come destinazione Android, iOS, Mac, Windows e Web.

Le funzionalità principali di questo modello includono:

  • Possibilità di scegliere una Blazor modalità di rendering interattiva per l'app Web.
  • Creazione automatica dei progetti appropriati, inclusi un Blazor Web App (rendering interattivo globale) e un'app .NET MAUIBlazor Hybrid .
  • I progetti creati usano una libreria di classi condivisa Razor (RCL) per gestire i componenti dell'interfaccia Razor utente.
  • Il codice di esempio è incluso che illustra come usare l'inserimento delle dipendenze per fornire implementazioni di interfaccia diverse per l'app Blazor Hybrid e .Blazor Web App

Per iniziare, installare .NET 9 SDK e installare il .NET MAUI carico di lavoro, che contiene il modello:

dotnet workload install maui

Creare una soluzione dal modello di progetto in una shell dei comandi usando il comando seguente:

dotnet new maui-blazor-web

Il modello è disponibile anche in Visual Studio.

Nota

Attualmente, si verifica un'eccezione se Blazor le modalità di rendering vengono definite a livello di pagina/componente. Per altre informazioni, vedere BlazorWebView richiede un modo per abilitare l'override di ResolveComponentForRenderMode (dotnet/aspnetcore #51235).

Per altre informazioni, vedere Creare un'app .NET MAUIBlazor Hybrid con un oggetto Blazor Web App.

Rilevare la posizione di rendering, l'interattività e la modalità di rendering assegnata in fase di esecuzione

È stata introdotta una nuova API progettata per semplificare il processo di esecuzione di query degli stati dei componenti in fase di esecuzione. Questa API offre le funzionalità seguenti:

  • Determinare il percorso di esecuzione corrente del componente: può essere utile per il debug e l'ottimizzazione delle prestazioni dei componenti.
  • Controllare se il componente è in esecuzione in un ambiente interattivo: può essere utile per i componenti con comportamenti diversi in base all'interattività del proprio ambiente.
  • Recuperare la modalità di rendering assegnata per il componente: comprendere la modalità di rendering può essere utile per ottimizzare il processo di rendering e migliorare le prestazioni complessive di un componente.

Per altre informazioni, vedere Blazor di rendering core.

Esperienza di riconnessione lato server migliorata:

Sono stati apportati i miglioramenti seguenti all'esperienza di riconnessione lato server predefinita:

  • Quando l'utente torna a un'app con un circuito disconnesso, la riconnessione viene tentata immediatamente anziché attendere la durata dell'intervallo di riconnessione successivo. In questo modo si migliora l'esperienza utente quando si passa a un'app in una scheda del browser che non è più in stato di sospensione.

  • Quando un tentativo di riconnessione raggiunge il server, ma il server ha già rilasciato il circuito, viene eseguito automaticamente un aggiornamento della pagina. Ciò impedisce all'utente di dover aggiornare manualmente la pagina se è probabile che si verifichi una riconnessione corretta.

  • La riconnessione usa una strategia di backoff calcolata. Per impostazione predefinita, i primi tentativi di riconnessione si verificano in rapida successione senza un intervallo di tentativi prima che vengano introdotti ritardi calcolati tra i tentativi. È possibile personalizzare il comportamento dell'intervallo di ripetizione dei tentativi specificando una funzione per calcolare l'intervallo di ripetizione dei tentativi, come illustrato nell'esempio di backoff esponenziale seguente:

    Blazor.start({
      circuit: {
        reconnectionOptions: {
          retryIntervalMilliseconds: (previousAttempts, maxRetries) => 
            previousAttempts >= maxRetries ? null : previousAttempts * 1000
        },
      },
    });
    
  • Lo stile dell'interfaccia utente di riconnessione predefinita è stato modernizzato.

Per altre informazioni, vedere Blazor di baseSignalR.

Serializzazione semplificata dello stato di autenticazione per Blazor Web Apps

Le nuove API semplificano l'aggiunta dell'autenticazione a un oggetto esistente Blazor Web App. Quando si crea un nuovo Blazor Web App con l'autenticazione usando singoli account e si abilita l'interattività basata su WebAssembly, il progetto include un oggetto personalizzato AuthenticationStateProvider nei progetti server e client.

Questi provider passano allo stato di autenticazione dell'utente nel browser. L'autenticazione nel server anziché nel client consente all'app di accedere allo stato di autenticazione durante la pre-esecuzione e prima dell'inizializzazione del runtime WebAssembly .NET.

Le implementazioni personalizzate AuthenticationStateProvider usano il servizio Stato componente persistente (PersistentComponentState) per serializzare lo stato di autenticazione in commenti HTML e leggerlo da WebAssembly per creare una nuova AuthenticationState istanza.

Ciò funziona bene se si è iniziato dal Blazor Web App modello di progetto e si è selezionata l'opzione Account singoli , ma è molto codice da implementare o copiare se si sta provando ad aggiungere l'autenticazione a un progetto esistente. Sono ora disponibili API, che fanno ora parte del Blazor Web App modello di progetto, che possono essere chiamate nei progetti server e client per aggiungere questa funzionalità:

Per impostazione predefinita, l'API serializza solo il nome lato server e le attestazioni del ruolo per l'accesso nel browser. È possibile passare un'opzione a per AddAuthenticationStateSerialization includere tutte le attestazioni.

Per altre informazioni, vedere le sezioni seguenti di Blazor di base:

Aggiungere pagine di rendering lato server statico (SSR) a un oggetto interattivo a livello globale Blazor Web App

Con il rilascio di .NET 9, è ora più semplice aggiungere pagine SSR statiche alle app che adottano interattività globale.

Questo approccio è utile solo quando l'app ha pagine specifiche che non possono funzionare con il rendering interattivo server o WebAssembly. Ad esempio, adottare questo approccio per le pagine che dipendono dalla lettura/scrittura di cookie HTTP e possono funzionare solo in un ciclo di richiesta/risposta invece del rendering interattivo. Per le pagine che funzionano con il rendering interattivo, non è consigliabile forzarle a usare il rendering statico di SSR, perché è meno efficiente e meno reattivo per l'utente finale.

Contrassegnare qualsiasi Razor pagina del componente con il nuovo [ExcludeFromInteractiveRouting] attributo assegnato con la @attributeRazor direttiva :

@attribute [ExcludeFromInteractiveRouting]

L'applicazione dell'attributo fa sì che la navigazione alla pagina esesce dal routing interattivo. Lo spostamento in ingresso è costretto a eseguire un ricaricamento a pagina intera, risolvendo invece la pagina tramite routing interattivo. Il ricaricamento a pagina intera forza il componente radice di primo livello, in genere il App componente (App.razor), a eseguire il rerender dal server, consentendo all'app di passare a una diversa modalità di rendering di primo livello.

Il RazorComponentsEndpointHttpContextExtensions.AcceptsInteractiveRouting metodo di estensione consente al componente di rilevare se l'attributo [ExcludeFromInteractiveRouting] viene applicato alla pagina corrente.

App Nel componente usare il modello nell'esempio seguente:

  • Le pagine che non sono annotate con l'attributo predefinito per la [ExcludeFromInteractiveRouting]InteractiveServer modalità di rendering con interattività globale. È possibile sostituire InteractiveServer con InteractiveWebAssembly o InteractiveAuto per specificare una modalità di rendering globale predefinita diversa.
  • Le pagine annotate con l'attributo [ExcludeFromInteractiveRouting] adopt static SSR (PageRenderMode è assegnato null).
<!DOCTYPE html>
<html>
<head>
    ...
    <HeadOutlet @rendermode="@PageRenderMode" />
</head>
<body>
    <Routes @rendermode="@PageRenderMode" />
    ...
</body>
</html>

@code {
    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;

    private IComponentRenderMode? PageRenderMode
        => HttpContext.AcceptsInteractiveRouting() ? InteractiveServer : null;
}

Un'alternativa all'uso del metodo di estensione consiste nel RazorComponentsEndpointHttpContextExtensions.AcceptsInteractiveRouting leggere manualmente i metadati dell'endpoint usando HttpContext.GetEndpoint()?.Metadata.

Questa funzionalità è descritta nella documentazione di riferimento nelle Blazor di rendering di ASP.NET Core.

Inserimento del costruttore

Razor i componenti supportano l'inserimento del costruttore.

Nell'esempio seguente la classe parziale (code-behind) inserisce il NavigationManager servizio usando un costruttore primario:

public partial class ConstructorInjection(NavigationManager navigation)
{
    private void HandleClick()
    {
        navigation.NavigateTo("/counter");
    }
}

Per altre informazioni, vedere ASP.NET Core Blazor dependency injection.

Compressione Websocket per componenti Interactive Server

Per impostazione predefinita, i componenti Interactive Server abilitano la compressione per le connessioni WebSocket e impostano una frame-ancestorsdirettiva CSP (Content Security Policy) impostata su 'self', che consente solo l'incorporamento dell'app in un'origine <iframe> da cui viene servita l'app quando è abilitata la compressione o quando viene fornita una configurazione per il contesto WebSocket.

La compressione può essere disabilitata impostando ConfigureWebSocketOptions su null, che riduce la vulnerabilità dell'app per l'attacco , ma può comportare una riduzione delle prestazioni:

.AddInteractiveServerRenderMode(o => o.ConfigureWebSocketOptions = null)

Configurare un provider di servizi di frame-ancestors configurazione più 'none' rigoroso con il valore (virgolette singole necessarie), che consente la compressione WebSocket, ma impedisce ai browser di incorporare l'app in qualsiasi <iframe>:

.AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")

Per ulteriori informazioni, vedi le seguenti risorse:

Gestire gli eventi di composizione della tastiera in Blazor

La nuova KeyboardEventArgs.IsComposing proprietà indica se l'evento della tastiera fa parte di una sessione di composizione. Tenere traccia dello stato di composizione degli eventi della tastiera è fondamentale per la gestione dei metodi di input dei caratteri internazionali.

Aggiunta del parametro OverscanCount a QuickGrid

Il QuickGrid componente espone ora una OverscanCount proprietà che specifica il numero di righe aggiuntive di cui viene eseguito il rendering prima e dopo l'area visibile quando la virtualizzazione è abilitata.

Il valore predefinito OverscanCount è 3. L'esempio seguente aumenta a OverscanCount 4:

<QuickGrid ItemsProvider="itemsProvider" Virtualize="true" OverscanCount="4">
    ...
</QuickGrid>

InputNumberil componente supporta l'attributo type="range"

Il InputNumber<TValue> componente supporta ora l'attributotype="range" del modello e la convalida del modulo, in genere sottoposto a rendering come dispositivo di scorrimento o controllo di composizione anziché come casella di testo:

<EditForm Model="Model" OnSubmit="Submit" FormName="EngineForm">
    <div>
        <label>
            Nacelle Count (2-6): 
            <InputNumber @bind-Value="Model!.NacelleCount" max="6" min="2" 
                step="1" type="range" />
        </label>
    </div>
    <div>
        <button type="submit">Submit</button>
    </div>
</EditForm>

@code {
    [SupplyParameterFromForm]
    private EngineSpecifications? Model { get; set; }

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

    private void Submit() {}

    public class EngineSpecifications
    {
        [Required, Range(minimum: 2, maximum: 6)]
        public int NacelleCount { get; set; }
    }
}

Nuovi eventi di navigazione avanzati

Attivare i callback JavaScript prima o dopo la navigazione avanzata utilizzando nuovi listener di eventi:

  • blazor.addEventListener("enhancednavigationstart", {CALLBACK})
  • blazor.addEventListener("enhancednavigationend", {CALLBACK})

Per altre informazioni, vedere ASP.NET Core Blazor JavaScript con rendering statico lato server (SSR statico).

SignalR

In questa sezione vengono descritte le nuove funzionalità per SignalR.

Supporto dei tipi polimorfici in SignalR Hub

I metodi hub ora possono accettare una classe base anziché la classe derivata per abilitare scenari polimorfici. Il tipo di base deve essere annotato per consentire il polimorfismo.

public class MyHub : Hub
{
    public void Method(JsonPerson person)
    {
        if (person is JsonPersonExtended)
        {
        }
        else if (person is JsonPersonExtended2)
        {
        }
        else
        {
        }
    }
}

[JsonPolymorphic]
[JsonDerivedType(typeof(JsonPersonExtended), nameof(JsonPersonExtended))]
[JsonDerivedType(typeof(JsonPersonExtended2), nameof(JsonPersonExtended2))]
private class JsonPerson
{
    public string Name { get; set; }
    public Person Child { get; set; }
    public Person Parent { get; set; }
}

private class JsonPersonExtended : JsonPerson
{
    public int Age { get; set; }
}

private class JsonPersonExtended2 : JsonPerson
{
    public string Location { get; set; }
}

Attività migliorate per SignalR

SignalR ora dispone di ActivitySource sia per il server hub che per il client.

ActivitySource del server .NET SignalR

Il SignalR ActivitySource denominato Microsoft.AspNetCore.SignalR.Server genera eventi per le chiamate al metodo hub:

  • Ogni metodo è una propria attività, quindi qualsiasi elemento che genera un'attività durante la chiamata al metodo hub si trova nell'attività del metodo hub.
  • Le attività del metodo hub non hanno un elemento padre. Ciò significa che non sono raggruppati nella connessione a esecuzione SignalR prolungata.

L'esempio seguente usa il .NET Aspire dashboard e i pacchetti OpenTelemetry :

<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />

Aggiungere il codice di avvio seguente al Program.cs file:

using OpenTelemetry.Trace;
using SignalRChat.Hubs;

// Set OTEL_EXPORTER_OTLP_ENDPOINT environment variable depending on where your OTEL endpoint is.
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

builder.Services.AddOpenTelemetry()
    .WithTracing(tracing =>
    {
        if (builder.Environment.IsDevelopment())
        {
            // View all traces only in development environment.
            tracing.SetSampler(new AlwaysOnSampler());
        }

        tracing.AddAspNetCoreInstrumentation();
        tracing.AddSource("Microsoft.AspNetCore.SignalR.Server");
    });

builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddOtlpExporter());

var app = builder.Build();

Di seguito è riportato l'output di esempio del dashboard aspirare:

Elenco attività per SignalR gli eventi di chiamata al metodo hub

ActivitySource client di .NET SignalR

Il SignalR ActivitySource denominato Microsoft.AspNetCore.SignalR.Client genera eventi per un client SignalR:

  • Il client di SignalR .NET ha un ActivitySource denominato Microsoft.AspNetCore.SignalR.Client. Le chiamate all'hub ora creano un intervallo client. Si noti che altri client SignalR, ad esempio il client JavaScript, non supportano il tracciamento. Questa funzionalità verrà aggiunta a più client nelle versioni future.
  • Le chiamate hub nel client e nel server supportano la propagazione del contesto . La propagazione del contesto di traccia abilita la traccia distribuita vera. È ora possibile visualizzare il flusso delle chiamate dal client al server e di nuovo.

Ecco come vengono esaminate queste nuove attività nel dashboard .NET Aspire:

SignalR tracciamento distribuito nel dashboard di Aspire

SignalR supporta il taglio e native AOT

Continuando il percorso AOT nativo avviato in .NET 8, è stato abilitato il taglio e il supporto nativo per la compilazione in anticipo (AOT) per SignalR scenari client e server. È ora possibile sfruttare i vantaggi in termini di prestazioni dell'uso di AOT nativo nelle applicazioni che usano SignalR per le comunicazioni Web in tempo reale.

Introduzione

Installare la versione più recente di .NET 9 SDK.

Creare una soluzione dal webapiaot modello in una shell dei comandi usando il comando seguente:

dotnet new webapiaot -o SignalRChatAOTExample

Sostituire il contenuto del Program.cs file con il codice seguente SignalR :

using Microsoft.AspNetCore.SignalR;
using System.Text.Json.Serialization;

var builder = WebApplication.CreateSlimBuilder(args);

builder.Services.AddSignalR();
builder.Services.Configure<JsonHubProtocolOptions>(o =>
{
    o.PayloadSerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default);
});

var app = builder.Build();

app.MapHub<ChatHub>("/chatHub");
app.MapGet("/", () => Results.Content("""
<!DOCTYPE html>
<html>
<head>
    <title>SignalR Chat</title>
</head>
<body>
    <input id="userInput" placeholder="Enter your name" />
    <input id="messageInput" placeholder="Type a message" />
    <button onclick="sendMessage()">Send</button>
    <ul id="messages"></ul>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.7/signalr.min.js"></script>
    <script>
        const connection = new signalR.HubConnectionBuilder()
            .withUrl("/chatHub")
            .build();

        connection.on("ReceiveMessage", (user, message) => {
            const li = document.createElement("li");
            li.textContent = `${user}: ${message}`;
            document.getElementById("messages").appendChild(li);
        });

        async function sendMessage() {
            const user = document.getElementById("userInput").value;
            const message = document.getElementById("messageInput").value;
            await connection.invoke("SendMessage", user, message);
        }

        connection.start().catch(err => console.error(err));
    </script>
</body>
</html>
""", "text/html"));

app.Run();

[JsonSerializable(typeof(string))]
internal partial class AppJsonSerializerContext : JsonSerializerContext { }

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

L'esempio precedente produce un eseguibile windows nativo di 10 MB e un eseguibile Linux di 10,9 MB.

Limiti

  • Attualmente è supportato solo il protocollo JSON:
    • Come illustrato nel codice precedente, le app che usano la serializzazione JSON e Native AOT devono usare il System.Text.Json generatore di origine.
    • Questo approccio segue lo stesso approccio delle API minime.
  • Nel server i parametri del SignalR metodo Hub di tipo IAsyncEnumerable<T> e ChannelReader<T> dove T è un ValueType (struct) non sono supportati. L'uso di questi tipi comporta un'eccezione di runtime all'avvio in fase di sviluppo e nell'app pubblicata. Per altre informazioni, vedere SignalR: Uso di IAsyncEnumerable<T> e ChannelReader<T> con ValueTypes in AOT nativo (dotnet/aspnetcore #56179).
  • Gli hub fortemente tipizzato non sono supportati con AOT nativo (PublishAot). L'uso di hub fortemente tipizzato con AOT nativo genererà avvisi durante la compilazione e la pubblicazione e un'eccezione di runtime. L'uso di hub fortemente tipizzato con taglio (PublishedTrimmed) è supportato.
  • Solo Task, Task<T>, ValueTasko ValueTask<T> sono supportati per i tipi restituiti asincroni.

API minime

Questa sezione descrive le nuove funzionalità per le API minime.

Aggiunta di InternalServerError e InternalServerError<TValue> a TypedResults

La TypedResults classe è un veicolo utile per restituire risposte basate su codice di stato HTTP fortemente tipizzato da un'API minima. TypedResults include ora i metodi e i tipi factory per restituire le risposte "500 Internal Server Error" dagli endpoint. Ecco un esempio che restituisce una risposta 500:

var app = WebApplication.Create();

app.MapGet("/", () => TypedResults.InternalServerError("Something went wrong!"));

app.Run();

Chiamare ProducesProblem e ProducesValidationProblem sui gruppi di route

I ProducesProblem metodi di estensione e ProducesValidationProblem sono stati aggiornati per supportare l'uso nei gruppi di route. Questi metodi indicano che tutti gli endpoint in un gruppo di route possono restituire ProblemDetails o ValidationProblemDetails risposte ai fini dei metadati OpenAPI.

var app = WebApplication.Create();

var todos = app.MapGroup("/todos")
    .ProducesProblem();

todos.MapGet("/", () => new Todo(1, "Create sample app", false));
todos.MapPost("/", (Todo todo) => Results.Ok(todo));

app.Run();

record Todo(int Id, string Title, boolean IsCompleted);

Problem e ValidationProblem i tipi di risultati supportano la costruzione con IEnumerable<KeyValuePair<string, object?>> valori

Prima di .NET 9, la creazione di tipi di risultati Problem e ValidationProblem in API minime richiedeva l'inizializzazione delle errors proprietà e extensions con un'implementazione di IDictionary<string, object?>. In questa versione, queste API di costruzione supportano overload che usano IEnumerable<KeyValuePair<string, object?>>.

var app = WebApplication.Create();

app.MapGet("/", () =>
{
    var extensions = new List<KeyValuePair<string, object?>> { new("test", "value") };
    return TypedResults.Problem("This is an error with extensions",
                                                       extensions: extensions);
});

Grazie all'utente di GitHub joegoldman2 per questo contributo.

OpenAPI

Questa sezione descrive le nuove funzionalità per OpenAPI

Supporto predefinito per la generazione di documenti OpenAPI

La specifica OpenAPI è uno standard per descrivere le API HTTP. Lo standard consente agli sviluppatori di definire la forma delle API che possono essere collegate a generatori client, generatori di server, strumenti di test, documentazione e altro ancora. In .NET 9, ASP.NET Core offre il supporto predefinito per la generazione di documenti OpenAPI che rappresentano API minime o basate su controller tramite il pacchetto Microsoft.AspNetCore.OpenApi .

Le chiamate di codice evidenziate seguenti:

  • AddOpenApi per registrare le dipendenze necessarie nel contenitore di inserimento delle dipendenze dell'app.
  • MapOpenApi per registrare gli endpoint OpenAPI necessari nelle route dell'app.
var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();

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

app.Run();

Installare il Microsoft.AspNetCore.OpenApi pacchetto nel progetto usando il comando seguente:

dotnet add package Microsoft.AspNetCore.OpenApi

Eseguire l'app e passare a per openapi/v1.json visualizzare il documento OpenAPI generato:

Documento di OpenAPI

I documenti OpenAPI possono anche essere generati in fase di compilazione aggiungendo il Microsoft.Extensions.ApiDescription.Server pacchetto:

dotnet add package Microsoft.Extensions.ApiDescription.Server

Per modificare il percorso dei documenti OpenAPI generati, impostare il percorso di destinazione nella proprietà OpenApiDocumentsDirectory nel file di progetto dell'app:

<PropertyGroup>
  <OpenApiDocumentsDirectory>$(MSBuildProjectDirectory)</OpenApiDocumentsDirectory>
</PropertyGroup>

Eseguire dotnet build ed esaminare il file JSON generato nella directory del progetto.

Generazione di documenti OpenAPI in fase di compilazione

ASP.NET generazione di documenti OpenAPI predefinita di Core offre supporto per varie personalizzazioni e opzioni. Fornisce trasformatori di documenti, operazioni e schemi e ha la possibilità di gestire più documenti OpenAPI per la stessa applicazione.

Per altre informazioni sulle nuove funzionalità del documento OpenAPI di ASP.NET Core, vedere la nuova documentazione di Microsoft.AspNetCore.OpenApi.

Microsoft.AspNetCore.OpenApi supporta il taglio e L'AOT nativo

OpenAPI in ASP.NET Core supporta il taglio e l'AOT nativo. La procedura seguente consente di creare e pubblicare un'app OpenAPI con taglio e AOT nativo:

Creare un nuovo progetto api Web di base (AOT) di ASP.NET.

dotnet new webapiaot

Aggiungere il pacchetto Microsoft.AspNetCore.OpenAPI.

dotnet add package Microsoft.AspNetCore.OpenApi

Aggiornare Program.cs per abilitare la generazione di documenti OpenAPI.

+ builder.Services.AddOpenApi();

var app = builder.Build();

+ app.MapOpenApi();

Pubblica l'app.

dotnet publish

Autenticazione e autorizzazione

In questa sezione vengono descritte le nuove funzionalità per l'autenticazione e l'autorizzazione.

OpenIdConnectHandler aggiunge il supporto per le richieste di autorizzazione push (PAR)

Vorremmo ringraziare Joe DeCock da Duende Software per l'aggiunta di richieste di autorizzazione push (PAR) a ASP.NET Core OpenIdConnectHandler. Joe ha descritto il background e la motivazione per l'abilitazione di PAR nella sua proposta di API come indicato di seguito:

Le richieste di autorizzazione push (PAR) sono uno standard OAuth relativamente nuovo che migliora la sicurezza dei flussi OAuth e OIDC spostando i parametri di autorizzazione dal canale anteriore al canale back. Ovvero, spostare i parametri di autorizzazione dagli URL di reindirizzamento nel browser per indirizzare il computer alle chiamate HTTP del computer sul back-end.

Ciò impedisce a un cyberattacker nel browser di:

  • Visualizzazione dei parametri di autorizzazione, che potrebbero causare la perdita di informazioni personali.
  • Manomissione di tali parametri. Ad esempio, il cyberattacker potrebbe modificare l'ambito di accesso richiesto.

Il push dei parametri di autorizzazione mantiene anche gli URL delle richieste brevi. I parametri di autorizzazione possono ottenere molto tempo quando si usano funzionalità OAuth e OIDC più complesse, ad esempio richieste di autorizzazione avanzata. Gli URL che causano lunghi problemi in molti browser e infrastrutture di rete.

L'uso di PAR è incoraggiato dal gruppo di lavoro FAPI all'interno della OpenID Foundation. Ad esempio, il profilo di sicurezza FAPI2.0 richiede l'uso di PAR. Questo profilo di sicurezza viene utilizzato da molti dei gruppi che lavorano su open banking (principalmente in Europa), nell'assistenza sanitaria e in altri settori con requisiti di sicurezza elevati.

PAR è supportato da diversi identity provider, tra cui

Per .NET 9, è stato deciso di abilitare PAR per impostazione predefinita se il identity documento di individuazione del provider annuncia il supporto per PAR, perché dovrebbe fornire una sicurezza avanzata per i provider che lo supportano. Il identity documento di individuazione del provider si trova in genere in .well-known/openid-configuration. Se ciò causa problemi, è possibile disabilitare PAR tramite OpenIdConnectOptions.PushedAuthorizationBehavior come indicato di seguito:

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect("oidc", oidcOptions =>
    {
        // Other provider-specific configuration goes here.

        // The default value is PushedAuthorizationBehavior.UseIfAvailable.

        // 'OpenIdConnectOptions' does not contain a definition for 'PushedAuthorizationBehavior'
        // and no accessible extension method 'PushedAuthorizationBehavior' accepting a first argument
        // of type 'OpenIdConnectOptions' could be found
        oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable;
    });

Per assicurarsi che l'autenticazione abbia esito positivo solo se si usa PAR, usare invece PushedAuthorizationBehavior.Require . Questa modifica introduce anche un nuovo evento OnPushAuthorization in OpenIdConnectEvents che può essere usato per personalizzare la richiesta di autorizzazione push o gestirla manualmente. Per altri dettagli, vedere la proposta dell'API.

Personalizzazione dei parametri OIDC e OAuth

I gestori di autenticazione OAuth e OIDC ora hanno un'opzione AdditionalAuthorizationParameters per semplificare la personalizzazione dei parametri dei messaggi di autorizzazione che vengono in genere inclusi come parte della stringa di query di reindirizzamento. In .NET 8 e versioni precedenti, è necessario un callback personalizzato OnRedirectToIdentityProvider o un metodo sottoposto BuildChallengeUrl a override in un gestore personalizzato. Ecco un esempio di codice .NET 8:

builder.Services.AddAuthentication().AddOpenIdConnect(options =>
{
    options.Events.OnRedirectToIdentityProvider = context =>
    {
        context.ProtocolMessage.SetParameter("prompt", "login");
        context.ProtocolMessage.SetParameter("audience", "https://api.example.com");
        return Task.CompletedTask;
    };
});

L'esempio precedente può ora essere semplificato con il codice seguente:

builder.Services.AddAuthentication().AddOpenIdConnect(options =>
{
    options.AdditionalAuthorizationParameters.Add("prompt", "login");
    options.AdditionalAuthorizationParameters.Add("audience", "https://api.example.com");
});

Configurare HTTP.sys flag di autenticazione estesa

È ora possibile configurare i HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING flag e HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL HTTP.sys usando le nuove EnableKerberosCredentialCaching proprietà e CaptureCredentials nel HTTP.sys per ottimizzare la modalità di gestione delle autenticazione di WindowsAuthenticationManager. Ad esempio:

webBuilder.UseHttpSys(options =>
{
    options.Authentication.Schemes = AuthenticationSchemes.Negotiate;
    options.Authentication.EnableKerberosCredentialCaching = true;
    options.Authentication.CaptureCredentials = true;
});

Varie

Le sezioni seguenti descrivono varie nuove funzionalità.

Nuova HybridCache libreria

Importante

HybridCache è attualmente ancora in anteprima, ma verrà completamente rilasciato dopo .NET 9.0 in una versione secondaria futura delle estensioni .NET.

L'API HybridCache consente di colmare alcune lacune nelle API e IDistributedCache esistentiIMemoryCache. Aggiunge anche nuove funzionalità, ad esempio:

  • Protezione "stampede" per impedire il recupero parallelo dello stesso lavoro.
  • Serializzazione configurabile.

HybridCacheè progettato per essere una sostituzione dell'eliminazione per l'uso e IDistributedCache esistente IMemoryCache e offre una semplice API per l'aggiunta di nuovo codice di memorizzazione nella cache. Fornisce un'API unificata per la memorizzazione nella cache in-process e out-of-process.

Per vedere come l'API HybridCache è semplificata, confrontarla con il codice che usa IDistributedCache. Di seguito è riportato un esempio dell'aspetto dell'uso IDistributedCache :

public class SomeService(IDistributedCache cache)
{
    public async Task<SomeInformation> GetSomeInformationAsync
        (string name, int id, CancellationToken token = default)
    {
        var key = $"someinfo:{name}:{id}"; // Unique key for this combination.
        var bytes = await cache.GetAsync(key, token); // Try to get from cache.
        SomeInformation info;
        if (bytes is null)
        {
            // Cache miss; get the data from the real source.
            info = await SomeExpensiveOperationAsync(name, id, token);

            // Serialize and cache it.
            bytes = SomeSerializer.Serialize(info);
            await cache.SetAsync(key, bytes, token);
        }
        else
        {
            // Cache hit; deserialize it.
            info = SomeSerializer.Deserialize<SomeInformation>(bytes);
        }
        return info;
    }

    // This is the work we're trying to cache.
    private async Task<SomeInformation> SomeExpensiveOperationAsync(string name, int id,
        CancellationToken token = default)
    { /* ... */ }
}

Questo è un sacco di lavoro per ottenere il giusto ogni volta, inclusi elementi come la serializzazione. Inoltre, nello scenario di mancata memorizzazione nella cache, è possibile terminare con più thread simultanei, ottenere tutti un mancato riscontro nella cache, recuperare tutti i dati sottostanti, serializzarli e tutti gli invii tali dati alla cache.

Per semplificare e migliorare questo codice con HybridCache, è prima necessario aggiungere la nuova libreria Microsoft.Extensions.Caching.Hybrid:

<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0" />

Registrare il HybridCache servizio, come si vuole registrare un'implementazione IDistributedCache :

builder.Services.AddHybridCache(); // Not shown: optional configuration API.

È ora possibile eseguire l'offload della maggior parte dei problemi di memorizzazione nella cache in HybridCache:

public class SomeService(HybridCache cache)
{
    public async Task<SomeInformation> GetSomeInformationAsync
        (string name, int id, CancellationToken token = default)
    {
        return await cache.GetOrCreateAsync(
            $"someinfo:{name}:{id}", // Unique key for this combination.
            async cancel => await SomeExpensiveOperationAsync(name, id, cancel),
            token: token
        );
    }
}

Viene fornito un'implementazione concreta della HybridCache classe astratta tramite l'inserimento delle dipendenze, ma è previsto che gli sviluppatori possano fornire implementazioni personalizzate dell'API. L'implementazione HybridCache gestisce tutti gli elementi correlati alla memorizzazione nella cache, inclusa la gestione simultanea delle operazioni. Il cancel token qui rappresenta l'annullamento combinato di tutti i chiamanti simultanei, non solo l'annullamento del chiamante che è possibile vedere ( ovvero token).

Gli scenari con velocità effettiva elevata possono essere ulteriormente ottimizzati usando il TState modello, per evitare un sovraccarico dovuto alle variabili acquisite e ai callback per istanza:

public class SomeService(HybridCache cache)
{
    public async Task<SomeInformation> GetSomeInformationAsync(string name, int id, CancellationToken token = default)
    {
        return await cache.GetOrCreateAsync(
            $"someinfo:{name}:{id}", // unique key for this combination
            (name, id), // all of the state we need for the final call, if needed
            static async (state, token) =>
                await SomeExpensiveOperationAsync(state.name, state.id, token),
            token: token
        );
    }
}

HybridCache usa l'implementazione configurata IDistributedCache , se presente, per la memorizzazione nella cache out-of-process secondaria, ad esempio usando Redis. Ma anche senza , IDistributedCacheil HybridCache servizio fornirà comunque la protezione nella cache in-process e "stampede".

Nota sul riutilizzo degli oggetti

Nel codice esistente tipico che usa IDistributedCache, ogni recupero di un oggetto dalla cache comporta la deserializzazione. Questo comportamento significa che ogni chiamante simultaneo ottiene un'istanza separata dell'oggetto, che non può interagire con altre istanze. Il risultato è thread safety, poiché non esiste alcun rischio di modifiche simultanee alla stessa istanza dell'oggetto.

Poiché un sacco di HybridCache utilizzo verrà adattato dal codice esistente IDistributedCache , HybridCache mantiene questo comportamento per impostazione predefinita per evitare di introdurre bug di concorrenza. Tuttavia, un determinato caso d'uso è intrinsecamente thread-safe:

  • Se i tipi memorizzati nella cache non sono modificabili.
  • Se il codice non li modifica.

In questi casi, informare HybridCache che è sicuro riutilizzare le istanze in base a:

  • Contrassegnare il tipo come sealed. La sealed parola chiave in C# indica che la classe non può essere ereditata.
  • Applicazione dell'attributo [ImmutableObject(true)] . L'attributo [ImmutableObject(true)] indica che lo stato dell'oggetto non può essere modificato dopo la creazione.

Riutilizzando istanze, HybridCache è possibile ridurre il sovraccarico delle allocazioni di CPU e oggetti associate alla deserializzazione per chiamata. Ciò può comportare miglioramenti delle prestazioni negli scenari in cui gli oggetti memorizzati nella cache sono di grandi dimensioni o a cui si accede di frequente.

Altre HybridCache funzionalità

Come IDistributedCache, HybridCache supporta la rimozione tramite chiave con un RemoveKeyAsync metodo .

HybridCache fornisce anche API facoltative per IDistributedCache le implementazioni, per evitare byte[] allocazioni. Questa funzionalità viene implementata dalle versioni di anteprima dei Microsoft.Extensions.Caching.StackExchangeRedis pacchetti e Microsoft.Extensions.Caching.SqlServer .

La serializzazione viene configurata come parte della registrazione del servizio, con supporto per serializzatori specifici del tipo e generalizzati tramite i WithSerializer metodi e .WithSerializerFactory concatenati dalla AddHybridCache chiamata. Per impostazione predefinita, la libreria gestisce string e byte[] internamente e usa System.Text.Json per tutto il resto, ma è possibile usare protobuf, xml o qualsiasi altro elemento.

HybridCache supporta runtime .NET meno recenti, fino a .NET Framework 4.7.2 e .NET Standard 2.0.

Per altre informazioni su HybridCache, vedere Libreria HybridCache in ASP.NET Core

Miglioramenti alla pagina delle eccezioni per gli sviluppatori

La pagina delle eccezioni dello sviluppatore ASP.NET Core viene visualizzata quando un'app genera un'eccezione non gestita durante lo sviluppo. La pagina delle eccezioni per sviluppatori fornisce informazioni dettagliate sull'eccezione e sulla richiesta.

Anteprima 3 aggiunta dei metadati dell'endpoint alla pagina delle eccezioni dello sviluppatore. ASP.NET Core usa i metadati dell'endpoint per controllare il comportamento dell'endpoint, ad esempio routing, memorizzazione nella cache delle risposte, limitazione della frequenza, generazione OpenAPI e altro ancora. L'immagine seguente mostra le nuove informazioni sui metadati nella Routing sezione della pagina delle eccezioni per sviluppatori:

Nuove informazioni sui metadati nella pagina delle eccezioni per sviluppatori

Durante il test della pagina delle eccezioni dello sviluppatore, sono stati identificati piccoli miglioramenti della qualità della vita. Sono stati spediti in Anteprima 4:

  • Migliore disposizione del testo. I cookie lunghi, i valori delle stringhe di query e i nomi dei metodi non aggiungono più barre di scorrimento orizzontale del browser.
  • Testo più grande che si trova nei disegni moderni.
  • Dimensioni di tabella più coerenti.

L'immagine animata seguente mostra la nuova pagina delle eccezioni per sviluppatori:

Pagina delle eccezioni per i nuovi sviluppatori

Miglioramenti del debug del dizionario

La visualizzazione del debug di dizionari e altre raccolte chiave-valore ha un layout migliorato. La chiave viene visualizzata nella colonna chiave del debugger invece di essere concatenata con il valore . Le immagini seguenti mostrano la visualizzazione precedente e nuova di un dizionario nel debugger.

Prima:

Esperienza precedente del debugger

Dopo:

Nuova esperienza del debugger

ASP.NET Core include molte raccolte chiave-valore. Questa esperienza di debug migliorata si applica a:

  • Intestazioni HTTP
  • Stringhe di query
  • Form
  • Cookie
  • Visualizzare i dati
  • Dati route
  • Funzionalità

Correzione di 503 durante il riciclo dell'app in IIS

Per impostazione predefinita, è ora presente un ritardo di 1 secondo tra quando IIS riceve una notifica di riciclo o arresto e quando ANCM indica al server gestito di avviare l'arresto. Il ritardo è configurabile tramite la ANCM_shutdownDelay variabile di ambiente o impostando l'impostazione del shutdownDelay gestore. Entrambi i valori sono in millisecondi. Il ritardo consiste principalmente nel ridurre la probabilità di una gara in cui:

  • IIS non ha avviato l'accodamento delle richieste per passare alla nuova app.
  • ANCM inizia a rifiutare nuove richieste che vengono inserite nell'app precedente.

I computer o i computer più lenti con un utilizzo più elevato della CPU possono voler regolare questo valore per ridurre la probabilità di 503.

Esempio di impostazione shutdownDelay:

<aspNetCore processPath="dotnet" arguments="myapp.dll" stdoutLogEnabled="false" stdoutLogFile=".logsstdout">
  <handlerSettings>
    <!-- Milliseconds to delay shutdown by.
    this doesn't mean incoming requests will be delayed by this amount,
    but the old app instance will start shutting down after this timeout occurs -->
    <handlerSetting name="shutdownDelay" value="5000" />
  </handlerSettings>
</aspNetCore>

La correzione si trova nel modulo ANCM installato a livello globale proveniente dal bundle di hosting.

ASP0026: Analizzatore da avvisare quando [Authorize] viene sottoposto a override da [AllowAnonymous] da "lontano"

Sembra intuitivo che un [Authorize] attributo posizionato "più vicino" a un'azione MVC rispetto a un [AllowAnonymous] attributo eseguirà l'override dell'attributo e forza l'autorizzazione [AllowAnonymous] . Tuttavia, questo non è necessariamente il caso. Ciò che conta è l'ordine relativo degli attributi.

Il codice seguente mostra esempi in cui un attributo più vicino [Authorize] viene sottoposto a override da un [AllowAnonymous] attributo più lontano.

[AllowAnonymous]
public class MyController
{
    [Authorize] // Overridden by the [AllowAnonymous] attribute on the class
    public IActionResult Private() => null;
}
[AllowAnonymous]
public class MyControllerAnon : ControllerBase
{
}

[Authorize] // Overridden by the [AllowAnonymous] attribute on MyControllerAnon
public class MyControllerInherited : MyControllerAnon
{
}

public class MyControllerInherited2 : MyControllerAnon
{
    [Authorize] // Overridden by the [AllowAnonymous] attribute on MyControllerAnon
    public IActionResult Private() => null;
}
[AllowAnonymous]
[Authorize] // Overridden by the preceding [AllowAnonymous]
public class MyControllerMultiple : ControllerBase
{
}

In .NET 9 Preview 6 è stato introdotto un analizzatore che evidenzia istanze come queste in cui un attributo più vicino [Authorize] viene sottoposto a override da un [AllowAnonymous] attributo che è più lontano da un'azione MVC. L'avviso punta all'attributo sottoposto [Authorize] a override con il messaggio seguente:

ASP0026 [Authorize] overridden by [AllowAnonymous] from farther away

L'azione corretta da eseguire se viene visualizzato questo avviso dipende dall'intenzione alla base degli attributi. L'attributo più lontano [AllowAnonymous] deve essere rimosso se non espone involontariamente l'endpoint agli utenti anonimi. Se l'attributo è stato progettato per eseguire l'override [AllowAnonymous] di un attributo più vicino [Authorize] , è possibile ripetere l'attributo [AllowAnonymous] dopo l'attributo [Authorize] per chiarire la finalità.

[AllowAnonymous]
public class MyController
{
    // This produces no warning because the second, "closer" [AllowAnonymous]
    // clarifies that [Authorize] is intentionally overridden.
    // Specifying AuthenticationSchemes can still be useful
    // for endpoints that allow but don't require authenticated users.
    [Authorize(AuthenticationSchemes = "Cookies")]
    [AllowAnonymous]
    public IActionResult Privacy() => null;
}

Metriche di connessione migliorate Kestrel

Sono stati apportati miglioramenti significativi alle metriche di connessione includendo i metadati relativi al Kestrelmotivo per cui una connessione non è riuscita. La kestrel.connection.duration metrica include ora il motivo di chiusura della connessione nell'attributo error.type .

Di seguito è riportato un piccolo esempio dei error.type valori:

  • tls_handshake_failed - La connessione richiede TLS e l'handshake TLS non è riuscito.
  • connection_reset - La connessione è stata chiusa in modo imprevisto dal client mentre le richieste erano in corso.
  • request_headers_timeout - Kestrel ha chiuso la connessione perché non ha ricevuto intestazioni di richiesta nel tempo.
  • max_request_body_size_exceeded - Kestrel ha chiuso la connessione perché i dati caricati hanno superato le dimensioni massime.

In precedenza, la diagnosi dei Kestrel problemi di connessione richiedeva a un server di registrare la registrazione dettagliata e di basso livello. Tuttavia, i log possono essere costosi da generare e archiviare e può essere difficile trovare le informazioni corrette tra il rumore.

Le metriche sono un'alternativa molto più economica che può essere lasciata in un ambiente di produzione con un impatto minimo. Le metriche raccolte possono guidare dashboard e avvisi. Una volta identificato un problema a livello generale con le metriche, è possibile iniziare ulteriori indagini usando la registrazione e altri strumenti.

Si prevede che le metriche di connessione migliorate siano utili in molti scenari:

  • Analisi dei problemi di prestazioni causati da brevi durate delle connessioni.
  • Osservare gli attacchi esterni in corso su che influiscono sulle Kestrel prestazioni e sulla stabilità.
  • La registrazione di tentativi di attacchi esterni sulla KestrelKestrelprotezione avanzata predefinita ha impedito la protezione avanzata.

Per altre informazioni, vedere ASP.NET Metriche principali.

Personalizzare Kestrel gli endpoint named pipe

KestrelIl supporto della named pipe è stato migliorato con opzioni di personalizzazione avanzate. Il nuovo CreateNamedPipeServerStream metodo nelle opzioni named pipe consente di personalizzare le pipe per endpoint.

Un esempio di dove questo è utile è un'app Kestrel che richiede due endpoint di pipe con sicurezza di accesso diversa. L'opzione CreateNamedPipeServerStream può essere usata per creare pipe con impostazioni di sicurezza personalizzate, a seconda del nome della pipe.

var builder = WebApplication.CreateBuilder();

builder.WebHost.ConfigureKestrel(options =>
{
    options.ListenNamedPipe("pipe1");
    options.ListenNamedPipe("pipe2");
});

builder.WebHost.UseNamedPipes(options =>
{
    options.CreateNamedPipeServerStream = (context) =>
    {
        var pipeSecurity = CreatePipeSecurity(context.NamedPipeEndpoint.PipeName);

        return NamedPipeServerStreamAcl.Create(context.NamedPipeEndPoint.PipeName, PipeDirection.InOut,
            NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte,
            context.PipeOptions, inBufferSize: 0, outBufferSize: 0, pipeSecurity);
    };
});

ExceptionHandlerMiddleware opzione per scegliere il codice di stato in base al tipo di eccezione

Una nuova opzione durante la configurazione di consente agli sviluppatori di app di scegliere il codice di stato da restituire quando si verifica un'eccezione durante la ExceptionHandlerMiddleware gestione delle richieste. La nuova opzione modifica il codice di stato impostato nella ProblemDetails risposta da ExceptionHandlerMiddleware.

app.UseExceptionHandler(new ExceptionHandlerOptions
{
    StatusCodeSelector = ex => ex is TimeoutException
        ? StatusCodes.Status503ServiceUnavailable
        : StatusCodes.Status500InternalServerError,
});

Rifiutare esplicitamente le metriche HTTP in determinati endpoint e richieste

.NET 9 introduce la possibilità di rifiutare esplicitamente le metriche HTTP per endpoint e richieste specifici. Rifiutare esplicitamente le metriche di registrazione è utile per gli endpoint spesso chiamati da sistemi automatizzati, ad esempio i controlli di integrità. La registrazione delle metriche per queste richieste è in genere non necessaria.

Le richieste HTTP a un endpoint possono essere escluse dalle metriche aggiungendo metadati. Uno dei seguenti:

  • Aggiungere l'attributo [DisableHttpMetrics] al controller api Web, SignalR all'hub o al servizio gRPC.
  • Chiamare DisableHttpMetrics quando si esegue il mapping degli endpoint all'avvio dell'app:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();

var app = builder.Build();
app.MapHealthChecks("/healthz").DisableHttpMetrics();
app.Run();

La MetricsDisabled proprietà è stata aggiunta a IHttpMetricsTagsFeature per:

  • Scenari avanzati in cui una richiesta non esegue il mapping a un endpoint.
  • Disabilitazione dinamica della raccolta di metriche per richieste HTTP specifiche.
// Middleware that conditionally opts-out HTTP requests.
app.Use(async (context, next) =>
{
    var metricsFeature = context.Features.Get<IHttpMetricsTagsFeature>();
    if (metricsFeature != null &&
        context.Request.Headers.ContainsKey("x-disable-metrics"))
    {
        metricsFeature.MetricsDisabled = true;
    }

    await next(context);
});

Supporto per la protezione dei dati per l'eliminazione di chiavi

Prima di .NET 9, le chiavi di protezione dei dati non erano eliminabili per impostazione predefinita, per evitare la perdita di dati. L'eliminazione di una chiave rende irretrievabili i dati protetti. Data la loro piccola dimensione, l'accumulo di queste chiavi ha in genere rappresentato un impatto minimo. Tuttavia, per supportare servizi estremamente a esecuzione prolungata, è stata introdotta l'opzione per eliminare le chiavi. In genere, è consigliabile eliminare solo le chiavi precedenti. Eliminare chiavi solo quando è possibile accettare il rischio di perdita di dati in cambio di risparmi di archiviazione. È consigliabile non eliminare le chiavi di protezione dei dati.

using Microsoft.AspNetCore.DataProtection.KeyManagement;

var services = new ServiceCollection();
services.AddDataProtection();

var serviceProvider = services.BuildServiceProvider();

var keyManager = serviceProvider.GetService<IKeyManager>();

if (keyManager is IDeletableKeyManager deletableKeyManager)
{
    var utcNow = DateTimeOffset.UtcNow;
    var yearAgo = utcNow.AddYears(-1);

    if (!deletableKeyManager.DeleteKeys(key => key.ExpirationDate < yearAgo))
    {
        Console.WriteLine("Failed to delete keys.");
    }
    else
    {
        Console.WriteLine("Old keys deleted successfully.");
    }
}
else
{
    Console.WriteLine("Key manager does not support deletion.");
}

Il middleware supporta l'inserimento delle dipendenze con chiave

Il middleware supporta ora l'inserimento delle dipendenze con chiave sia nel costruttore che nel Invoke/InvokeAsync metodo :

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddKeyedSingleton<MySingletonClass>("test");
builder.Services.AddKeyedScoped<MyScopedClass>("test2");

var app = builder.Build();
app.UseMiddleware<MyMiddleware>();
app.Run();

internal class MyMiddleware
{
    private readonly RequestDelegate _next;

    public MyMiddleware(RequestDelegate next,
        [FromKeyedServices("test")] MySingletonClass service)
    {
        _next = next;
    }

    public Task Invoke(HttpContext context,
        [FromKeyedServices("test2")]
            MyScopedClass scopedService) => _next(context);
}

Considerare attendibile il certificato di sviluppo HTTPS di ASP.NET Core in Linux

Nelle distribuzioni Linux basate su Ubuntu e Fedora ora dotnet dev-certs https --trust configura ASP.NET certificato di sviluppo HTTPS core come certificato attendibile per:

  • Browser Chromium, ad esempio Google Chrome, Microsoft Edge e Chromium.
  • Mozilla Firefox e i browser derivati da Mozilla.
  • API .NET, ad esempio HttpClient

In precedenza, --trust funzionava solo su Windows e macOS. L'attendibilità del certificato viene applicata per utente.

Per stabilire un trust in OpenSSL, lo dev-certs strumento:

  • Inserisce il certificato in ~/.aspnet/dev-certs/trust
  • Esegue una versione semplificata dello strumento di c_rehash openSSL nella directory.
  • Chiede all'utente di aggiornare la SSL_CERT_DIR variabile di ambiente.

Per stabilire un trust in dotnet, lo strumento inserisce il certificato nell'archivio My/Root certificati.

Per stabilire l'attendibilità nei database NSS, se presenti, lo strumento cerca nella home directory i profili Firefox, ~/.pki/nssdbe ~/snap/chromium/current/.pki/nssdb. Per ogni directory trovata, lo strumento aggiunge una voce a nssdb.

Modelli aggiornati alle versioni più recenti di Bootstrap, jQuery e convalida jQuery

I modelli di progetto e le librerie core di ASP.NET sono stati aggiornati per usare le versioni più recenti di Bootstrap, jQuery e jQuery Validation, in particolare:

  • Bootstrap 5.3.3
  • jQuery 3.7.1
  • Convalida di jQuery 1.21.0