Freigeben über


Neuerungen in ASP.NET Core 9.0

In diesem Artikel werden die wichtigsten Änderungen in ASP.NET Core 9.0 aufgezeigt und Links zur relevanten Dokumentation bereitgestellt.

Dieser Artikel wurde für .NET 9 Preview 4 aktualisiert.

Blazor

In diesem Abschnitt werden neue Features für Blazor beschrieben.

Hinzufügen statischer serverseitiger Renderingseiten (SSR) zu einer global interaktiven Blazor Web-App

Mit der Veröffentlichung von .NET 9 ist es jetzt einfacher, statische SSR-Seiten zu Apps hinzuzufügen, die globale Interaktivität übernehmen.

Dieser Ansatz ist nur hilfreich, wenn bestimmte Seiten der App nicht mit interaktivem Server- oder WebAssembly-Rendering funktionieren. Dieser Ansatz eignet sich z. B. für Seiten, die vom Lesen/Schreiben von HTTP-cookies abhängig sind und nur in einem Anforderungs-/Antwortzyklus funktionieren können, nicht mit interaktivem Rendering. Für Seiten, die mit interaktivem Rendering funktionieren, sollten Sie kein statisches SSR-Rendering erzwingen, da es weniger reaktionsfähig und weniger effizient für den Endbenutzer ist.

Markieren Sie eine beliebige Razor-Komponentenseite mit dem neuen [ExcludeFromInteractiveRouting]-Attribut, das mit der @attributeRazor-Direktive zugewiesen wurde:

@attribute [ExcludeFromInteractiveRouting]

Durch das Anwenden des Attributs wird die Navigation zu der Seite über das interaktive Routing beendet. Die eingehende Navigation wird gezwungen, die Seite vollständig neu zu laden, anstatt die Seite über das interaktive Routing aufzulösen. Das vollständige Neuladen zwingt die Stammkomponente der obersten Ebene, in der Regel die App-Komponente (App.razor), vom Server erneut zu rendern, sodass die App zu einem anderen Rendermodus auf oberster Ebene wechseln kann.

Mit der HttpContext.AcceptsInteractiveRouting-Erweiterungsmethode kann die Komponente erkennen, ob [ExcludeFromInteractiveRouting] auf die aktuelle Seite angewendet wird.

Verwenden Sie in der App-Komponente das Muster im folgenden Beispiel:

  • Seiten, die nicht mit [ExcludeFromInteractiveRouting]-Standard für den InteractiveServer Rendermodus mit globaler Interaktivität versehen sind. Sie können InteractiveServer durch InteractiveWebAssembly oder InteractiveAuto ersetzen, um einen anderen standardmäßigen globalen Rendermodus anzugeben.
  • Seiten, die mit [ExcludeFromInteractiveRouting] versehen sind, übernehmen statischen SSR (PageRenderMode wird null zugewiesen).
<!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;
}

Eine Alternative zur Verwendung der HttpContext.AcceptsInteractiveRouting-Erweiterungsmethode besteht darin, Endpunktmetadaten manuell mithilfe von HttpContext.GetEndpoint()?.Metadata zu lesen.

Dieses Feature wird in der Referenzdokumentation in ASP.NET Core Blazor Rendermodi behandelt.

Constructor Injection

Razor Komponenten unterstützen die Konstruktoreinfügung.

Im folgenden Beispiel fügt die partielle (CodeBehind)-Klasse den NavigationManager Dienst mithilfe eines primären Konstruktors ein:

public partial class ConstructorInjection(NavigationManager navigation)
{
    protected NavigationManager Navigation { get; } = navigation;
}

Weitere Informationen finden Sie unter Abhängigkeitsinjektion in ASP.NET Core Blazor.

Websocket-Komprimierung für interaktive Serverkomponenten

