Teilen ü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 6 aktualisiert.

Blazor

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

.NET MAUIBlazor Hybrid und Web App-Lösungsvorlage

Eine neue Lösungsvorlage erleichtert die Erstellung von .NET MAUI-nativen und Blazor-Web-Client-Apps, die dieselbe Benutzeroberfläche verwenden. Diese Vorlage zeigt, wie Sie Client-Apps erstellen, welche die Wiederverwendung von Code maximieren und Android, iOS, Mac, Windows und Web verwenden.

Zu den wichtigsten Funktionen dieser Vorlage gehören:

  • Die Möglichkeit, einen interaktiven Blazor-Rendering-Modus für die Web-App zu wählen.
  • Automatische Erstellung der entsprechenden Projekte, einschließlich einer Blazor-Web App (globales interaktives Autorendering) und einer .NET MAUIBlazor Hybrid-App.
  • Die erstellten Projekte verwenden eine freigegebene Razor Klassenbibliothek (RCL), um die Komponenten der Benutzeroberfläche Razor zu verwalten.
  • Beispielcode ist enthalten, der veranschaulicht, wie Abhängigkeitsinjektion verwendet wird, um verschiedene Schnittstellenimplementierungen für die Blazor Hybrid-App und die Blazor-Web App bereitzustellen.

Um loszulegen, installieren Sie das .NET 9 SDK und installieren Sie den Workload .NET MAUI, der die Vorlage enthält:

dotnet workload install maui

Erstellen Sie eine Projektmappe aus der Projektvorlage in einer Befehlsshell mithilfe des folgenden Befehls:

dotnet new maui-blazor-web

Die Vorlage ist auch in Visual Studio verfügbar.

Hinweis

