Fremde WEB-APIs anzusprechen ist längst eine Standardaufgabe für viele C# / .Net Entwickler. Einige folgen streng den Prinzipien von REST, andere wiederum nur grob. Als Gemeinsamkeit haben sie, dass die API über eine Swagger-Datei dokumentiert wird. Swagger-Dateien beschreiben alle zur Verfügung stehenden Endpunkte zusammen mit den Datenobjekten, welche zum Dienst und zurück zum Client übertragen werden. Daneben gibt es noch weitere Informationen, etwa zu möglichen Fehlern oder zu Gültigkeitsbereichen einzelner Felder. In den letzten Jahren habe ich mehrere Projekte gesehen, welche die Swagger-Dokumentation als Basis für einen eigenen, typisierten HttpClient verwenden. Der typisierte HttpClient wurde dabei aber meist von Hand programmiert. Das muss nicht immer so sein. Mit NSwag [1] etwa steht ein Werkzeug zur Verfügung, mit dessen Hilfe ein typisierter HttpClient generiert werden kann. In diesem Artikel möchte ich das anhand einer ASP.Net Core Blazor Applikation zeigen.
Inhaltsverzeichnis
Die API
Die API für dieses Beispiel stammt aus dem Artikel „Hexagonale Architektur mit C#, .Net 6 und Blazor“ [2]. Es geht um eine Liste von Workshop-Objekten, welche per
- POST (neu anlegen),
- PUT (ändern),
- DELETE (löschen) und
- GET (ein Objekt lesen oder Objekte suchen)
verwaltet werden kann. Insgesamt also nichts kompliziertes. Das API Projekt generiert mittels Swashbuckle.AspNetCore eine Swagger-Datei und dazu die passende Swagger-UI zum Testen. Nachfolgend ein Screenshot aus der Swagger-UI.
Der generierte typisierte HttpClient im (Blazor) Frontend
Das Frontend ist mithilfe von ASP.Net Core Blazor gebaut. Der Client-Code wird also ebenso wie der Server in C# geschrieben. Die DTOs (Data Transfer Objects) können somit direkt wiederverwendet werden. Im Beispiel ist das durch das gemeinsame Projekt HappyCoding.HexagonalArchitecture.Application.Dtos gelöst. Der typisierte HttpClient dagegen muss für das Blazor Frontend separat entwickelt werden. Genau an dieser Stelle ist NSwag interessant, da es einen typisierten HttpClient anhand der Swagger-Datei des Servers generieren kann. Im Beispiel gehe ich den Weg über das NSwagStudio. Im nachfolgenden Screenshot sieht man darin die Konfiguration mit Swagger-Datei und Einstellungen für den generierten C# Code.
Allgemein bietet NSwag eine lange Liste an Einstellungen. Hierbei geht es um so schlichte Dinge wie den Namen des Namespace oder den Namen der HttpClient-Klasse. Zusätzlich geht es um Details wie die verwendeten Collection-Klassen. Für das Beispiel hier ist relevant, dass man das Generieren von DTOs deaktivieren kann. Diese teilen wir uns bereits mit dem Server, daher sollten diese nicht nochmal durch NSwag erzeugt werden. Im nachfolgenden Screenshot sehen wir die 3 für den typisierten Client relevanten Dateien im Beispielprojekt. WorkshopClient.GeneratedByNSwag.cs ist dabei der durch NSwag generierte C# Code. WorkshopClient.nswag ist die dazu verwendete Konfiguration und WorkshopClient.cs schließlich erweitert den generierten Client im Hinblick auf die verwendete Json Serializer Konfiguration.
Den generierten typisierten HttpClient im (Blazor) Frontend verwenden
Die Verwendung des generierten WorkshopClient ist relativ einfach. Zunächst muss er wie üblich innerhalb der Program.cs eingebunden werden. Nachfolgender Codeausschnitt zeigt das in Zeile 8.
public static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app"); builder.RootComponents.Add<HeadOutlet>("head::after"); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddScoped<IWorkshopClient, WorkshopClient>(); await builder.Build().RunAsync(); }
Anschließend kann der WorkshopClient in der Blazor Applikation verwendet werden. Nachfolgender Codeausschnitt zeigt etwa die Verwendung in der Detailseite zu einem Workshop-Objekt. Diese Seite dient als Userinterface zum Anlegen, Anzeigen und Ändern von Workshop-Objekten. Der Client wird mithilfe des Interface IWorkshopClient eingebunden. Dieses Interface wurde übrigens auch durch NSwag generiert, genauso wie dessen Implementierung. Obige Methoden aus der Swagger-Datei werden hier in Form der Methoden WorkshopsGETAsync, WorkshopsPOSTAsync und WorkshopsPUTAsync verwendet.
public partial class WorkshopDetail { [Parameter] public string WorkshopID { get; set; } // ... [Inject] public IWorkshopClient WorkshopClient { get; set; } = null!; // ... protected override async Task OnInitializedAsync() { await base.OnInitializedAsync(); if (string.IsNullOrEmpty(this.WorkshopID)) { this.IsCreating = true; this.EditingWorkshop = CreateEmptyWorkshop(); } else { this.IsCreating = false; this.EditingWorkshop = await this.WorkshopClient.WorkshopsGETAsync( Guid.Parse(this.WorkshopID), CancellationToken.None); } } private async Task SubmitWorkshopAsync() { if (this.IsCreating) { await this.WorkshopClient.WorkshopsPOSTAsync( this.EditingWorkshop, CancellationToken.None); } else { await this.WorkshopClient.WorkshopsPUTAsync( new WorkshopDto() { ID = Guid.Parse(this.WorkshopID), Project = EditingWorkshop.Project, Protocol = EditingWorkshop.Protocol, StartTimestamp = EditingWorkshop.StartTimestamp, Title = EditingWorkshop.Title }, CancellationToken.None); } this.Navigation.NavigateTo("/ui/workshops"); } // ... }
Fazit
Wir haben gesehen, wie man einen typisierten Client per NSwag aus einer Swagger-Datei heraus generiert und in einem C#-Projekt einbindet. Das ist aber nur ein Weg, wie man mit NSwag arbeiten kann. Dieser hat zur Folge, dass der generierte Code nach Änderungen an der API (und damit an der Swagger-Datei) durch den Entwickler händisch neu generiert werden muss. Aus diesem Grund habe ich die NSwag Konfiguration direkt im Projektverzeichnis mit abgelegt. Weiterhin ist es wichtig, dass am generierten Code nichts manuell angepasst wird. Solche manuelle Änderungen müssten schließlich nach jedem neuen Generieren wiederholt werden.
Man kann sich auch überlegen, NSwag direkt in den Build-Prozess zu integrieren. Auf einem solchen Weg kann man sicherstellen, dass der generierte Client immer zur API passt. Ich persönlich habe mich bei diesem Beispiel dagegen entschieden. Hintergrund ist, dass ich einen halbautomatischen Weg wie in diesem Beispiel meist als ersten Schritt sinnvoller finde. So ist es eine bewusste Entscheidung des Entwicklers, wann und wie er den typisierten Client auf Basis einer neuen Swagger-Datei generiert.
Downloads
- Quellcode des Beispiels aus diesem Artikel
https://www.rolandk.de/files/2022/HappyCoding.HexagonalArchitecture-ClientGen.zip
Verweise
- GitHub Repository von NSwag
https://github.com/RicoSuter/NSwag - Artikel „Hexagonale Architektur mit C#, .Net 6 und Blazor“
https://www.rolandk.de/wp-posts/2022/09/hexagonale-architektur-mit-c-net-6-und-blazor/ - Der durch NSwag generierte Client auf GitHub
https://github.com/RolandKoenig/HappyCoding/tree/61a7402465fe68dc2b2d1170719613ebd3e5764f/2022/HappyCoding.HexagonalArchitecture/HappyCoding.HexagonalArchitecture.WebUI/Client/Services
Ebenfalls interessant
- Request-Komprimierung mit C# und HttpClient
https://www.rolandk.de/wp-posts/2024/10/request-komprimierung-mit-c-und-httpclient/ - Hexagonale Architektur mit C#, .Net 6 und Blazor
https://www.rolandk.de/wp-posts/2022/09/hexagonale-architektur-mit-c-net-6-und-blazor/ - ASP.Net Core mit OpenUI5 – OData V4 Model
https://www.rolandk.de/wp-posts/2019/05/asp-net-core-mit-openui5/