Standardmäßig aktivieren Interaktive Server-Komponenten die Komprimierung für WebSocket-Verbindungen und legen eineframe-ancestors CSP-Direktive (Content Security Policy) fest, die 'self'nur das Einbetten der App in einen <iframe> Ursprung ermöglicht, von dem die App bei aktivierter Komprimierung bereitgestellt wird oder wenn eine Konfiguration für den WebSocket-Kontext bereitgestellt wird.

Komprimierung kann durch Festlegen ConfigureWebSocketOptions auf null, wodurch die Sicherheitsanfälligkeit der App zum Angriff reduziert wird, dies kann jedoch zu einer verringerten Leistung führen:

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

Konfigurieren Sie einen strikteren frame-ancestors CSP mit einem Wert von 'none' (einfache Anführungszeichen erforderlich), was die WebSocket-Komprimierung zulässt, aber verhindert, dass Browser die App in eine der <iframe>folgenden Werte einbetten:

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

Weitere Informationen finden Sie in den folgenden Ressourcen:

Behandeln von Tastaturkompositionsereignissen in Blazor

Die neue KeyboardEventArgs.IsComposing-Eigenschaft gibt an, ob das Tastaturereignis Teil einer Kompositionssitzung ist. Das Nachverfolgen des Kompositionszustands von Tastaturereignissen ist entscheidend für die Behandlung internationaler Zeicheneingabemethoden.

Parameter OverscanCount zu QuickGrid hinzugefügt

Die QuickGrid-Komponente macht jetzt eine OverscanCount-Eigenschaft verfügbar, die angibt, wie viele zusätzliche Zeilen vor und nach dem sichtbaren Bereich gerendert werden, wenn die Virtualisierung aktiviert ist.

Der Standard OverscanCount ist „3“. Das folgende Beispiel erhöht OverscanCount um 4:

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

SignalR

In diesem Abschnitt werden neue Features für SignalR beschrieben.

Polymorphe Typunterstützung in SignalR-Hubs

Hubmethoden können nun eine Basisklasse anstelle der abgeleiteten Klasse akzeptieren, um polymorphe Szenarien zu ermöglichen. Der Basistyp muss annotiert werden, um Polymorphismus zu ermöglichen.

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

Minimale APIs

In diesem Abschnitt werden neue Features für minimale APIs beschrieben.

InternalServerError und InternalServerError<TValue> wurden zu TypedResults hinzugefügt.

Die Klasse TypedResults ist ein hilfreiches Mittel für die Rückgabe von stark typisierten HTTP-Statuscode-basierten Antworten aus einer minimalen API. TypedResults enthält jetzt Factorymethoden und Typen für die Rückgabe von „500 Interner Serverfehler“-Antworten von Endpunkten. Hier ist ein Beispiel, das eine 500-Antwort zurückgibt:

var app = WebApplication.Create();

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

app.Run();

OpenAPI

Integrierte Unterstützung für die OpenAPI-Dokumentgenerierung

Die OpenAPI-Spezifikation ist ein Standard zur Beschreibung von HTTP-APIs. Mit dem Standard können Entwickler die Form der APIs definieren, die an Clientgeneratoren, Servergeneratoren, Testtools, Dokumentationen und vieles mehr angeschlossen werden können. In .NET 9 Preview bietet ASP.NET Core integrierte Unterstützung für das Generieren von OpenAPI-Dokumenten, die controllerbasierte oder minimale APIs über das Microsoft.AspNetCore.OpenApi-Paket darstellen.

Der folgende hervorgehobene Code ruft Folgendes auf:

  • AddOpenApi zum Registrieren der erforderlichen Abhängigkeiten im DI-Container der App.
  • MapOpenApi zum Registrieren der erforderlichen OpenAPI-Endpunkte in den Routen der App.
var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();

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

app.Run();

Installieren Sie das Microsoft.AspNetCore.OpenApi-Paket im Projekt mithilfe des folgenden Befehls:

dotnet add package Microsoft.AspNetCore.OpenApi --prerelease

Führen Sie die App aus, und navigieren Sie zu openapi/v1.json, um das generierte OpenAPI-Dokument anzuzeigen:

