Improve ReCClient static client and documentation

Updated `ReCClientOptions` to include a warning about the `LogSuccessfulRequests` option throwing an `InvalidOperationException` if no `ILogger` is registered via DI. Added validation and thread-safety to `BuildStaticClient` using `Lazy<IServiceProvider>`.

Introduced `StaticBuildConfiguration` for callback-based configuration and detailed its properties. Clarified usage patterns, added VB.NET and C# examples, and documented validation rules.

Marked older `BuildStaticClient` overloads as `[Obsolete]` while retaining functionality. Expanded context on static client use cases and synchronous wrappers. Improved documentation clarity and consistency.
This commit is contained in:
2026-05-21 08:33:24 +02:00
parent 9e1bee9ea3
commit 1fc395f495

View File

@@ -118,6 +118,10 @@ services.Configure<ReCClientOptions>(opt =>
}); });
{{/code}} {{/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 == == 3. Überblick über die API-Klassen ==
`ReCClient` bündelt mehrere thematische API-Klassen als Eigenschaften: `ReCClient` bündelt mehrere thematische API-Klassen als Eigenschaften:
@@ -369,7 +373,7 @@ Empfehlungen:
== 6. Komfort-APIs: statischer Provider und synchrone Wrapper == == 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<ReCClientOptions>`-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 Hintergrund
@@ -396,64 +400,129 @@ Wann besser DI verwenden
=== 6.1 Statischer Client mit BuildStaticClient / Create === === 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<IServiceProvider>`** 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<T>` 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"}} {{code language="vb.net"}}
' Einmalig beim Anwendungsstart Imports Microsoft.Extensions.DependencyInjection
ReCClient.BuildStaticClient("https://ihre-rec-api-adresse.com/") 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() Dim client As ReCClient = ReCClient.Create()
Await client.RecActions.InvokeAsync(profilId, "batch-001") Await client.RecActions.InvokeAsync(profilId, "batch-001")
{{/code}} {{/code}}
{{code language="csharp"}} {{code language="csharp"}}
// Once at application startup
ReCClient.BuildStaticClient("https://ihre-rec-api-adresse.com/");
// Later somewhere in code
var client = ReCClient.Create(); var client = ReCClient.Create();
await client.RecActions.InvokeAsync(profileId, "batch-001"); await client.RecActions.InvokeAsync(profileId, "batch-001");
{{/code}} {{/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<StaticBuildConfiguration>`-Variante zu verwenden, bleiben aber funktionsfähig:
{{code language="vb.net"}} {{code language="vb.net"}}
ReCClient.BuildStaticClient(Sub(http) ' Variante mit Basis-URL als String
http.BaseAddress = New Uri("https://ihre-rec-api-adresse.com/") ReCClient.BuildStaticClient("https://ihre-rec-api-adresse.com/")
http.Timeout = TimeSpan.FromSeconds(30)
End Sub) ' Mit Options-Callback und optionalem Logger
ReCClient.BuildStaticClient(
"https://ihre-rec-api-adresse.com/",
Sub(opt) opt.LogSuccessfulRequests = True,
myLogger)
{{/code}} {{/code}}
{{code language="csharp"}} {{code language="csharp"}}
ReCClient.BuildStaticClient(http => // Variant with base URL string
{ ReCClient.BuildStaticClient("https://ihre-rec-api-adresse.com/");
http.BaseAddress = new Uri("https://ihre-rec-api-adresse.com/");
http.Timeout = TimeSpan.FromSeconds(30);
});
{{/code}}
Optional kann zusätzlich `ReCClientOptions` über einen Callback gesetzt werden die Signatur entspricht der von `AddRecClient`: // With options callback and optional logger
{{code language="vb.net"}}
ReCClient.BuildStaticClient( ReCClient.BuildStaticClient(
"https://ihre-rec-api-adresse.com/", "https://ihre-rec-api-adresse.com/",
Sub(opt) opt => opt.LogSuccessfulRequests = true,
opt.LogSuccessfulRequests = True myLogger);
End Sub)
{{/code}}
{{code language="csharp"}}
ReCClient.BuildStaticClient(
"https://ihre-rec-api-adresse.com/",
opt =>
{
opt.LogSuccessfulRequests = true;
});
{{/code}} {{/code}}
=== 6.2 Synchrone Wrapper über TaskSyncExtensions === === 6.2 Synchrone Wrapper über TaskSyncExtensions ===