Udostępnij za pośrednictwem


Co nowego w programie ASP.NET Core 9.0

W tym artykule przedstawiono najważniejsze zmiany w programie ASP.NET Core 9.0 z linkami do odpowiedniej dokumentacji.

Optymalizacja dostarczania zasobów statycznych

MapStaticAssets Zasady routingu punktów końcowych to nowa funkcja, która optymalizuje dostarczanie zasobów statycznych w aplikacjach ASP.NET Core.

Aby uzyskać informacje na temat dostarczania zasobów statycznych dla Blazor aplikacji, zobacz statyczne pliki ASP.NET CoreBlazor.

Przestrzeganie najlepszych rozwiązań produkcyjnych dotyczących obsługi zasobów statycznych wymaga znacznej ilości pracy i wiedzy technicznej. Bez optymalizacji, takich jak kompresja, buforowanie i odciski palców:

  • Przeglądarka musi wysyłać dodatkowe żądania na każdym ładowaniu strony.
  • Więcej bajtów niż jest to konieczne jest transferowanych za pośrednictwem sieci.
  • Czasami nieaktualne wersje plików są udostępniane klientom.

Tworzenie wydajnych aplikacji internetowych wymaga optymalizacji dostarczania zasobów do przeglądarki. Możliwe optymalizacje obejmują:

MapStaticAssets to nowa funkcja, która optymalizuje dostarczanie zasobów statycznych w aplikacji. Jest zaprojektowana do pracy ze wszystkimi strukturami interfejsu użytkownika, w tym Blazor, Razor Pages i MVC. Zazwyczaj jest to bezpośrednie zastąpienie 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 działa przez połączenie procesów kompilowania i publikowania w celu zbierania informacji o wszystkich zasobach statycznych w aplikacji. Te informacje są następnie wykorzystywane przez bibliotekę środowiska uruchomieniowego do wydajnego udostępniania tych plików w przeglądarce.

MapStaticAssets może zastąpić UseStaticFiles w większości sytuacji, jednak jest zoptymalizowany pod kątem obsługi zasobów, które aplikacja zna w czasie budowania i publikacji. Jeśli aplikacja obsługuje zasoby z innych lokalizacji, takich jak dyski lub zasoby osadzone, UseStaticFiles powinny być używane.

MapStaticAssets zapewnia następujące korzyści, których nie znaleziono w programie UseStaticFiles:

  • Kompresja czasu kompilacji dla wszystkich zasobów w aplikacji:
    • gzip podczas opracowywania i gzip + brotli podczas publikacji.
    • Wszystkie zasoby są kompresowane w celu zmniejszenia rozmiaru zasobów do minimum.
  • Oparte na ETagszawartości: Etags dla każdego zasobu to Base64 zakodowany ciąg znaków skrótu SHA-256 zawartości. Dzięki temu przeglądarka ponownie pobierze plik tylko wtedy, gdy jego zawartość uległa zmianie.

W poniższej tabeli przedstawiono oryginalne i skompresowane rozmiary plików CSS oraz plików JS w domyślnym szablonie Razor stron:

Plik Oryginał Skompresowane % redukcji
bootstrap.min.css 163 17.5 89.26%
jquery.js 89.6 28 68.75%
bootstrap.min.js 78.5 20 74.52%
Łącznie 331.1 65.5 80.20%

W poniższej tabeli przedstawiono oryginalne i skompresowane rozmiary przy użyciu biblioteki Fluent UI Blazor komponentów:

Plik Oryginalne Skompresowane % redukcji
fluent.js 384 73 80.99%
fluent.css 94 11 88.30%
Łącznie 478 84 82.43%

Łącznie 478 KB przed kompresją i 84 KB po kompresji.

W poniższej tabeli przedstawiono oryginalne i skompresowane rozmiary przy użyciu biblioteki składników MudBlazorBlazor :

Plik Oryginał Skompresowane Redukcja
MudBlazor.min.css 541 37.5 93.07%
MudBlazor.min.js 47.4 9,2 80.59%
Łącznie 588,4 46,7 92.07%

Optymalizacja odbywa się automatycznie w przypadku korzystania z programu MapStaticAssets. Po dodaniu lub zaktualizowaniu biblioteki, na przykład przy użyciu nowego kodu JavaScript lub CSS, zasoby są optymalizowane w ramach kompilacji. Optymalizacja jest szczególnie korzystna dla środowisk mobilnych, które mogą mieć niższą przepustowość lub zawodne połączenia.

Aby uzyskać więcej informacji na temat nowych funkcji dostarczania plików, zobacz następujące zasoby:

Włączanie kompresji dynamicznej na serwerze lub przy użyciu MapStaticAssets

MapStaticAssets ma następujące zalety niż kompresja dynamiczna na serwerze:

  • Jest prostsze, ponieważ nie ma określonej konfiguracji serwera.
  • Jest bardziej wydajne, ponieważ zasoby są kompresowane w czasie kompilacji.
  • Umożliwia programiście spędzenie dodatkowego czasu podczas procesu kompilacji, aby upewnić się, że zasoby mają minimalny rozmiar.

Rozważmy poniższą tabelę porównującą kompresję MudBlazor z kompresją dynamiczną usług IIS i MapStaticAssets:

IIS gzip MapStaticAssets MapStaticAssets redukcja
≅ 90 37.5 59%

Blazor

W tej sekcji opisano nowe funkcje programu Blazor.

.NET MAUI Blazor Hybrid szablon rozwiązania i aplikacja internetowa

Nowy szablon rozwiązania ułatwia tworzenie .NET MAUI natywnych i Blazor internetowych aplikacji klienckich korzystających z tego samego interfejsu użytkownika. W tym szablonie pokazano, jak tworzyć aplikacje klienckie, które maksymalizują ponowne użycie kodu oraz docelowe systemy Android, iOS, Mac, Windows i Web.

Najważniejsze funkcje tego szablonu obejmują:

  • Możliwość wybrania interaktywnego Blazor trybu renderowania dla aplikacji internetowej.
  • Automatyczne tworzenie odpowiednich projektów, w tym Blazor Web App (globalne interaktywne renderowanie auto) i aplikacja .NET MAUIBlazor Hybrid.
  • Utworzone projekty wykorzystują wspólną bibliotekę klas Razor (RCL) do utrzymania składników interfejsu użytkownika Razor.
  • Dołączono przykładowy kod, który pokazuje, jak używać wstrzykiwania zależności w celu zapewnienia różnych implementacji interfejsu dla aplikacji Blazor Hybrid i Blazor Web App.