OpenAPI-Dokument

OpenAPI-Dokumente können auch zur Buildzeit generiert werden, indem Sie das Microsoft.Extensions.ApiDescription.Server-Paket hinzufügen:

dotnet add package Microsoft.Extensions.ApiDescription.Server --prerelease

Fügen Sie in der Projektdatei der App Folgendes hinzu:

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

Führen Sie dotnet build aus, und prüfen Sie die generierte JSON-Datei im Projektverzeichnis.

OpenAPI-Dokumentgenerierung zur Erstellungszeit

In ASP.NET Core integrierte OpenAPI-Dokumentgenerierung bietet Unterstützung für verschiedene Anpassungen und Optionen. Sie bietet Dokument- und Operationstransformatoren und die Möglichkeit, mehrere OpenAPI-Dokumente für dieselbe Anwendung zu verwalten.

Weitere Informationen zu ASP.NET den neuen OpenAPI-Dokumentfunktionen von Core finden Sie in den neuen Microsoft.AspNetCore.OpenApi-Dokumenten.

Authentifizierung und Autorisierung

In diesem Abschnitt werden neue Features für Authentifizierung und Autorisierung beschrieben.

Anpassung der OIDC- und OAuth-Parameter

Die OAuth- und OIDC-Authentifizierungs-Handler verfügen jetzt über eine AdditionalAuthorizationParameters Option, mit der Sie die Parameter der Autorisierungsnachricht, die normalerweise Teil des Redirect-Query-Strings sind, einfacher anpassen können. In .NET 8 und früher erfordert dies einen benutzerdefinierten OnRedirectToIdentityProvider Callback oder eine überschriebene BuildChallengeUrl Methode in einem benutzerdefinierten Handler. Hier ist ein Beispiel für .NET 8 Code:

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

Das vorangegangene Beispiel kann nun zu folgendem Code vereinfacht werden:

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

Konfigurieren Sie die erweiterten Authentifizierungs-Flags von HTTP.sys

Sie können nun die HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING und HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL HTTP.sys-Flags konfigurieren, indem Sie die neuen EnableKerberosCredentialCaching und CaptureCredentials Eigenschaften der HTTP.sys AuthenticationManager verwenden, um die Handhabung der Windows-Authentifizierung zu optimieren. Zum Beispiel:

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

Verschiedenes

In den folgenden Abschnitten werden verschiedene neue Features beschrieben.

Neue HybridCache-Bibliothek

Die HybridCache-API überbrückt einige Lücken in den IDistributedCache- und IMemoryCache-APIs. Außerdem werden neue Funktionen hinzugefügt, z. B.:

  • "Stampede"-Schutz, um parallele Abrufe derselben Arbeit zu verhindern.
  • Konfigurierbare Serialisierung.

HybridCache ist als Drop-In-Ersatz für vorhandene IDistributedCache- und IMemoryCache-Nutzung konzipiert und bietet eine einfache API zum Hinzufügen von neuem Zwischenspeicherungscode. Es bietet eine einheitliche API für die In-Process- und Out-of-Process-Zwischenspeicherung.

Um zu sehen, wie die HybridCache-API vereinfacht wird, vergleichen Sie sie mit Code, der IDistributedCache verwendet. Hier sehen Sie ein Beispiel dafür, wie die Verwendung von IDistributedCache aussieht:

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)
    { /* ... */ }
}

Das ist viel Arbeit, um jedes Mal richtig zu liegen, einschließlich Dinge wie Serialisierung. Und im Szenario „Cachefehler“ könnten Sie mit mehreren gleichzeitigen Threads enden, die alle einen Cachefehler erhalten, alle zugrunde liegenden Daten abrufen, serialisieren und alle diese Daten an den Cache senden.

Um diesen Code mit HybridCache zu vereinfachen und zu verbessern, müssen wir zuerst die neue Bibliothek Microsoft.Extensions.Caching.Hybrid hinzufügen:

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

