diff --git a/docs/ReC.Client.xwiki b/docs/ReC.Client.xwiki index 53a62db..0f533d0 100644 --- a/docs/ReC.Client.xwiki +++ b/docs/ReC.Client.xwiki @@ -118,6 +118,10 @@ services.Configure(opt => }); {{/code}} +{{warning}} +Wenn `LogSuccessfulRequests = true` gesetzt, aber **kein** `ILogger` über DI registriert ist, wirft der `ReCClient`-Konstruktor eine `InvalidOperationException`. Stellen Sie sicher, dass ein Logging-Provider (z. B. `services.AddLogging(...)`) registriert ist, oder lassen Sie die Option auf `false`. +{{/warning}} + == 3. Überblick über die API-Klassen == `ReCClient` bündelt mehrere thematische API-Klassen als Eigenschaften: @@ -369,7 +373,7 @@ Empfehlungen: == 6. Komfort-APIs: statischer Provider und synchrone Wrapper == -Neben dem empfohlenen DI-basierten Ansatz bietet **ReC.Client** absichtlich auch einen **statischen Komfort-Pfad** sowie **synchrone Wrapper** an. Diese Bestandteile sind nicht „veraltet im Sinne von eingefroren" – sie werden weiterhin gepflegt und bei Bedarf um neue Funktionen erweitert (Beispiel: der jüngst hinzugefügte optionale `Action`-Parameter in `BuildStaticClient`). Sie sind jedoch bewusst mit `[Obsolete]` markiert, damit Aufrufer sie nicht „aus Versehen" auswählen, sondern eine bewusste Entscheidung treffen. +Neben dem empfohlenen DI-basierten Ansatz bietet **ReC.Client** absichtlich auch einen **statischen Komfort-Pfad** sowie **synchrone Wrapper** an. Diese Bestandteile sind nicht „veraltet im Sinne von eingefroren" – sie werden weiterhin gepflegt und bei Bedarf um neue Funktionen erweitert. Sie sind jedoch bewusst mit `[Obsolete]` markiert, damit Aufrufer sie nicht „aus Versehen" auswählen, sondern eine bewusste Entscheidung treffen. Hintergrund @@ -396,64 +400,129 @@ Wann besser DI verwenden === 6.1 Statischer Client mit BuildStaticClient / Create === -`ReCClient.BuildStaticClient(...)` baut intern eine `IServiceCollection` auf, ruft `AddRecClient(...)` auf und legt einen statischen `IServiceProvider` ab. Anschließend liefert `ReCClient.Create()` Instanzen aus diesem Provider. +`ReCClient.BuildStaticClient(...)` baut intern eine `IServiceCollection` auf, ruft `AddRecClient(...)` auf und legt einen **statischen, thread-safen `Lazy`** an. Der eigentliche `IServiceProvider` wird **erst beim ersten Aufruf von `ReCClient.Create()`** und genau einmal gebaut. -Wichtig: `BuildStaticClient` darf **nur einmal** beim Anwendungsstart aufgerufen werden. +Wichtig: -Variante mit Basis-URL: +* `BuildStaticClient` darf **nur einmal** beim Anwendungsstart aufgerufen werden. Ein zweiter Aufruf – egal welcher Overload – wirft `InvalidOperationException("Static Provider is already built.")`. +* Die Konstruktion des `IServiceProvider` ist **threadsicher** (`Lazy` mit `LazyThreadSafetyMode.ExecutionAndPublication`). + +==== 6.1.1 Empfohlene Form: BuildStaticClient mit StaticBuildConfiguration ==== + +Da der statische Pfad mehrere optionale Bestandteile hat (Basis-URL **oder** `HttpClient`-Konfiguration, `ReCClientOptions`, eigener `ILogger`, zusätzliche Service-Registrierungen), gibt es einen Callback-basierten Overload, der alle Optionen in einem `StaticBuildConfiguration`-Objekt bündelt: {{code language="vb.net"}} -' Einmalig beim Anwendungsstart -ReCClient.BuildStaticClient("https://ihre-rec-api-adresse.com/") +Imports Microsoft.Extensions.DependencyInjection +Imports Microsoft.Extensions.Logging -' Später irgendwo im Code +ReCClient.BuildStaticClient( + Sub(cfg) + cfg.BaseAddress = "https://ihre-rec-api-adresse.com/" + + cfg.ConfigureOptions = Sub(opt) + opt.LogSuccessfulRequests = True + End Sub + + ' Optional: eigene zusätzliche Registrierungen, z. B. Logging-Provider + cfg.ConfigureServices = Sub(services) + services.AddLogging(Sub(b) b.AddConsole()) + End Sub + End Sub) +{{/code}} + +{{code language="csharp"}} +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +ReCClient.BuildStaticClient(cfg => +{ + cfg.BaseAddress = "https://ihre-rec-api-adresse.com/"; + + cfg.ConfigureOptions = opt => + { + opt.LogSuccessfulRequests = true; + }; + + // Optional: additional service registrations, e.g. logging + cfg.ConfigureServices = services => + { + services.AddLogging(b => b.AddConsole()); + }; +}); +{{/code}} + +Eigenschaften von `StaticBuildConfiguration`: + +* `BaseAddress` – Basis-URI der ReC.API. **Schließt sich gegenseitig** mit `ConfigureClient` aus. +* `ConfigureClient` – Delegate zum direkten Konfigurieren des `HttpClient` (z. B. `BaseAddress` + `Timeout` + Header). Schließt sich gegenseitig mit `BaseAddress` aus. +* `ConfigureOptions` – Optionaler Delegate für `ReCClientOptions` (z. B. `LogSuccessfulRequests`). +* `Logger` – Optionale `ILogger`-Instanz, die als Singleton in die interne `IServiceCollection` registriert wird. +* `ConfigureServices` – Optionaler Delegate, mit dem der Aufrufer beliebige zusätzliche Registrierungen auf der internen `IServiceCollection` vornehmen kann (z. B. `AddLogging(...)` oder eigene Abhängigkeiten). + +Validierung beim Aufruf: + +* `BuildStaticClient` wirft `ArgumentNullException`, wenn der `configure`-Callback `null` ist. +* `BuildStaticClient` wirft `InvalidOperationException`, wenn weder `BaseAddress` noch `ConfigureClient` gesetzt sind, **oder** wenn beide gleichzeitig gesetzt sind. + +Variante mit `HttpClient`-Feinkonfiguration: + +{{code language="vb.net"}} +ReCClient.BuildStaticClient( + Sub(cfg) + cfg.ConfigureClient = Sub(http) + http.BaseAddress = New Uri("https://ihre-rec-api-adresse.com/") + http.Timeout = TimeSpan.FromSeconds(30) + End Sub + End Sub) +{{/code}} + +{{code language="csharp"}} +ReCClient.BuildStaticClient(cfg => +{ + cfg.ConfigureClient = http => + { + http.BaseAddress = new Uri("https://ihre-rec-api-adresse.com/"); + http.Timeout = TimeSpan.FromSeconds(30); + }; +}); +{{/code}} + +Nach dem `BuildStaticClient`-Aufruf liefert `ReCClient.Create()` Instanzen aus dem statischen Provider: + +{{code language="vb.net"}} Dim client As ReCClient = ReCClient.Create() Await client.RecActions.InvokeAsync(profilId, "batch-001") {{/code}} {{code language="csharp"}} -// Once at application startup -ReCClient.BuildStaticClient("https://ihre-rec-api-adresse.com/"); - -// Later somewhere in code var client = ReCClient.Create(); await client.RecActions.InvokeAsync(profileId, "batch-001"); {{/code}} -Variante mit `HttpClient`-Konfiguration: +==== 6.1.2 Ältere Komfort-Overloads ==== + +Aus Bequemlichkeit existieren zwei weitere Overloads, die intern an die `StaticBuildConfiguration`-Variante delegieren. Sie sind als `Obsolete` markiert mit dem Hinweis, die `Action`-Variante zu verwenden, bleiben aber funktionsfähig: {{code language="vb.net"}} -ReCClient.BuildStaticClient(Sub(http) - http.BaseAddress = New Uri("https://ihre-rec-api-adresse.com/") - http.Timeout = TimeSpan.FromSeconds(30) -End Sub) +' Variante mit Basis-URL als String +ReCClient.BuildStaticClient("https://ihre-rec-api-adresse.com/") + +' Mit Options-Callback und optionalem Logger +ReCClient.BuildStaticClient( + "https://ihre-rec-api-adresse.com/", + Sub(opt) opt.LogSuccessfulRequests = True, + myLogger) {{/code}} {{code language="csharp"}} -ReCClient.BuildStaticClient(http => -{ - http.BaseAddress = new Uri("https://ihre-rec-api-adresse.com/"); - http.Timeout = TimeSpan.FromSeconds(30); -}); -{{/code}} +// Variant with base URL string +ReCClient.BuildStaticClient("https://ihre-rec-api-adresse.com/"); -Optional kann zusätzlich `ReCClientOptions` über einen Callback gesetzt werden – die Signatur entspricht der von `AddRecClient`: - -{{code language="vb.net"}} +// With options callback and optional logger ReCClient.BuildStaticClient( "https://ihre-rec-api-adresse.com/", - Sub(opt) - opt.LogSuccessfulRequests = True - End Sub) -{{/code}} - -{{code language="csharp"}} -ReCClient.BuildStaticClient( - "https://ihre-rec-api-adresse.com/", - opt => - { - opt.LogSuccessfulRequests = true; - }); + opt => opt.LogSuccessfulRequests = true, + myLogger); {{/code}} === 6.2 Synchrone Wrapper über TaskSyncExtensions ===