Derzeit tritt eine Ausnahme auf, wenn Blazor-Rendermodi auf Seiten-/Komponentenebene definiert sind. Weitere Informationen finden Sie unter BlazorWebView benötigt eine Möglichkeit, ResolveComponentForRenderMode (dotnet/aspnetcore #51235) zu aktivieren.

Weitere Informationen finden Sie unter Erstellen einer .NET MAUIBlazor Hybrid-App mit einer Blazor-Web App.

Optimierung der Übermittlung statischer Ressourcen

MapStaticAssets ist eine neue Middleware, welche die Bereitstellung statischer Ressourcen in jeder ASP.NET Core-App, einschließlich Blazor-Apps, optimiert.

Weitere Informationen finden Sie in den folgenden Ressourcen:

Erkennen des Renderorts, der Interaktivität und des zugewiesenen Rendermodus zur Laufzeit

Wir haben eine neue API eingeführt, die das Abfragen von Komponentenzuständen zur Laufzeit vereinfacht. Diese API bietet die folgenden Funktionen:

  • Ermitteln Sie den aktuellen Ausführungsort der Komponente: Dies kann besonders hilfreich beim Debuggen und Optimieren der Komponentenleistung sein.
  • Überprüfen Sie, ob die Komponente in einer interaktiven Umgebung ausgeführt wird: Dies kann für Komponenten hilfreich sein, die je nach Interaktivität ihrer Umgebung unterschiedliche Verhaltensweisen aufweisen.
  • Abrufen des zugewiesenen Rendermodus für die Komponente: Das Verständnis des Rendermodus kann dabei helfen, den Renderingprozess zu optimieren und die Gesamtleistung einer Komponente zu verbessern.

Weitere Informationen finden Sie unter ASP.NET CoreBlazor-Rendermodi.

Verbesserte serverseitige erneute Verbindung:

Die folgenden Verbesserungen wurden an der serverseitigen Standardverbindungsumgebung vorgenommen:

  • Wenn der Benutzer zurück zu einer App mit einer getrennten Verbindung navigiert, wird die erneute Verbindung sofort versucht, anstatt auf die Dauer des nächsten erneuten Verbindungsintervalls zu warten. Dies verbessert die Benutzererfahrung, wenn Sie in einer Browser-Registerkarte, die in den Ruhezustand übergegangen ist, zu einer App navigieren.

  • Wenn ein erneuter Verbindungsversuch den Server erreicht, der Server aber bereits den Schaltkreis freigegeben hat, tritt automatisch eine Seitenaktualisierung auf. Dadurch wird verhindert, dass der Benutzer die Seite manuell aktualisieren muss, wenn dies wahrscheinlich zu einer erfolgreichen erneuten Verbindung führt.

  • Die Zeitdauer für die erneute Verbindung verwendet eine berechnete Backoffstrategie. Standardmäßig treten die ersten Wiederholungsversuche in schneller Folge ohne Wiederholungsintervall auf, bevor berechnete Verzögerungen zwischen Versuchen eingeführt werden. Sie können das Wiederholungsintervallverhalten anpassen, indem Sie eine Funktion zum Berechnen des Wiederholungsintervalls angeben, wie das folgende exponentielle Backoff-Beispiel veranschaulicht:

    Blazor.start({
      circuit: {
        reconnectionOptions: {
          retryIntervalMilliseconds: (previousAttempts, maxRetries) => 
            previousAttempts >= maxRetries ? null : previousAttempts * 1000
        },
      },
    });
    
  • Das Design der Standard-Benutzeroberfläche für die Wiederverbindung wurde modernisiert.

Weitere Informationen finden Sie in den Anleitungen zu ASP.NET Core BlazorSignalR.

Vereinfachte Serialisierung des Authentifizierungsstatus für Blazor-Web-Apps

Neue APIs erleichtern das Hinzufügen der Authentifizierung zu einer bestehenden Blazor-Web-App. Wenn Sie eine neue Blazor-Web-App mit Authentifizierung über Einzelkonten erstellen und die WebAssembly-basierte Interaktivität aktivieren, enthält das Projekt sowohl im Server- als auch im Client-Projekt einen benutzerdefinierten Wert AuthenticationStateProvider.

Diese Anbieter geben den Authentifizierungsstatus des Benutzers an den Browser weiter. Die Authentifizierung auf dem Server statt auf dem Client ermöglicht der Anwendung den Zugriff auf den Authentifizierungsstatus während des Prerenderings und vor der Initialisierung der Blazor WebAssembly-Laufzeit.

Die benutzerdefinierten AuthenticationStateProvider-Implementierungen verwenden den Dienst Persistent Component State (PersistentComponentState), um den Authentifizierungsstatus in HTML-Kommentare zu serialisieren und ihn von WebAssembly zurückzulesen, um eine neue AuthenticationState-Instanz zu erstellen.

Das funktioniert gut, wenn Sie von der Blazor-Web-App-Projektvorlage ausgegangen sind und die Option Individuelle Konten gewählt haben, aber es ist eine Menge Code, den Sie selbst implementieren oder kopieren müssen, wenn Sie versuchen, die Authentifizierung zu einem bestehenden Projekt hinzuzufügen. Es gibt jetzt APIs, die jetzt Teil der Blazor-Web App-Projektvorlage sind, die in Server- und Clientprojekten aufgerufen werden können, um diese Funktionalität hinzuzufügen:

  • AddAuthenticationStateSerialization: Fügt die erforderlichen Dienste hinzu, um den Authentifizierungsstatus auf dem Server zu serialisieren.
  • AddAuthenticationStateDeserialization: Fügt die erforderlichen Dienste hinzu, um den Authentifizierungsstatus im Browser zu deserialisieren.

Diese API serialisiert standardmäßig nur die serverseitigen Namens- und Rollenansprüche für den Zugriff im Browser. Eine Option kann an AddAuthenticationStateSerialization übergeben werden, um alle Ansprüche einzuschließen.

Weitere Informationen finden Sie in den folgenden Abschnitten des ** Artikels:

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 Serverkomponenten die Komprimierung für WebSocket-Verbindungen und legen eine frame-ancestors-CSP-Anweisung (Content Security Policy) auf 'self' fest, die nur das Einbetten der App in <iframe> vom 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; }
}

Verbesserte Aktivitäten für SignalR

SignalR verfügt jetzt über eine ActivitySource mit dem Namen Microsoft.AspNetCore.SignalR.Server, die Ereignisse für Hubmethodenaufrufe ausgibt:

  • Jede Methode ist eine eigene Aktivität, sodass alles, was während des Hubmethodenaufrufs eine Aktivität ausgibt, unter der Hubmethodenaktivität liegt.
  • Hubmethodenaktivitäten verfügen nicht über ein übergeordnetes Element. Dies bedeutet, dass sie nicht unter der langlebigen SignalR-Verbindung gebündelt werden.

Im folgenden Beispiel wird das .NET Aspire-Dashboard und die OpenTelemetry-Pakete verwendet:

<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" />

Fügen Sie der Datei Program.cs den folgenden Startcode hinzu:

// 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())
        {
            // We want to view all traces in development
            tracing.SetSampler(new AlwaysOnSampler());
        }

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

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

Im Folgenden finden Sie eine Beispielausgabe vom Aspire-Dashboard:

Aktivitätsliste für SignalR Hub Methodenaufrufe

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.

Verbesserungen der Intellisense-Vervollständigung für das OpenAPI-Paket

Die OpenAPI-Unterstützung von ASP.NET Core ist jetzt besser zugänglich und benutzerfreundlicher. Die OpenAPI-APIs werden als unabhängiges Paket ausgeliefert, getrennt vom gemeinsamen Framework. Bis jetzt bedeutete dies, dass Entwicklerteams nicht den Komfort von Code-Vervollständigungsfunktionen wie Intellisense für OpenAPI-APIs hatten.

Wir haben diese Lücke erkannt und einen neuen Vervollständigungsanbieter und eine Code-Korrekturfunktion eingeführt. Diese Tools sollen das Auffinden und die Verwendung von OpenAPI-APIs erleichtern und Entwicklerteams die Integration von OpenAPI in ihre Projekte ermöglichen. Der Vervollständigungsanbieter bietet Code-Vorschläge in Echtzeit, während die Code-Korrektur dabei hilft, häufige Fehler zu korrigieren und die API-Nutzung zu verbessern. Diese Verbesserung ist Teil unseres ständigen Bestrebens, die Erfahrung für Entwicklerteams zu verbessern und API-bezogene Workflows zu optimieren.

Wenn eine Benutzerin oder ein Benutzer eine Anweisung eingibt, für die eine OpenAPI-bezogene API verfügbar ist, zeigt der Vervollständigungsanbieter eine Empfehlung für die API an. In den folgenden Screenshots werden beispielsweise die Vervollständigungen für AddOpenApi und MapOpenApi erstellt, wenn eine Benutzerin oder ein Benutzer eine Aufrufanweisung für einen unterstützten Typ, wie IEndpointConventionBuilder eingibt:

Vervollständigungen für OpenAPI

Wenn die Vervollständigung akzeptiert wird und das Paket Microsoft.AspNetCore.OpenApi nicht installiert ist, stellt ein Codefixer eine Verknüpfung zum automatischen Installieren der Abhängigkeit im Projekt bereit.

Automatische Paketinstallation

Unterstützung für [Required]- und [DefaultValue]-Attribute für Parameter und Eigenschaften

Wenn [Required]- und [DefaultValue]-Attribute auf Parameter oder Eigenschaften innerhalb komplexer Typen angewendet werden, ordnet die OpenAPI-Implementierung diese den Eigenschaften required und default im OpenAPI-Dokument zu, das dem Parameter oder Typschema zugeordnet ist.

Die folgende API erzeugt z. B. das begleitende Schema für den Typ Todo.

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapPost("/todos", (Todo todo) => { });

app.Run();

class Todo
{
	public int Id { get; init; }
	public required string Title { get; init; }
	[DefaultValue("A new todo")]
	public required string Description { get; init; }
	[Required]
	public DateTime CreatedOn { get; init; }
}
{
	"required": [
	  "title",
	  "description",
	  "createdOn"
	],
	"type": "object",
	"properties": {
	  "id": {
	    "type": "integer",
	    "format": "int32"
	  },
	  "title": {
	    "type": "string"
	  },
	  "description": {
	    "type": "string",
	    "default": "A new todo"
	  },
	  "createdOn": {
	    "type": "string",
	    "format": "date-time"
	  }
	}
}

Unterstützung für Schematransformatoren in OpenAPI-Dokumenten

Integrierte OpenAPI-Unterstützung wird jetzt mit Unterstützung für Schematransformatoren bereitgestellt, die zum Ändern von Schemata verwendet werden können, die von System.Text.Json und der OpenAPI-Implementierung generiert werden. Wie Dokument- und Vorgangstransformatoren können Schematransformatoren auf dem OpenApiOptions-Objekt registriert werden. Das folgende Codebeispiel veranschaulicht die Verwendung eines Schematransformators zum Hinzufügen eines Beispiels zum Schema eines Typs.

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Microsoft.OpenApi.Any;