Aby rozpocząć, zainstaluj zestaw SDK platformy .NET 9 i zainstaluj .NET MAUI obciążenie, które zawiera szablon:

dotnet workload install maui

Stwórz rozwiązanie z szablonu projektu w wierszu poleceń przy użyciu następującego polecenia:

dotnet new maui-blazor-web

Szablon jest również dostępny w programie Visual Studio.

Uwaga

Obecnie wyjątek występuje, jeśli Blazor tryby renderowania są definiowane na poziomie poszczególnych stron/składników. Aby uzyskać więcej informacji, zobacz BlazorWebView potrzebuje metody włączenia zastępowania ResolveComponentForRenderMode (dotnet/aspnetcore #51235).

Aby uzyskać więcej informacji, zobacz Tworzenie .NET MAUIBlazor Hybrid aplikacji przy użyciu elementu Blazor Web App.

Wykrywanie lokalizacji renderowania, interakcyjności i przypisanego trybu renderowania w czasie wykonywania

Wprowadziliśmy nowy interfejs API zaprojektowany w celu uproszczenia procesu wykonywania zapytań dotyczących stanów składników w czasie wykonywania. Ten interfejs API zapewnia następujące możliwości:

  • Określ bieżącą lokalizację wykonywania składnika: może to być przydatne do debugowania i optymalizowania wydajności składników.
  • Sprawdź, czy składnik jest uruchomiony w środowisku interaktywnym: może to być przydatne w przypadku składników, które mają różne zachowania w oparciu o interakcyjność ich środowiska.
  • Pobieranie przypisanego trybu renderowania dla składnika: Zrozumienie trybu renderowania może pomóc w optymalizacji procesu renderowania i poprawie ogólnej wydajności składnika.

Aby uzyskać więcej informacji, zobacz tryby renderowania ASP.NET CoreBlazor.

Ulepszone doświadczenie ponownego łączenia po stronie serwera

Następujące ulepszenia zostały wprowadzone do domyślnego procesu ponownego nawiązywania połączenia po stronie serwera:

  • Gdy użytkownik przechodzi z powrotem do aplikacji z odłączonym obwodem, następuje natychmiastowe ponowne nawiązanie połączenia, a nie oczekiwanie na czas trwania następnego interwału ponownego połączenia. Poprawia to doświadczenie użytkownika podczas przechodzenia do aplikacji na karcie przeglądarki, która przeszła w stan uśpienia.

  • Gdy próba ponownego nawiązania połączenia osiągnie serwer, ale serwer już zwolnił obwód, nastąpi automatyczne odświeżenie strony. Uniemożliwia to użytkownikowi ręczne odświeżenie strony, jeśli prawdopodobnie spowoduje to pomyślne ponowne nawiązanie połączenia.

  • Czas ponownego nawiązywania połączenia używa obliczonej strategii wycofywania. Domyślnie pierwsze kilka ponownych prób nawiązania połączenia odbywa się w krótkim odstępie czasu bez interwału ponawiania prób przed wprowadzeniem obliczonych opóźnień między próbami. Zachowanie interwału ponawiania prób można dostosować, określając funkcję do obliczenia interwału ponawiania, jak pokazano w poniższym przykładzie zastosowania wykładniczego opóźnienia:

    Blazor.start({
      circuit: {
        reconnectionOptions: {
          retryIntervalMilliseconds: (previousAttempts, maxRetries) => 
            previousAttempts >= maxRetries ? null : previousAttempts * 1000
        },
      },
    });
    
  • Styl domyślnego interfejsu użytkownika ponownego łączenia został unowocześniony.

Aby uzyskać więcej informacji, zobacz wskazówki dotyczące platformy ASP.NET CoreBlazorSignalR.

Uproszczona serializacja stanu uwierzytelniania dla Blazor Web App

Nowe interfejsy API ułatwiają dodawanie uwierzytelniania do istniejącego Blazor Web App. Po utworzeniu nowego Blazor Web App z uwierzytelnianiem przy użyciu indywidualnych kont i włączeniu interaktywności opartej na WebAssembly, w projekcie znajduje się niestandardowy AuthenticationStateProvider zarówno w projekcie serwera, jak i w projekcie klienta.

Ci dostawcy przekazują stan uwierzytelniania użytkownika do przeglądarki. Uwierzytelnianie na serwerze zamiast na kliencie pozwala aplikacji uzyskać dostęp do stanu uwierzytelniania podczas prerenderingu i zanim zostanie zainicjowane środowisko uruchomieniowe .NET WebAssembly.

Implementacje niestandardowe AuthenticationStateProvider używają usługi stanu trwałego składnika (PersistentComponentState), aby serializować stan uwierzytelniania w komentarzach HTML i odczytywać go z powrotem z zestawu WebAssembly w celu utworzenia nowego AuthenticationState wystąpienia.

Działa to dobrze, jeśli rozpoczęto pracę z szablonu projektu Blazor Web App i wybrano opcję Konta indywidualne, ale jest to dużo kodu do samodzielnego zaimplementowania lub skopiowania, jeśli próbujesz dodać do istniejącego projektu uwierzytelnianie. Istnieją teraz interfejsy API, które są częścią szablonu projektu Blazor Web App, i można je wywoływać w projektach serwera i klienta, aby dodać tę funkcjonalność.

Domyślnie interfejs API serializuje tylko nazwę oraz oświadczenia roli po stronie serwera w celu uzyskania dostępu w przeglądarce. Opcję można przekazać do AddAuthenticationStateSerialization, aby uwzględnić wszystkie roszczenia.

Aby uzyskać więcej informacji, zapoznaj się z poniższymi częściami ASP.NET Core Blazor uwierzytelnianie i autoryzacja:

Dodawanie statycznych stron renderowania po stronie serwera (SSR) do strony globalnie interakcyjnej Blazor Web App

W wersji platformy .NET 9 jest teraz prostsze dodawanie statycznych stron SSR do aplikacji, które przyjmują globalną interakcyjność.

Takie podejście jest przydatne tylko wtedy, gdy aplikacja ma określone strony, które nie mogą działać z interaktywnym renderowaniem na serwerze lub w WebAssembly. Na przykład zastosuj to podejście dla stron, które zależą od odczytywania/zapisywania plików cookie HTTP i mogą działać tylko w cyklu żądania/odpowiedzi zamiast renderowania interakcyjnego. W przypadku stron, które działają z renderowaniem interaktywnym, nie należy wymuszać używania statycznego renderowania SSR, ponieważ jest mniej wydajne i mniej elastyczne dla użytkownika końcowego.

Oznacz dowolną Razor stronę składnika przy użyciu nowego[ExcludeFromInteractiveRouting]atrybutu przypisanego @attributeRazor do dyrektywy:

@attribute [ExcludeFromInteractiveRouting]

Zastosowanie atrybutu powoduje przejście do strony i opuszczenie routingu interakcyjnego. Nawigacja przychodząca jest zmuszona do pełnego załadowania strony, zamiast rozwiązywania strony za pośrednictwem interaktywnego routingu. Ponowne ładowanie pełnej strony wymusza, aby główny komponent najwyższego poziomu, zazwyczaj komponent App (App.razor), został ponownie załadowany z serwera, co pozwala aplikacji przełączać się na inny tryb renderowania najwyższego poziomu.

RazorComponentsEndpointHttpContextExtensions.AcceptsInteractiveRouting Metoda rozszerzenia umożliwia składnikowi wykrywanie, czy [ExcludeFromInteractiveRouting] atrybut jest stosowany do bieżącej strony.

W składniku App użyj wzorca w poniższym przykładzie:

  • Strony, które nie są oznaczone atrybutem [ExcludeFromInteractiveRouting], domyślnie przechodzą w tryb renderowania InteractiveServer z globalną interakcyjnością. Możesz zastąpić InteractiveServer na InteractiveWebAssembly lub InteractiveAuto, aby określić inny domyślny globalny tryb renderowania.
  • Strony z adnotacjami atrybutu [ExcludeFromInteractiveRouting] przyjmują statyczny SSR (PageRenderMode jest przypisany 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;
}

Alternatywą dla użycia metody rozszerzenia RazorComponentsEndpointHttpContextExtensions.AcceptsInteractiveRouting jest ręczne odczytywanie metadanych punktu końcowego przy użyciu HttpContext.GetEndpoint()?.Metadata.

Ta funkcja jest objęta dokumentacją referencyjną w trybach renderowania ASP.NET Core.

Wstrzykiwanie konstruktora

Razor składniki obsługują wstrzykiwanie konstruktora.

W poniższym przykładzie klasa częściowa (code-behind) wprowadza usługę NavigationManager za pomocą podstawowego konstruktora :

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

Aby uzyskać więcej informacji, zobacz wstrzykiwanie zależności ASP.NET Core.

Kompresja protokołu Websocket dla składników interactive server

Domyślnie składniki Interaktywnego Serwera umożliwiają kompresję dla połączeń WebSocket i ustawiają dyrektywę Polityki Bezpieczeństwa Treści (CSP) na 'self', która zezwala tylko na osadzanie aplikacji w elemencie <iframe> źródła, z którego aplikacja jest obsługiwana, gdy kompresja jest włączona lub gdy dostępna jest konfiguracja kontekstu protokołu WebSocket.

Kompresję można wyłączyć, ustawiając wartość ConfigureWebSocketOptions na null, co zmniejsza podatność aplikacji na ataki, ale może spowodować zmniejszenie wydajności:

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

Skonfiguruj bardziej rygorystyczną politykę CSP z wartością 'none' (należy użyć pojedynczych cudzysłowów), która umożliwia kompresję WebSocket, ale uniemożliwia przeglądarkom osadzanie aplikacji w dowolnym elemencie <iframe>.

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

Aby uzyskać więcej informacji, zobacz następujące zasoby:

Obsługa zdarzeń kompozycji klawiatury w programie Blazor

Nowa KeyboardEventArgs.IsComposing właściwość wskazuje, czy zdarzenie klawiatury jest częścią sesji kompozycji. Śledzenie stanu kompozycji zdarzeń klawiatury ma kluczowe znaczenie dla obsługi międzynarodowych metod wprowadzania znaków.

Dodano parametr OverscanCount do elementu QuickGrid

Składnik QuickGrid uwidacznia teraz właściwość określającą OverscanCount , ile dodatkowych wierszy jest renderowanych przed i po widocznym regionie po włączeniu wirtualizacji.

Wartość domyślna OverscanCount to 3. Poniższy przykład zwiększa OverscanCount wartość do 4:

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

InputNumber składnik obsługuje type="range" atrybut

Składnik InputNumber<TValue> teraz obsługuje atrybut type="range", który tworzy element zakresu obsługujący powiązanie modelu i walidację formularza, zazwyczaj renderowany jako suwak lub regulator, a nie pole tekstowe.

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

Nowe rozszerzone zdarzenia nawigacyjne

Wyzwalaj wywołania zwrotne JavaScript przed lub po ulepszonej nawigacji z użyciem nowych nasłuchiwaczy zdarzeń.

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

Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor JavaScript ze statycznym renderowaniem po stronie serwera (static SSR).

Przesyłanie strumieniowe żądań po stronie klienta

Interaktywne renderowanie WebAssembly w Blazor obsługuje teraz przesyłanie strumieniowe żądań po stronie klienta przy użyciu opcji request.SetBrowserReqeustStreamingEnabled(true) na HttpRequestMessage.

Aby uzyskać więcej informacji, zobacz następujące zasoby:

SignalR

W tej sekcji opisano nowe funkcje programu SignalR.

Obsługa typu polimorficznego w Hubach SignalR

Metody koncentratora mogą teraz akceptować klasę bazową zamiast klasy pochodnej, aby umożliwić scenariusze polimorficzne. Typ podstawowy musi być oznakowany, aby umożliwić polimorfizm.

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

Ulepszone aktywności dla SignalR

SignalR teraz ma element ActivitySource zarówno dla serwera koncentratora, jak i klienta.

Źródło aktywności serwera .NET SignalR

SignalR ActivitySource o nazwie Microsoft.AspNetCore.SignalR.Server emituje zdarzenia dla wywołań metod w hubie:

  • Każda metoda jest swoją własną aktywnością, więc wszystko, co emituje aktywność podczas wywołania metody centrum, jest częścią aktywności tej metody centrum.
  • Działania metody Hub nie mają elementu nadrzędnego. Oznacza to, że nie są połączone w ramach długotrwałego połączenia SignalR.

W poniższym przykładzie użyto pulpitu nawigacyjnego .NET Aspire i pakietów 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" />

Dodaj następujący kod startowy Program.cs do pliku:

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

Poniżej przedstawiono przykładowe dane wyjściowe z pulpitu nawigacyjnego Aspire:

Lista działań dla zdarzeń wywołania metody huba

Klient .NET SignalR ActivitySource

SignalR ActivitySource o nazwie Microsoft.AspNetCore.SignalR.Client emituje zdarzenia dla klienta SignalR:

  • Klient platformy .NET SignalR ma ActivitySource o nazwie Microsoft.AspNetCore.SignalR.Client. Teraz wywołania hubu tworzą przedział klienta. Należy pamiętać, że inni klienci SignalR, tacy jak klient JavaScript, nie obsługują śledzenia. Ta funkcja zostanie dodana do większej liczby klientów w przyszłych wersjach.
  • Wywołania hubu po stronie klienta i serwera obsługują propagację kontekstu . Propagacja kontekstu śledzenia umożliwia prawdziwie rozproszone śledzenie. Teraz można zobaczyć przepływ wywołań z klienta do serwera i z powrotem.

Oto jak te nowe działania wyglądają na pulpicie nawigacyjnym .NET Aspire:

SignalR rozproszone śledzenie na pulpicie nawigacyjnym Aspire

SignalR obsługuje przycinanie i natywną funkcję AOT

Kontynuowanie natywnej podróży AOT rozpoczętej na platformie .NET 8 umożliwiło przycinanie i natywną obsługę kompilacji przed czasem (AOT) zarówno SignalR dla scenariuszy klienta, jak i serwera. Teraz możesz skorzystać z zalet wydajności, jakie oferuje Native AOT, w aplikacjach, które wykorzystują SignalR do komunikacji internetowej w czasie rzeczywistym.

Wprowadzenie

Zainstaluj najnowszy zestaw .NET 9 SDK.

Utwórz rozwiązanie na podstawie szablonu webapiaot w powłoce poleceń przy użyciu następującego polecenia:

dotnet new webapiaot -o SignalRChatAOTExample

Zastąp zawartość Program.cs pliku następującym SignalR kodem:

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

W poprzednim przykładzie jest generowany natywny plik wykonywalny systemu Windows o wartości 10 MB i plik wykonywalny systemu Linux o wartości 10,9 MB.

Ograniczenia

  • Obecnie obsługiwany jest tylko protokół JSON:
    • Jak pokazano w poprzednim kodzie, aplikacje korzystające z serializacji JSON i natywnego AOT muszą używać System.Text.Json generatora źródła.
    • Jest to zgodne z tym samym podejściem co minimalne interfejsy API.
  • Na serwerze SignalR, parametry metody koncentratora typu IAsyncEnumerable<T> i ChannelReader<T>, gdzie T jest typem wartości (struct), nie są obsługiwane. Użycie tych typów powoduje wyjątek środowiska uruchomieniowego podczas uruchamiania zarówno w trakcie tworzenia aplikacji, jak i w opublikowanej aplikacji. Aby uzyskać więcej informacji, zobacz Używanie IAsyncEnumerable<T> i ChannelReader<T> z typami wartości w natywnym AOT (dotnet/aspnetcore #56179).
  • Silnie typizowane koncentratory nie są obsługiwane w przypadku natywnej AOT (PublishAot). Używanie silnie typowanych hubów z natywnym AOT spowoduje ostrzeżenia podczas kompilacji i publikowania, a także wyjątek w czasie wykonywania. Używanie silnie typiowanych koncentratorów z przycinaniem (PublishedTrimmed) jest obsługiwane.
  • Tylko Task, Task<T>, ValueTasklub ValueTask<T> są obsługiwane w przypadku typów zwracanych asynchronicznych.

Minimalistyczne interfejsy API

W tej sekcji opisano nowe funkcje dla minimalnych interfejsów API.

Dodano InternalServerError i InternalServerError<TValue> do TypedResults

Klasa TypedResults jest przydatnym narzędziem do zwracania silnie typizowanych odpowiedzi opartych na kodzie stanu HTTP z minimalnego API. TypedResults Teraz zawiera metody fabryczne i typy zwracania odpowiedzi "500 Wewnętrzny błąd serwera" z punktów końcowych. Oto przykład, który zwraca kod odpowiedzi 500:

var app = WebApplication.Create();

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

app.Run();

Wywołaj ProducesProblem i ProducesValidationProblem w grupach tras

Metody rozszerzenia ProducesProblem i ProducesValidationProblem zostały zaktualizowane, aby mogły być używane w grupach tras. Te metody wskazują, że wszystkie punkty końcowe w grupie tras mogą zwracać odpowiedzi ProblemDetails lub ValidationProblemDetails na potrzeby metadanych 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);

Typy wyników Problem i ValidationProblem obsługują konstrukcję z wartościami IEnumerable<KeyValuePair<string, object?>>

Przed .NET 9, konstruowanie typów wyników Problem i Problem walidacji w minimalnych interfejsach API wymagało zainicjowania właściwości errors i extensions przy użyciu implementacji IDictionary<string, object?>. W tej wersji te interfejsy API konstrukcji obsługują przeciążenia, które zużywają wartość 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);
});

