Share via


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.

Questo articolo è stato aggiornato per .NET 9 Preview 4.

Blazor

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

Aggiungere pagine di rendering statico lato server (SSR) a un'app Web interattiva Blazor a livello globale

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 HTTP cookiee possono funzionare solo in un ciclo di richiesta/risposta anziché in un 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 HttpContext.AcceptsInteractiveRouting metodo di estensione consente al componente di rilevare se [ExcludeFromInteractiveRouting] viene applicato alla pagina corrente.

App Nel componente usare il modello nell'esempio seguente:

  • Pagine che non sono annotate per [ExcludeFromInteractiveRouting] impostazione predefinita alla InteractiveServer modalità di rendering con interattività globale. È possibile sostituire InteractiveServer con InteractiveWebAssembly o InteractiveAuto per specificare una modalità di rendering globale predefinita diversa.
  • Pagine annotate con [ExcludeFromInteractiveRouting] l'adozione di SSR statico (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 HttpContext.AcceptsInteractiveRouting leggere manualmente i metadati dell'endpoint usando HttpContext.GetEndpoint()?.Metadata.

Questa funzionalità è descritta nella documentazione di riferimento nelle modalità di rendering di ASP.NET CoreBlazor.

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)
{
    protected NavigationManager Navigation { get; } = navigation;
}

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 'none' configurazione più frame-ancestors 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>

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; }
}

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();

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 Preview 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 --prerelease

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 --prerelease

Nel file di progetto dell'app aggiungere quanto segue:

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

Eseguire dotnet build ed esaminare il file ON generato JSnella 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 e operazioni 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.

Autenticazione e autorizzazione

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

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

L'API HybridCache consente di colmare alcune lacune nelle API e IMemoryCache esistentiIDistributedCache. 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 IMemoryCache esistente IDistributedCache 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 :

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 Yhe [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 cookienomi delle stringhe di query e dei metodi lunghi 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
  • Cookies
  • 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.