var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi(options =>
{
    options.UseSchemaTransformer((schema, context, cancellationToken) =>
    {
        if (context.Type == typeof(Todo))
        {
            schema.Example = new OpenApiObject
            {
                ["id"] = new OpenApiInteger(1),
                ["title"] = new OpenApiString("A short title"),
                ["description"] = new OpenApiString("A long description"),
                ["createdOn"] = new OpenApiDateTime(DateTime.Now)
            };
        }
        return Task.CompletedTask;
    });
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.MapOpenApi();
}

app.MapPost("/todos", (Todo todo) => { });

app.Run();

class Todo
{
	public int Id { get; init; }
	public required string Title { get; init; }
	[DefaultValue("A new todo")]
	public required string Description { get; init; }
	[Required]
	public DateTime CreatedOn { get; init; }
}

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 Schlüsselwort sealed in C# bedeutet, dass die Klasse nicht geerbt werden kann.
  • Anwenden des [ImmutableObject(true)]-Attributs. Das Attribut [ImmutableObject(true)] gibt an, dass der Status des Objekts nach der Erstellung 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.

Optimieren der Bereitstellung statischer Webobjekte

Die Einhaltung bewährter Methoden für die Bereitstellung statischer Ressourcen erfordert einen erheblichen Arbeitsaufwand und technisches Fachwissen. Ohne Optimierungen wie Komprimierung, Zwischenspeichern und Fingerabdrücke:

  • Der Browser muss zusätzliche Anforderungen an jeden Seiteladevorgang stellen.
  • Mehr Bytes als erforderlich werden über das Netzwerk übertragen.
  • Manchmal werden veraltete Versionen von Dateien für Clients bereitgestellt.

Das Erstellen von leistungsfähigen Web-Apps erfordert die Optimierung der Ressourcenübermittlung an den Browser. Mögliche Optimierungen sind:

  • Eine bestimmte Ressource wird einmal bereitgestellt, bis sich die Datei ändert oder der Browser seinen Cache leert. Legen Sie den ETag-Header fest.
  • Es wird verhindert, dass der Browser alte oder veraltete Ressourcen verwendet, nachdem eine Anwendung aktualisiert wurde. Legen Sie den Header Zuletzt geändert fest.
  • Legen Sie den richtigen Zwischenspeicherungsheader fest.
  • Verwenden Sie die Zwischenspeicherungs-Middleware.
  • Stellen Sie wenn möglich komprimierte Versionen der Ressourcen bereit.
  • Verwenden Sie einen CDN, um die Ressourcen näher an die Benutzerin oder den Benutzer zu bringen.
  • Es minimiert die Größe der Ressourcen, die dem Browser zur Verfügung gestellt werden. Diese Optimierung beinhaltet keine Minimierung.

MapStaticAssets ist eine neue Middleware, die die Übermittlung statischer Objekte in einer App optimiert. Sie ist für alle UI-Frameworks geeignet, einschließlich Blazor, Razor Pages und MVC. Sie ist in der Regel ein Drop-In-Ersatz für 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();

Der Vorgang von MapStaticAssets besteht in der Kombination von Build- und Veröffentlichungsprozessen, um Informationen über alle statischen Ressourcen in einer Anwendung zu sammeln. Diese Informationen werden dann von der Laufzeitbibliothek verwendet, um diese Dateien effizient an den Browser zu liefern.

MapStaticAssets kann UseStaticFiles in den meisten Situationen ersetzen, ist aber für die Bereitstellung der Ressourcen optimiert, die der App zum Zeitpunkt der Erstellung und Veröffentlichung bekannt sind. Wenn die App Ressourcen von anderen Speicherorten bereitstellt, z. B. von der Festplatte oder von eingebetteten Ressourcen, sollte UseStaticFiles verwendet werden.

MapStaticAssets bietet die folgenden Vorteile, die UseStaticFiles nicht hat:

  • Buildzeitkomprimierung für alle Ressourcen in der App:
    • gzip während der Entwicklung und gzip + brotli während der Veröffentlichung.
    • Alle Ressourcen werden mit dem Ziel komprimiert, die Größe der Ressourcen auf ein Minimum zu reduzieren.
  • Inhaltsbasierte ETags: Die Etags für jede Ressource sind die Base64-codierte Zeichenfolge des SHA-256-Hashs des Inhalts. Dadurch wird sichergestellt, dass der Browser eine Datei nur dann erneut lädt, wenn sich der Inhalt geändert hat.

In der folgenden Tabelle sind die ursprünglichen und komprimierten Größen der CSS- und JS-Dateien in der Razor Pages-Standardvorlage aufgeführt:

Datei Ursprünglich Compressed % Reduzierung
bootstrap.min.css 163 17,5 89,26 %
jquery.js 89,6 28 68,75 %
bootstrap.min.js 78,5 20 74,52 %
Gesamt 331,1 65,5 80,20 %

Die folgende Tabelle zeigt die ursprünglichen und komprimierten Größen mithilfe der Fluent UI Blazor Komponentenbibliothek:

Datei Ursprünglich Compressed % Reduzierung
fluent.js 384 73 80,99 %
fluent.css 94 11 88,30 %
Gesamt 478 84 82,43 %

Ergebnis: 478 KB unkomprimiert zu 84 KB komprimiert.

Die folgende Tabelle zeigt die originalen und komprimierten Größen mithilfe der MudBlazorBlazor-Komponentenbibliothek:

Datei Ursprünglich Compressed Reduzierung
MudBlazor.min.css 541 37,5 93,07 %
MudBlazor.min.js 47,4 9.2 80,59 %
Gesamt 588,4 46,7 92,07 %

Optimierung erfolgt automatisch bei Verwendung von MapStaticAssets. Wenn eine Bibliothek hinzugefügt oder aktualisiert wird, z. B. mit neuem JavaScript oder CSS, werden die Ressourcen als Teil des Builds optimiert. Die Optimierung ist besonders für mobile Umgebungen von Vorteil, die eine geringere Bandbreite oder unzuverlässige Verbindungen haben können.

Weitere Informationen über die neuen Funktionen zur Bereitstellung von Dateien finden Sie in den folgenden Ressourcen:

Aktivieren der dynamischen Komprimierung auf dem Server gegenüber der Verwendung von MapStaticAssets

MapStaticAssets hat die folgenden Vorteile gegenüber der dynamischen Komprimierung auf dem Server:

  • Es ist einfacher, da keine serverspezifische Konfiguration vorhanden ist.
  • Es ist leistungsfähiger, da die Ressourcen zur Erstellungszeit komprimiert werden.
  • Es ermöglicht der Entwicklerin oder dem Entwickler, während des Buildprozesses zusätzliche Zeit damit zu verbringen, sicherzustellen, dass die Ressourcen die Mindestgröße aufweisen.

Betrachten Sie die folgende Tabelle, in der die MudBlazor-Komprimierung mit der dynamischen IIS-Komprimierung und MapStaticAssets verglichen wird:

IIS gzip MapStaticAssets MapStaticAssets-Reduzierung
≅ 90 37,5 59 %

ASP0026: Analysetool, das warnt, wenn [Authorize] von [AllowAnonymous] von „weiter entfernt“ außer Kraft gesetzt wird

Es erscheint intuitiv, dass ein [Authorize]-Attribut, das "näher" an einer MVC-Aktion platziert ist als ein [AllowAnonymous]-Attribut, das [AllowAnonymous]-Attribut außer Kraft setzt und eine Autorisierung erzwingt. Dies ist jedoch nicht unbedingt der Fall. Was zählt, ist die relative Reihenfolge der Attribute.

Der folgende Code zeigt Beispiele, in denen ein näher gelegenes [Authorize]-Attribut durch ein weiter entferntes [AllowAnonymous]-Attribut außer Kraft gesetzt wird.

[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 haben wir ein Analysetool eingeführt, das solche Fälle hervorhebt, in denen ein näheres [Authorize]-Attribut durch ein [AllowAnonymous]-Attribut überschrieben wird, das weiter von einer MVC-Aktion entfernt ist. Die Warnung verweist auf das überschriebene [Authorize]-Attribut mit der folgenden Meldung:

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

Was Sie tun müssen, wenn Sie diese Warnung sehen, hängt von der Absicht ab, die hinter den Attributen steht. Das weiter entfernte [AllowAnonymous]-Attribut sollte entfernt werden, wenn es den Endpunkt ungewollt anonymen Benutzerinnen und Benutzern zugänglich macht. Wenn das [AllowAnonymous]-Attribut ein näheres [Authorize]-Attribut außer Kraft setzen soll, können Sie das [AllowAnonymous]-Attribut nach dem [Authorize]-Attribut wiederholen, um die Absicht zu verdeutlichen.

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