Dzięki użytkownikowi GitHub joegoldman2 za ten wkład!

OpenAPI

W tej sekcji opisano nowe funkcje interfejsu OpenAPI

Wbudowana obsługa generowania dokumentów OpenAPI

Specyfikacja OpenAPI jest standardem opisującym HTTP API. Standard umożliwia deweloperom definiowanie kształtu interfejsów API, które mogą być podłączone do generatorów klientów, generatorów serwerów, narzędzi do testowania, dokumentacji i nie tylko. Na platformie .NET 9 platforma ASP.NET Core zapewnia wbudowaną obsługę generowania dokumentów OpenAPI reprezentujących interfejsy API oparte na kontrolerach lub minimalnych interfejsach API za pośrednictwem pakietu Microsoft.AspNetCore.OpenApi .

Następujące wyróżnione wywołania kodu:

  • AddOpenApi aby zarejestrować niezbędne zależności w kontenerze DI aplikacji.
  • MapOpenApi aby zarejestrować wymagane punkty końcowe OpenAPI w trasach aplikacji.
var builder = WebApplication.CreateBuilder();

builder.Services.AddOpenApi();

var app = builder.Build();

app.MapOpenApi();

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

app.Run();

Microsoft.AspNetCore.OpenApi Zainstaluj pakiet w projekcie przy użyciu następującego polecenia:

