Clarify static API and sync wrapper usage in ReC.Client
Expand documentation on `[Obsolete]` static APIs and sync wrappers, emphasizing their maintained status and appropriate use cases. - Added detailed examples for `BuildStaticClient` and `Create` in VB.NET and C#, including configuration options. - Updated `TaskSyncExtensions.Sync` section with warnings about potential deadlocks and recommendations for `async/await`. - Introduced "6.3 Mid-Term Recommendation" to guide migration to DI and async patterns. - Highlighted scenarios where static APIs and sync wrappers remain appropriate, such as legacy .NET Framework projects or quick-start use cases. - Clarified that `[Obsolete]` is a reminder, not a breaking change.
This commit is contained in:
@@ -367,31 +367,40 @@ Empfehlungen:
|
|||||||
* Verwenden Sie `Assert.ThrowsAsync<ReCApiException>(...)`, um Fehlerpfade zu prüfen, und werten Sie `StatusCode`, `Method` und `RequestUri` aus.
|
* Verwenden Sie `Assert.ThrowsAsync<ReCApiException>(...)`, um Fehlerpfade zu prüfen, und werten Sie `StatusCode`, `Method` und `RequestUri` aus.
|
||||||
* Für GET-Tests reicht eine einzelne Methode pro Verhalten (typisiert vs. dynamisch) statt redundanter Setups.
|
* Für GET-Tests reicht eine einzelne Methode pro Verhalten (typisiert vs. dynamisch) statt redundanter Setups.
|
||||||
|
|
||||||
== 6. Veraltete / statische APIs ==
|
== 6. Komfort-APIs: statischer Provider und synchrone Wrapper ==
|
||||||
|
|
||||||
Einige historische Hilfsmittel sind weiterhin vorhanden und funktionsfähig, jedoch mit `[Obsolete]` markiert. Sie sind kein Breaking Change – Sie können sie übergangsweise weiter benutzen, sollten aber langfristig migrieren.
|
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.
|
||||||
|
|
||||||
Betroffen:
|
Hintergrund
|
||||||
|
|
||||||
* `ReCClient.BuildStaticClient(string)` und `ReCClient.BuildStaticClient(Action<HttpClient>)`
|
* In Projekten, die noch auf **.NET Framework 4.6.2** basieren, ist `Microsoft.Extensions.DependencyInjection` häufig nicht etabliert und die Einführung einer DI-Infrastruktur kostet Zeit. Damit Entwickler dort nicht ins Stocken geraten, gibt es einen statischen Einstieg, der **sofort einsatzbereit** ist.
|
||||||
* `ReCClient.Create()`
|
* Synchroner Code in älteren Codebasen kann nicht überall sofort auf `async/await` umgestellt werden. Für solche Stellen existieren die `Sync()`-Erweiterungen als pragmatische Brücke.
|
||||||
* `TaskSyncExtensions.Sync(...)` und `TaskSyncExtensions.Sync<TResult>(...)`
|
|
||||||
|
|
||||||
Warum als `Obsolete` markiert?
|
Status
|
||||||
|
|
||||||
* **Statischer Provider**: Ein global gehaltener `IServiceProvider` erschwert die Verwaltung von `HttpClient`-Lebenszyklen, kann zu schwer reproduzierbaren Problemen in lang laufenden Prozessen führen und behindert Tests.
|
* **Aktiv gepflegt**: Beide Pfade erhalten weiterhin Funktionalitäts- und Komfort-Updates.
|
||||||
* **Synchrone Blockade**: `Task.GetAwaiter().GetResult()` (was `TaskSyncExtensions.Sync` intern tut) kann in Umgebungen mit `SynchronizationContext` (z. B. WinForms, WPF, einige Test-Runner) zu Deadlocks führen und macht Fehler schwerer diagnostizierbar.
|
* **`[Obsolete]`-Markierung als Erinnerung**: Die Compiler-Warnung soll bewusst sichtbar bleiben, damit Teams die Übergangslösung nicht vergessen und mittelfristig zu DI + `async/await` migrieren.
|
||||||
|
* **Kein Breaking-Change-Risiko**: Aufrufe bleiben kompilierbar und ausführbar.
|
||||||
|
|
||||||
Funktionieren sie noch?
|
Wann der statische Pfad sinnvoll ist
|
||||||
|
|
||||||
* Ja. Die Methoden bleiben kompilierbar und ausführbar. Sie erhalten lediglich eine Compiler-Warnung bei der Verwendung.
|
* Bestehender VB.NET-/C#-Code auf .NET Framework 4.6.2 ohne eigene DI-Infrastruktur.
|
||||||
|
* Kleine Werkzeuge, Skripte oder Konsolenanwendungen, bei denen ein vollständiges Host-Setup übertrieben wäre.
|
||||||
|
* Schneller Einstieg in die Bibliothek, um ein erstes Ergebnis zu sehen, bevor die endgültige Architektur entschieden wird.
|
||||||
|
|
||||||
Wann darf man sie übergangsweise nutzen?
|
Wann besser DI verwenden
|
||||||
|
|
||||||
* In Legacy-Code, der nicht sofort auf DI + `async/await` umgestellt werden kann.
|
* Lang laufende Prozesse, Serveranwendungen, Tests.
|
||||||
* In sehr kurzen, isolierten Skripten oder Konsolenanwendungen ohne `SynchronizationContext`, wo das Risiko überschaubar ist.
|
* Sobald in der Anwendung ohnehin `IServiceCollection` / `IHostBuilder` vorhanden ist.
|
||||||
|
* Wenn `HttpClient`-Lebenszyklen, Logging-Scopes oder Optionen sauber verwaltet werden sollen.
|
||||||
|
|
||||||
Beispiel – statischer Client (nicht empfohlen, aber möglich):
|
=== 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.
|
||||||
|
|
||||||
|
Wichtig: `BuildStaticClient` darf **nur einmal** beim Anwendungsstart aufgerufen werden.
|
||||||
|
|
||||||
|
Variante mit Basis-URL:
|
||||||
|
|
||||||
{{code language="vb.net"}}
|
{{code language="vb.net"}}
|
||||||
' Einmalig beim Anwendungsstart
|
' Einmalig beim Anwendungsstart
|
||||||
@@ -411,23 +420,64 @@ var client = ReCClient.Create();
|
|||||||
await client.RecActions.InvokeAsync(profileId, "batch-001");
|
await client.RecActions.InvokeAsync(profileId, "batch-001");
|
||||||
{{/code}}
|
{{/code}}
|
||||||
|
|
||||||
Beispiel – synchrone Blockade (nicht empfohlen, aber möglich):
|
Variante mit `HttpClient`-Konfiguration:
|
||||||
|
|
||||||
|
{{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)
|
||||||
|
{{/code}}
|
||||||
|
|
||||||
|
{{code language="csharp"}}
|
||||||
|
ReCClient.BuildStaticClient(http =>
|
||||||
|
{
|
||||||
|
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`:
|
||||||
|
|
||||||
|
{{code language="vb.net"}}
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
{{/code}}
|
||||||
|
|
||||||
|
=== 6.2 Synchrone Wrapper über TaskSyncExtensions ===
|
||||||
|
|
||||||
|
`TaskSyncExtensions.Sync()` bzw. `Sync<TResult>()` blockieren den aktuellen Thread, bis die `Task` abgeschlossen ist. Sie sind nützlich, wenn der umliegende Code (noch) nicht asynchron sein kann.
|
||||||
|
|
||||||
{{code language="vb.net"}}
|
{{code language="vb.net"}}
|
||||||
Imports ReC.Client
|
Imports ReC.Client
|
||||||
|
|
||||||
' Achtung: kann zu Deadlocks führen
|
' Blockiert bis die Task fertig ist
|
||||||
recClient.RecActions.InvokeAsync(profilId, "batch-001").Sync()
|
recClient.RecActions.InvokeAsync(profilId, "batch-001").Sync()
|
||||||
{{/code}}
|
{{/code}}
|
||||||
|
|
||||||
{{code language="csharp"}}
|
{{code language="csharp"}}
|
||||||
using ReC.Client;
|
using ReC.Client;
|
||||||
|
|
||||||
// Warning: may deadlock depending on context
|
// Blocks until the task completes
|
||||||
recClient.RecActions.InvokeAsync(profileId, "batch-001").Sync();
|
recClient.RecActions.InvokeAsync(profileId, "batch-001").Sync();
|
||||||
{{/code}}
|
{{/code}}
|
||||||
|
|
||||||
Migrations-Tipps:
|
Hinweis: In Umgebungen mit einem `SynchronizationContext` (z. B. WinForms, WPF oder bestimmte Test-Runner) kann blockierendes Warten zu Deadlocks führen. Für Konsolen- und Hintergrundprozesse ist das Risiko in der Regel gering. Wo immer möglich: `async/await` bevorzugen.
|
||||||
|
|
||||||
* **`BuildStaticClient` / `Create`** ? ersetzen durch `services.AddRecClient(...)` und Konstruktor-Injektion.
|
=== 6.3 Mittelfristige Empfehlung ===
|
||||||
* **`TaskSyncExtensions.Sync`** ? den umliegenden Codepfad asynchron machen (`async Task`) und `await` verwenden.
|
|
||||||
|
* **`BuildStaticClient` / `Create`**: für Legacy-Einstiege okay; sobald `IServiceCollection` vorhanden ist, auf `services.AddRecClient(...)` und Konstruktor-Injektion umstellen.
|
||||||
|
* **`TaskSyncExtensions.Sync`**: nur lokal kapseln. Wenn eine Methode bereits `async` sein darf, direkt `await` verwenden.
|
||||||
|
* Die `[Obsolete]`-Warnung dient als **dauerhafter Reminder**, dass es sich um einen bewussten Komfort-Pfad handelt – sie ist kein Hinweis darauf, dass die APIs entfernt werden.
|
||||||
|
|||||||
Reference in New Issue
Block a user