Registrieren Sie den HybridCache-Dienst, wie Sie eine IDistributedCache-Implementierung registrieren würden:

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

Jetzt können die meisten Zwischenspeicherungsbedenken auf HybridCache entladen werden:

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

Wir stellen eine konkrete Implementierung der abstrakten Klasse HybridCache über Abhängigkeitsinjektion bereit, aber es ist beabsichtigt, dass Entwickler benutzerdefinierte Implementierungen der API bereitstellen können. Die HybridCache-Implementierung befasst sich mit allem, was mit dem Caching zusammenhängt, einschließlich der Handhabung gleichzeitiger Operationen. Das cancel-Token steht hier für die kombinierte Annullierung aller gleichzeitigen Anrufer –- nicht nur für die Annullierung des Anrufers, den wir sehen können (d. h. token).

Szenarien mit hohem Durchsatz können mithilfe des TState-Musters weiter optimiert werden, um den Aufwand von erfassten Variablen und Rückrufen pro Instanz zu vermeiden:

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 verwendet ggf. die konfigurierte IDistributedCache-Implementierung für sekundäre Out-of-Process-Speicherung, z. B. mithilfe von Redis. Aber auch ohne IDistributedCache bietet der HybridCache-Dienst weiterhin In-Process-Cache- und Stampede-Schutz.

Ein Hinweis zur Wiederverwendung von Objekten

Im typischen vorhandenen Code, der IDistributedCache verwendet, führt jeder Abruf eines Objekts aus dem Cache zur Deserialisierung. Dieses Verhalten bedeutet, dass jeder gleichzeitige Aufrufer eine separate Instanz des Objekts erhält, die nicht mit anderen Instanzen interagieren kann. Das Ergebnis ist Threadsicherheit, da es kein Risiko für gleichzeitige Änderungen an derselben Objektinstanz gibt.

Da viele HybridCache-Verwendungen von vorhandenem IDistributedCache-Code angepasst werden, behält HybridCache dieses Verhalten standardmäßig bei, um die Einführung von Parallelitätsfehlern zu vermeiden. Ein bestimmter Anwendungsfall ist jedoch inhärent threadsicher:

  • Wenn die zwischengespeicherten Typen unveränderlich sind.
  • Wenn der Code sie nicht ändert.

Informieren Sie in solchen Fällen HybridCache, dass es sicher ist, Instanzen wiederzuverwenden, indem Sie:

  • Den Typ als sealed markieren. Das sealed-Schlüsselwort in C# bedeutet, dass die Klasse nicht geerbt werden kann.
  • Anwenden des [ImmutableObject(true)]-Attributs. Das [ImmutableObject(true)]-Attribut gibt an, dass der Status des Objekts nach dem Erstellen nicht mehr geändert werden kann.

Durch die erneute Verwendung von Instanzen kann HybridCache den Mehraufwand der CPU- und Objektzuordnungen verringern, die mit der Deserialisierung pro Aufruf verbunden sind. Dies kann zu Leistungsverbesserungen in Szenarien führen, in denen die zwischengespeicherten Objekte groß sind oder häufig aufgerufen werden.

Weitere HybridCache-Funktionen

Wie IDistributedCache unterstützt HybridCache das Entfernen von Schlüsseln mit einer RemoveKeyAsync-Methode.

HybridCache stellt auch optionale APIs für IDistributedCache-Implementierungen bereit, um byte[]-Zuordnungen zu vermeiden. Dieses Feature wird von den Vorschauversionen der Microsoft.Extensions.Caching.StackExchangeRedis- und Microsoft.Extensions.Caching.SqlServer-Pakete implementiert.

Die Serialisierung wird als Teil der Registrierung des Diensts konfiguriert, wobei typspezifische und generalisierte Serialisierer über die WithSerializer- und .WithSerializerFactory-Methoden, verkettet vom AddHybridCache-Aufruf, unterstützt werden. Standardmäßig behandelt die Bibliothek string und byte[] intern und verwendet System.Text.Json für alles weitere, aber Sie können protobuf, xml oder etwas anderes verwenden.