dotnet add package Microsoft.AspNetCore.OpenApi

Uruchom aplikację i przejdź do openapi/v1.json strony , aby wyświetlić wygenerowany dokument OpenAPI:

Dokument OpenAPI

Dokumenty OpenAPI można również wygenerować w czasie kompilacji, dodając pakiet Microsoft.Extensions.ApiDescription.Server.

dotnet add package Microsoft.Extensions.ApiDescription.Server

Aby zmodyfikować lokalizację emitowanych dokumentów OpenAPI, ustaw ścieżkę docelową we właściwości OpenApiDocumentsDirectory w pliku projektu aplikacji:

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

Uruchom dotnet build i sprawdź wygenerowany plik JSON w katalogu projektu.

Generowanie dokumentu OpenAPI w czasie kompilacji

Wbudowana generacja dokumentów OpenAPI platformy ASP.NET Core zapewnia obsługę różnych dostosowań i opcji. Zapewnia ona przekształcanie dokumentów, operacji i schematów oraz umożliwia zarządzanie wieloma dokumentami OpenAPI dla tej samej aplikacji.

Aby dowiedzieć się więcej o nowych możliwościach dokumentów openAPI w programie ASP.NET Core, zobacz nowe dokumenty Microsoft.AspNetCore.OpenApi.

