Nachdem in der letzten Ausgabe die in .NET 9.0 Preview 3 und 4 eingeführten Neuerungen in C# 13.0, in der Runtime, dem SDK und der Basisklassenbibliothek von .NET 9.0 sowie Entity Framework 9.0 thematisiert wurden, geht es dieses Mal um Ergänzungen in ASP.NET Core 9.0, Blazor 9.0 sowie WPF und MAUI in .NET 9.0.
Nach Preview 1 und 2 folgten für .NET 9.0 im April 2024 Preview 3 und im Mai Preview 4. Die Vorschauversionen stehen zum kostenfreien Download unter [1] bereit. Als Visual Studio-Version braucht man eine Preview von Version 17.11, nicht die stabile Version 17.10.
ASP.NET Core kann zur Entwicklungszeit eine Fehlerseite anzeigen: app.UseDeveloperExceptionPage(). Dieses Feature gibt es seit .NET Core 1.0. In .NET 9.0 wurde die Registerkarte Routing aber um weitere Informationen über den Endpunkt ergänzt (Abb. 1). Außerdem hat Microsoft die Seite optimiert mit größerer Schrift, verbesserten Abständen und Umbrüchen.
Abb. 1: Erweiterte Registerkarte „Routing“ in der Entwicklungszeitfehlerseite bei ASP.NET Core
Für die Generierung einer OpenAPI Specification [2] zu ASP.NET Core WebAPIs verwendet Microsoft seit .NET 5.0 in den WebAPI-Projektvorlagen das externe NuGet-Paket Swashbuckle.AspNetCore aus der Community. Dieses basiert auf einem der in ASP.NET Core mitgelieferten API Explorer (Klassen im Namensraum Microsoft.AspNetCore.Mvc.ApiExplore) [3]. Aus der Community gibt es auch noch andere auf diesem API Explorer basierende OpenAPI-Lösungen, z. B. NSwag [4] und Asp.Api.Versioning [5].
Nun wurde aber das Community-Paket Swashbuckle.AspNetCore nach dem Release 6.5 vom 11. Januar 2023 nicht mehr gepflegt. Eine wesentliche Funktion fehlte für .NET 8.0: Die Zusammenarbeit mit dem Native AOT Compiler, den Microsoft in .NET 8.0 auf WebAPIs ausgedehnt hatte [6]. In .NET 8.0 gibt es zwar eine Projektvorlage für WebAPIs mit Native AOT, aber ohne OpenAPI. Zudem stellt Safia Abdalla aus dem ASP.NET-Core-Entwicklungsteam fest: „The evolution of our OpenAPI ‚support‘ has resulted in a large quantity of bugs and feature gaps“ [7].
Am 18. März 2024 kam konsequenterweise die Ankündigung von Microsoft [8], dass man nun die OpenAPI-Unterstützung komplett selbst entwickeln will und zwar in Form einer Ergänzung im seit .NET 7.0 bestehenden NuGet-Paket Microsoft.AspNetCore.OpenApi [9]. Microsoft.AspNetCore.OpenApi bietet seit .NET 7.0 die Methode WithOpenApi() für Minimal APIs.
Eine rudimentäre Unterstützung für OpenAPI ohne Communitypakete gibt es seit ASP.NET Core 9.0 Preview 4. Das Paket Microsoft.AspNetCore.OpenApi erzeugt bisher aber nur ein JSON-Dokument und (anders als Swashbuckle.AspNetCore) keine HTML-basierte Hilfe- und Testseiten für die WebAPIs. Laut [10] plant Microsoft, auch kein UI zu liefern: „Although it provides the benefit of an accessible UI for ad-hoc testing, it introduces engineering overhead around shipping (need to bundle web assets), has some security implications (it’s easy to accidently leak client secrets for certain authentication configurations), and introduces maintenance overhead (need to make sure that we upgrade swagger-ui as needed).“
Die ASP.NET-Core-WebAPI-Projektvorlagen verwenden auch in .NET 9.0 Preview 4 weiterhin Swashbuckle.AspNetCore, aber man kann die Projekte leicht umbauen, indem man die Aufrufe builder.Services.AddOpenApi(); und app.MapOpenApi(); in der Programmstartdatei Program.cs einfügt und dann das OpenAPI-JSON-Dokument unter dem relativen URL /openapi/v1.json abruft.
Auch Microsofts Ziel, dass OpenAPI damit gleichfalls mit Native AOT funktionieren soll, ist noch nicht erreicht, wie ein Test beim Erstellen dieses Beitrags zeigt. Nach dem Einbau von AddOpenApi() und MapOpenApi() in ein mit Native AOT kompiliertes WebAPI kommt es zum Laufzeitfehler: „InvalidOperationException: Reflection-based serialization has been disabled for this application“, obwohl die Dokumentation unter [11] zumindest in einem Satz andeutet „Compatible with native AoT“.
Zudem gibt es eine spannende Wende bei Swashbuckle.AspNetCore: Das Projekt lebt nun doch wieder und läuft mit Native AOT seit Version 6.6.0 vom 14. Mai 2024 [12] (siehe Pull Request). Man wird sehen müssen, ob Microsoft weiterhin den eigenen Weg gehen wird oder doch zurück zu Swashbuckle.AspNetCore rudert.
Die seit .NET 7.0 verfügbare Klasse Microsoft.AspNetCore.Http.TypedResults für ASP.NET-Core-basierte WebAPIs besitzt nun eine neue Methode InternalServerError(), die in .NET InternalServerError<T> zurückgibt und den Statuscode 500 (Internal Server Error) an den Client liefert. Microsoft zeigt TypedResults immer in Verbindung mit den ASP.NET Core Minimal WebAPIs, aber die Klasse lässt sich auch bei klassischen, Controller-basierten WebAPIs verwenden (Listing 1).
Listing 1: TypedResults.InternalServerError() liefert InternalServerError<T>
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
[Route("WeatherForecastMitFehler")]
[HttpGet]
public async Task<Results<Ok<WeatherForecast[]>, ForbidHttpResult, NotFound<string>, InternalServerError<string>, BadRequest<string>>> WeatherForecastMitFehler(int id)
{
try
{
if (id < 0) return TypedResults.Forbid();
if (id == 0) return TypedResults.NotFound("0 not allowed!");
if (id % 2 == 0) return TypedResults.InternalServerError("Ich habe leider einen schwachen Moment.");
return TypedResults.Ok(GetData().ToArray());
}
catch (Exception ex)
{
return TypedResults.BadRequest<string>(ex.Message);
}
}
…
}
Das ASP.NET Core Module (ANCM) in Microsofts Webserver Internet Information Services (IIS) besitzt in ASP.NET Core 9.0 eine neue Einstellung shutdownDelay. Mit dieser legen Betreiber:innen der Webanwendung fest, wie viele Millisekunden eine laufende Anwendung beim Recycling eines Anwendungsprozesses noch weiterlaufen soll.
Die Überlappung des alten und des neuen Prozesses gibt es schon seit langem. Sie stellt sicher, dass eine Anwendung auch beim Recycling (z. B. bei Codeaktualisierung) kontinuierlich erreichbar ist. Im Standard hat Microsoft diese Überlappung bisher auf 1 000 Millisekunden festgelegt, was aber in einigen Fällen (langsame System oder hohe CPU-Last) nicht ausreicht. Nun können Betreiber:innen einer Anwendung die Überlappung selbst per Einstellung in der Webserverkonfigurationsdatei Web.config steuern (Listing 2).
Listing 2
<aspNetCore processPath="dotnet" arguments="myapp.dll" stdoutLogEnabled="false" stdoutLogFile=".logsstdout">
<handlerSettings>
<!--
Milliseconds to delay shutdown of the old app app instance while the new instance starts.
Note: This doesn't delay the handling of incoming requests.
-->
<handlerSetting name="shutdownDelay" value="5000" />
</handlerSettings>
</aspNetCore>
Bereits seit .NET Core 1.0 gibt es für die Implementierung von Zwischenspeichern die Schnittstelle IDistributedCache [13]. Bisher gab es einige Implementierungen dieser Schnittstelle von Microsoft selbst:
Microsoft.Extensions.Caching.Redis.RedisCache
Microsoft.Extensions.Caching.StackExchangeRedis.RedisCache
Microsoft.Extensions.Caching.SqlServer.SqlServerCache
Microsoft.Extensions.Caching.Cosmos
Microsoft.Extensions.Caching.Distributed.MemoryDistributedCache (wobei dieser Cache nicht verteilt, sondern lokal arbeitet)
Außerdem gibt es Implementierungen von Amazon Web Services (AWS) [14] und aus der Community NCache [15]. Microsoft äußerte sich im Januar 2024 auf GitHub unzufrieden mit dieser Schnittstelle: „The distributed cache in asp.net (i.e. IDistributedCache) is not particularly developed; it is inconvenient to use, lacks many desirable features, and is inefficient“ [16]. So gibt es zum Beispiel in IDistributedCache keinen Schutz vor einem „Ansturm“ (engl. Stampede): Mehrere gleichzeitige Anforderungen für denselben nicht zwischengespeicherten Wert führen zu einer gleichzeitigen Backend-Last für dieselben Daten. Und um die Serialisierung der Daten in eine Bytefolge musste man sich selbst kümmern.
Der neue hybride Cache hat keine Implementierung der Schnittstelle IDistributedCache, sondern eine Abstraktion oberhalb der Implementierungen von IDistributedCache und vereint einen First-Level-Cache im RAM des lokalen Systems mit einem beliebigen Second-Level-Cache, der mit IDistributedCache arbeitet (Abb. 2). Daher der Name „hybrider Cache“. Den neuen hybriden Cache liefert Microsoft im NuGet-Paket Microsoft.Extensions.Caching.Hybrid, nicht nur für .NET 9.0, sondern auch für ältere Laufzeitumgebungen (.NET Framework 4.7.2 und .NET Standard 2.0).
Abb. 2: Der hybride Cache in .NET 9.0
Anstelle der notwendigen Kombination der Operationen GetAsync() und SetAsync() bietet der neue hybride Cache eine vereinfachte Methode GetOrCreateAsync() an (Listing 1). Dies ist ein Beispiel für lokales Caching. SetAsync() gibt...