HybridCache unterstützt ältere .NET-Runtimes bis zu .NET Framework 4.7.2 und .NET Standard 2.0.

Weitere Informationen zu HybridCache finden Sie unter HybridCache-Bibliothek in ASP.NET Core

Verbesserungen der Seite mit Ausnahmen für Entwickler

Die ASP.NET Core-Seite mit Ausnahmen für Entwickler wird angezeigt, wenn eine App während der Entwicklung eine unbehandelte Ausnahme auslöst. Die Seite mit Ausnahmen für Entwickler bietet ausführliche Informationen zur Ausnahme und zur Anforderung.

In Preview 3 wurden Endpunktmetadaten auf der Entwicklerausnahmeseite hinzugefügt. ASP.NET Core verwendet Endpunkt-Metadaten, um das Verhalten von Endpunkten zu steuern, z. B. Routing, Antwort-Caching, Ratenbegrenzung, OpenAPI-Generierung und mehr. Die folgende Abbildung zeigt die neuen Metadaten-Informationen im Abschnitt Routing auf der Entwicklerausnahmeseite:

Die neuen Metadaten-Informationen auf der Entwicklerausnahmeseite.

Beim Testen der Ausnahmeseite für Entwickler wurden kleine Verbesserungen der Lebensqualität identifiziert. Sie wurden in Preview 4 ausgeliefert:

  • Besserer Textumbruch. Lange cookies, Abfragezeichenfolgenwerte und Methodennamen fügen keine horizontalen Browser-Bildlaufleisten mehr hinzu.
  • Größerer Text, der in modernen Designs zu finden ist.
  • Konsistentere Tabellengrößen.

Die folgende animierte Abbildung zeigt die neue Seite mit Ausnahmen für Entwickler:

Die neue Seite mit Ausnahmen für Entwickler

Verbesserungen beim Debuggen von Wörterbuchen

Die Debuganzeige von Wörterbüchern und anderen Schlüsselwertsammlungen verfügt über ein verbessertes Layout. Der Schlüssel wird in der Schlüsselspalte des Debuggers angezeigt, anstatt mit dem Wert verkettet zu werden. Die folgenden Bilder stellen die alte und die neue Anzeige eines Wörterbuchs im Debugger dar.

Vorher:

Die bisherige Debugger-Oberfläche

Nachher:

Die neue Debugger-Oberfläche

ASP.NET Core verfügt über viele Schlüsselwertauflistungen. Diese verbesserte Debugerfahrung gilt für:

  • HTTP-Kopfzeilen
  • Abfragezeichenfolgen
  • Formulare
  • Cookie
  • Anzeigen von Daten
  • Routendaten
  • Features

Fix für 503-Apps während der Wiederverwendung von Apps in IIS

Standardmäßig gibt es jetzt eine Verzögerung von einer Sekunde, wenn IIS über eine Wiederverwendung oder ein Herunterfahren benachrichtigt wird und wenn ANCM den verwalteten Server anweist, das Herunterfahren zu initiieren. Die Verzögerung kann über die Umgebungsvariable ANCM_shutdownDelay oder durch Festlegen der Handlereinstellung shutdownDelay konfiguriert werden. Beide Werte sind in Millisekunden. Die Verzögerung soll in erster Linie die Wahrscheinlichkeit eines Wettrennens verringern, bei dem:

  • IIS keine Warteschlangenanforderungen gestartet hat, um zur neuen App zu wechseln.
  • ANCM mit der Ablehnung neuer Anforderungen beginnt, die in die alte App gelangen.

Für langsamere Computer oder Computer mit höherer CPU-Auslastung muss dieser Wert möglicherweise angepasst werden, um die Wahrscheinlichkeit 503 zu verringern.

Beispiel der Einstellung 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>

Der Fix befindet sich im global installierten ANCM-Modul, das aus dem Hostingbundle stammt.