Rozwiązanie Microsoft.AspNetCore.OpenApi obsługuje przycinanie i natywną AOT

OpenAPI w ASP.NET Core obsługuje przycinanie i natywną obsługę AOT. Poniższe kroki umożliwiają utworzenie i opublikowanie aplikacji OpenAPI z przycinaniem i natywną funkcją AOT:

Utwórz nowy projekt ASP.NET Core Web API (Native AOT).

dotnet new webapiaot

Dodaj pakiet Microsoft.AspNetCore.OpenAPI.

dotnet add package Microsoft.AspNetCore.OpenApi

Zaktualizuj Program.cs, aby umożliwić generowanie dokumentów OpenAPI.

+ builder.Services.AddOpenApi();

var app = builder.Build();

+ app.MapOpenApi();

Opublikuj aplikację.

dotnet publish

Uwierzytelnianie i autoryzacja

W tej sekcji opisano nowe funkcje uwierzytelniania i autoryzacji.

Program OpenIdConnectHandler dodaje obsługę wypychanych żądań autoryzacji (PAR)

Chcielibyśmy podziękować Joe DeCock z Duende Software za dodanie wypychanych żądań autoryzacji (PAR) do ASP.NET Core OpenIdConnectHandler. Joe opisał tło i motywację do włączenia PAR w swojej propozycji interfejsu API w następujący sposób:

Wypychane żądania autoryzacji (PAR) to stosunkowo nowy standard OAuth, który poprawia bezpieczeństwo przepływów OAuth i OIDC przez przeniesienie parametrów autoryzacji z kanału frontu do kanału wstecznego. Oznacza to przeniesienie parametrów autoryzacji z adresów URL przekierowań w przeglądarce do bezpośrednich wywołań HTTP pomiędzy serwerami na zapleczu.

Zapobiega to cyberatakom w przeglądarce:

  • Wyświetlanie parametrów autoryzacji, które mogą ujawniać dane osobowe.
  • Manipulowanie tymi parametrami. Na przykład cyberatak może zmienić żądany zakres dostępu.

Wypychanie parametrów autoryzacji sprawia, że adresy URL żądań są krótkie. Parametry autoryzacji mogą być bardzo długie w przypadku korzystania z bardziej złożonych funkcji OAuth i OIDC, takich jak zaawansowane żądania autoryzacji. Długie adresy URL powodują problemy w wielu przeglądarkach i infrastrukturach sieciowych.

Korzystanie z PAR jest zachęcane przez grupę roboczą FAPI w ramach OpenID Foundation. Na przykład profil zabezpieczeń FAPI2.0 wymaga użycia PAR. Ten profil zabezpieczeń jest używany przez wiele grup pracujących nad otwartą bankowością (przede wszystkim w Europie), w opiece zdrowotnej i w innych branżach z wysokimi wymaganiami dotyczącymi bezpieczeństwa.

PAR jest obsługiwany przez wielu dostawców tożsamości, w tym

W przypadku platformy .NET 9 zdecydowaliśmy się domyślnie włączyć PAR, jeśli dokument odnajdywania dostawcy tożsamości ogłasza obsługę PAR, gdyż powinno to zapewnić dodatkowe zabezpieczenia dla dostawców, którzy go obsługują. Dokument wykrywania dostawcy tożsamości znajduje się zwykle w .well-known/openid-configuration. Jeśli powoduje to problemy, możesz wyłączyć PAR za pośrednictwem OpenIdConnectOptions.PushedAuthorizationBehavior w następujący sposób:

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

Aby upewnić się, że uwierzytelnianie powiedzie się tylko przy użyciu PAR, zamiast tego użyj PushedAuthorizationBehavior.Require. Ta zmiana wprowadza również nowe zdarzenie OnPushAuthorization do biblioteki OpenIdConnectEvents, które może służyć do dostosowywania wypychanego żądania autoryzacji lub obsługi go ręcznie. Zobacz propozycję interfejsu API, aby uzyskać więcej szczegółów.

Dostosowywanie parametrów OIDC i OAuth

Programy obsługi uwierzytelniania OAuth i OIDC teraz oferują AdditionalAuthorizationParameters opcję ułatwiającą dostosowanie parametrów wiadomości autoryzacyjnej, które zazwyczaj są częścią ciągu zapytania przekierowania. W .NET 8 i wcześniejszych wersjach wymaga to niestandardowego wywołania zwrotnego OnRedirectToIdentityProvider lub zastąpienia metody BuildChallengeUrl w niestandardowym handlerze. Oto przykład kodu platformy .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;
    };
});

Powyższy przykład można teraz uprościć do następującego kodu:

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

Konfiguracja rozszerzonych flag uwierzytelniania HTTP.sys

Teraz można skonfigurować flagi HTTP_AUTH_EX_FLAG_ENABLE_KERBEROS_CREDENTIAL_CACHING i HTTP_AUTH_EX_FLAG_CAPTURE_CREDENTIAL HTTP.sys, używając nowych właściwości EnableKerberosCredentialCaching i CaptureCredentials w AuthenticationManager HTTP.sys, aby zoptymalizować sposób obsługi uwierzytelniania systemu Windows. Na przykład:

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

Różne

W poniższych sekcjach opisano różne nowe funkcje.

Nowa HybridCache biblioteka

Ważne

HybridCache jest obecnie nadal dostępne w wersji zapoznawczej, ale zostanie w pełni wydane po wersji .NET 9.0 w przyszłej mniejszej wersji rozszerzeń .NET.

HybridCache API łączy pewne luki w istniejących interfejsach API IDistributedCache i IMemoryCache. Dodaje również nowe funkcje, takie jak:

  • Ochrona "Stampede", aby zapobiec równoległym pobieraniu tych samych zadań.
  • Konfigurowalna serializacja.

HybridCache jest przeznaczony do bezpośredniego zastąpienia istniejących rozwiązań IDistributedCache i IMemoryCache, a także udostępnia prosty interfejs API do dodawania nowego kodu buforowania. Zapewnia ujednolicony interfejs API zarówno do krótko- i długoterminowego przechowywania danych w trakcie procesu, jak i poza nim.

Aby zobaczyć, jak HybridCache interfejs API jest uproszczony, porównaj go z kodem, który używa metody IDistributedCache. Oto przykład użycia 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)
    { /* ... */ }
}

To dużo pracy, aby wszystko zrobić poprawnie za każdym razem, w tym rzeczy takie jak serializacja. W scenariuszu chybienia pamięci podręcznej można znaleźć wiele współbieżnych wątków, z których każdy doświadcza chybienia, pobiera dane bazowe, serializuje je i wysyła te dane do pamięci podręcznej.

Aby uprościć i ulepszyć ten kod za pomocą HybridCache, najpierw musimy dodać nową bibliotekę Microsoft.Extensions.Caching.Hybrid:

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

Zarejestruj usługę HybridCache, tak jak zarejestrowałbyś implementację IDistributedCache.

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

Teraz większość problemów z buforowaniem można odciążyć do 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
        );
    }
}

Udostępniamy konkretną implementację klasy abstrakcyjnej HybridCache za pośrednictwem wstrzykiwania zależności, ale deweloperzy mogą udostępniać niestandardowe implementacje interfejsu API. Implementacja HybridCache dotyczy wszystkich elementów związanych z buforowaniem, w tym obsługą operacji współbieżnych. Token cancel w tym miejscu reprezentuje połączone anulowanie wszystkich współbieżnych wywołań — nie tylko anulowanie obiektu wywołującego, które widzimy (czyli token).

Scenariusze o dużej przepustowości można dodatkowo zoptymalizować przy użyciu wzorca TState, aby uniknąć narzutów związanych z przechwytywanymi zmiennymi oraz wywołań zwrotnych dla poszczególnych wystąpień.

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 używa skonfigurowanej IDistributedCache implementacji, jeśli istnieje, na potrzeby pomocniczego buforowania poza procesem, na przykład przy użyciu usługi Redis. Ale nawet bez IDistributedCache, usługa HybridCache nadal zapewnia buforowanie w trakcie procesu i ochronę przed przeciążeniem.

Uwaga dotycząca ponownego użycia obiektu

W typowym istniejącym kodzie, który używa IDistributedCache, każde pobieranie obiektu z pamięci podręcznej powoduje deserializację. To zachowanie oznacza, że każdy współbieżny obiekt wywołujący otrzymuje oddzielne wystąpienie obiektu, które nie może wchodzić w interakcje z innymi wystąpieniami. Rezultatem jest bezpieczeństwo przy pracy z wątkami, ponieważ eliminuje się ryzyko równoczesnych modyfikacji tego samego wystąpienia obiektu.

Ponieważ wiele przypadków użycia zostanie dostosowanych z istniejącego HybridCache kodu IDistributedCache, HybridCache zachowuje tę funkcjonalność domyślnie, aby uniknąć wprowadzania usterek związanych z współbieżnością. Jednak dany przypadek użycia jest z natury bezpieczny wątkowo:

  • Jeśli buforowane typy są niezmienne.
  • Jeśli kod nie zmodyfikuje ich.

W takich przypadkach należy poinformować HybridCache, że ponowne użycie wystąpień jest bezpieczne, np.:

  • Oznaczanie typu jako sealed. Słowo sealed kluczowe w języku C# oznacza, że nie można dziedziczyć klasy.
  • Zastosowanie do niego atrybutu [ImmutableObject(true)] . Atrybut [ImmutableObject(true)] wskazuje, że nie można zmienić stanu obiektu po jego utworzeniu.

Przez ponowne użycie instancji HybridCache może zmniejszyć obciążenie CPU i alokacji obiektów związane z deserializacją poszczególnych wywołań. Może to prowadzić do poprawy wydajności w scenariuszach, w których buforowane obiekty są duże lub często używane.

Inne HybridCache funkcje

Podobnie jak IDistributedCache, HybridCache obsługuje usuwanie według klucza za pomocą RemoveKeyAsync metody .

HybridCache udostępnia również opcjonalne interfejsy API dla implementacji IDistributedCache, aby uniknąć alokacji byte[]. Ta funkcja jest implementowana przez wersje zapoznawcze pakietów Microsoft.Extensions.Caching.StackExchangeRedis i Microsoft.Extensions.Caching.SqlServer.

Serializacja jest konfigurowana w ramach rejestrowania usługi, z obsługą serializatorów specyficznych dla typu i uogólnionych za pomocą metod WithSerializer i .WithSerializerFactory, w łańcuchu wywołań od wywołania AddHybridCache. Domyślnie biblioteka wewnętrznie obsługuje string i byte[], a do wszystkiego pozostałego używa System.Text.Json, ale możesz użyć protobuf, xml lub czegoś innego.

HybridCache obsługuje starsze środowiska uruchomieniowe platformy .NET, aż do wersji .NET Framework 4.7.2 i .NET Standard 2.0.

Aby uzyskać więcej informacji na temat HybridCache, zobacz bibliotekę HybridCache w programie ASP.NET Core

Ulepszenia strony wyjątków dla deweloperów

Strona wyjątku dewelopera platformy ASP.NET Core jest wyświetlana, gdy aplikacja zgłasza nieobsługiwany wyjątek podczas pracy deweloperskiej. Strona wyjątku dla deweloperów zawiera szczegółowe informacje o wyjątku i żądaniu.

Wersja zapoznawcza 3 dodała metadane punktu końcowego do strony wyjątków dla dewelopera. ASP.NET Core używa metadanych punktu końcowego do kontrolowania zachowania punktu końcowego, takich jak routing, buforowanie odpowiedzi, ograniczanie szybkości, generowanie interfejsu OpenAPI i nie tylko. Na poniższym obrazku przedstawiono nowe metadane w sekcji Routing strony błędu dewelopera:

Nowe informacje o metadanych na stronie wyjątku dewelopera

Podczas testowania strony wyjątków dla deweloperów zidentyfikowano drobne usprawnienia. Zostały one wysłane w wersji zapoznawczej 4:

  • Lepsze zawijanie tekstu. Długie pliki cookie, wartości ciągu zapytania i nazwy metod nie dodają już poziomych pasków przewijania przeglądarki.
  • Większy tekst występujący w nowoczesnych projektach.
  • Bardziej spójne rozmiary tabel.

Na poniższej animowanej ilustracji przedstawiono nową stronę wyjątku dla deweloperów:

Nowa strona wyjątku dla deweloperów

Ulepszenia debugowania słownika

Wyświetlanie słowników i innych kolekcji klucz-wartość podczas debugowania ma ulepszony układ. Klucz jest wyświetlany w kolumnie dla kluczy w debugerze, zamiast być łączony z wartością. Na poniższych obrazach przedstawiono stary i nowy ekran słownika w debugerze.

Przed:

Poprzednie środowisko debugera

Po:

Nowe środowisko debugera

ASP.NET Core ma wiele kolekcji klucz-wartość. To ulepszone środowisko debugowania ma zastosowanie do:

  • Nagłówki HTTP
  • Ciągi zapytań
  • Formularze
  • Pliki cookie
  • Wyświetlanie danych
  • Dane dotyczące trasy
  • Funkcje

Poprawka dotycząca błędów 503 podczas recyklingu aplikacji w IIS

Domyślnie występuje 1-sekundowe opóźnienie między powiadomieniem usług IIS o ponownym uruchomieniu lub zamknięciu a momentem, gdy ANCM informuje serwer zarządzany o rozpoczęciu zamykania. Opóźnienie można skonfigurować za pomocą zmiennej środowiskowej ANCM_shutdownDelay lub przez ustawienie shutdownDelay programu obsługi. Obie wartości znajdują się w milisekundach. Opóźnienie polega głównie na zmniejszeniu prawdopodobieństwa wyścigu, w którym:

  • Usługi IIS nie rozpoczęły kolejkowania żądań, aby przejść do nowej aplikacji.
  • Usługa ANCM rozpoczyna odrzucanie nowych żądań, które trafiają do starej aplikacji.

Wolniejsze maszyny lub maszyny z większym użyciem CPU powinny rozważyć dostosowanie tej wartości, aby zmniejszyć prawdopodobieństwo wystąpienia błędu 503.

Przykład ustawienia 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>

Poprawka znajduje się w globalnie zainstalowanym module ANCM pochodzącym z pakietu hostingu.

ASP0026: Analizator ostrzega, gdy [Authorize] jest zastępowany przez [AllowAnonymous] z bardziej oddalonego miejsca.

Wydaje się intuicyjne, że [Authorize] atrybut umieszczony "bliżej" akcji MVC niż [AllowAnonymous] atrybut zastąpi [AllowAnonymous] atrybut i wymusza autoryzację. Jednak niekoniecznie tak jest. To, co ma znaczenie, to względna kolejność atrybutów.

Poniższy kod pokazuje przykłady, w których atrybut [Authorize] umieszczony bliżej jest zastępowany przez atrybut [AllowAnonymous] znajdujący się dalej.

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

W programie .NET 9 (wersja zapoznawcza 6) wprowadziliśmy analizator, który będzie wyróżniał takie przypadki, w których atrybut znajdujący się bliżej [Authorize] zostaje zastąpiony przez atrybut [AllowAnonymous], który jest bardziej oddalony od akcji MVC. Ostrzeżenie wskazuje na nadpisany [Authorize] atrybut z następującym komunikatem:

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

Prawidłowe działanie, które należy podjąć, jeśli zobaczysz to ostrzeżenie, zależy od zamiarów związanych z atrybutami. Atrybut odejścia [AllowAnonymous] powinien zostać usunięty, jeśli nieumyślnie uwidacznia punkt końcowy użytkownikom anonimowym. Jeśli atrybut [AllowAnonymous] miał zastąpić atrybut [Authorize] bliższy niż, można powtórzyć atrybut [AllowAnonymous] po atrybucie [Authorize], aby wyjaśnić intencję.

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

Ulepszone Kestrel metryki połączenia

Wprowadziliśmy znaczącą poprawę Kestrelmetryk połączeń, włączając metadane dotyczące przyczyny niepowodzenia połączenia. Metryka kestrel.connection.duration teraz zawiera przyczynę zamknięcia połączenia w atrybucie error.type.

Oto mała próbka error.type wartości:

  • tls_handshake_failed — Połączenie wymaga protokołu TLS, a uzgadnianie protokołu TLS nie powiodło się.
  • connection_reset — Połączenie zostało nieoczekiwanie zamknięte przez klienta, gdy żądania były w toku.
  • request_headers_timeout - Kestrel zamknął połączenie, ponieważ nie otrzymał nagłówków żądań w odpowiednim czasie.
  • max_request_body_size_exceeded — Kestrel połączenie zostało zamknięte, ponieważ przekazane dane przekroczyły maksymalny rozmiar.

Wcześniej diagnozowanie Kestrel problemów z połączeniem wymagało od serwera rejestrowania szczegółowego, niskopoziomowego logowania. Jednak dzienniki mogą być kosztowne do wygenerowania i przechowywania i może być trudne do znalezienia odpowiednich informacji wśród szumu.

Metryki to dużo tańsza alternatywa, którą można pozostawić w środowisku produkcyjnym przy minimalnym wpływie. Zebrane metryki mogą napędzać pulpity nawigacyjne i alerty. Po zidentyfikowaniu problemu na wysokim poziomie z metrykami można rozpocząć dalsze badanie przy użyciu rejestrowania i innych narzędzi.

Oczekujemy, że ulepszone metryki połączenia będą przydatne w wielu scenariuszach:

  • Badanie problemów z wydajnością spowodowanych krótkim okresem istnienia połączenia.
  • Obserwujemy ciągłe ataki zewnętrzne na Kestrel, które wpływają na wydajność i stabilność.
  • Zarejestrowano próby zewnętrznych ataków na Kestrel, którym zapobiegły wbudowane funkcje zabezpieczeń Kestrel.

Aby uzyskać więcej informacji, zobacz ASP.NET Core metrics (Metryki podstawowe ASP.NET).

Dostosuj punkty końcowe nazwanych potoków Kestrel

KestrelUlepszono obsługę nazwanych potoków, oferując zaawansowane opcje dostosowywania. Nowa CreateNamedPipeServerStream metoda w opcjach nazwanych potoków umożliwia dostosowywanie potoków dla poszczególnych punktów końcowych.

Przykładem, gdzie jest to przydatne, jest aplikacja Kestrel, która wymaga dwóch punktów końcowych potoku z różnym bezpieczeństwem dostępu. Opcja CreateNamedPipeServerStream może służyć do tworzenia potoków z niestandardowymi ustawieniami zabezpieczeń w zależności od nazwy potoku.

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 opcja wyboru kodu stanu na podstawie typu wyjątku

Nowa opcja podczas konfigurowania ExceptionHandlerMiddleware programu umożliwia deweloperom aplikacji wybranie kodu stanu, który ma być zwracany, gdy wystąpi wyjątek podczas obsługi żądań. Nowa opcja zmienia kod stanu ustawiany w odpowiedzi ProblemDetails z tej otrzymanej od ExceptionHandlerMiddleware.

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

Rezygnuj z metryk HTTP na niektórych punktach końcowych i żądaniach

Platforma .NET 9 wprowadza możliwość rezygnacji z metryk HTTP dla określonych punktów końcowych i żądań. Rezygnacja z rejestrowania metryk jest korzystna dla punktów końcowych często wywoływanych przez zautomatyzowane systemy, takie jak kontrole kondycji. Rejestrowanie metryk dla tych żądań jest zwykle niepotrzebne.

Żądania HTTP do punktu końcowego można wykluczyć z metryk, dodając metadane. Któryś z dwóch:

  • Dodaj atrybut [DisableHttpMetrics] do kontrolera interfejsu sieci Web API, SignalR huba lub usługi gRPC.
  • Wywołaj metodę DisableHttpMetrics podczas mapowania punktów końcowych podczas uruchamiania aplikacji:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();

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

Właściwość MetricsDisabled została dodana do IHttpMetricsTagsFeature elementu dla:

  • Zaawansowane scenariusze, w których żądanie nie odwzorowuje się na punkt końcowy.
  • Dynamiczne wyłączanie kolekcji metryk dla określonych żądań HTTP.
// 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);
});

Obsługa ochrony danych na potrzeby usuwania kluczy

Przed wprowadzeniem .NET 9 klucze ochrony danych były z założenia niezbywalne, aby zapobiec utracie danych. Usunięcie klucza powoduje, że jego chronione dane są nieodwracalne. Biorąc pod uwagę ich mały rozmiar, akumulacja tych kluczy zwykle stwarzała minimalny wpływ. Jednak w celu obsługi bardzo długotrwałych usług wprowadziliśmy opcję usuwania kluczy. Ogólnie rzecz biorąc, należy usunąć tylko stare klucze. Usuń klucze tylko wtedy, gdy możesz zaakceptować ryzyko utraty danych w zamian za oszczędność przestrzeni dyskowej. Zalecamy, aby klucze ochrony danych nie były usuwane.

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

Oprogramowanie pośredniczące obsługuje klucz di

Oprogramowanie pośredniczące teraz obsługuje Keyed DI zarówno w konstruktorze, jak i w metodzie Invoke/InvokeAsync:

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

Ufaj certyfikatowi programistycznemu ASP.NET Core HTTPS w systemie Linux

W dystrybucjach Linux opartych na Ubuntu i Fedora, dotnet dev-certs https --trust konfiguruje teraz certyfikat programistyczny ASP.NET Core HTTPS jako zaufany certyfikat dla:

  • Przeglądarki Chromium, na przykład Google Chrome, Microsoft Edge i Chromium.
  • Mozilla Firefox i przeglądarki pochodne od Mozilla.
  • Interfejsy API platformy .NET, na przykład HttpClient

--trust Wcześniej pracował tylko w systemach Windows i macOS. Zaufanie certyfikatu jest stosowane dla poszczególnych użytkowników.

Aby ustanowić zaufanie w programie OpenSSL, dev-certs narzędzie:

  • Umieszcza certyfikat w ~/.aspnet/dev-certs/trust
  • Uruchamia uproszczoną wersję narzędzia c_rehash openSSL w katalogu.
  • Prosi użytkownika o zaktualizowanie zmiennej środowiskowej SSL_CERT_DIR .

Aby ustanowić zaufanie do dotnet, narzędzie umieszcza certyfikat w My/Root magazynie certyfikatów.

Aby ustanowić zaufanie do baz danych NSS, jeśli istnieją, narzędzie przeszukuje katalog domowy w poszukiwaniu profili przeglądarki Firefox, ~/.pki/nssdbi ~/snap/chromium/current/.pki/nssdb. Dla każdego znalezionego katalogu narzędzie dodaje wpis do pliku nssdb.

Szablony zaktualizowane do najnowszych wersji bootstrap, jQuery i jQuery Validation

Szablony i biblioteki projektów ASP.NET Core zostały zaktualizowane w celu używania najnowszych wersji bootstrap, jQuery i jQuery Validation, w szczególności:

  • Bootstrap 5.3.3
  • jQuery 3.7.1
  • jQuery Validation 1.21.0