Refactor ReCClient API for async and DI compatibility

Updated `RecActions.InvokeAsync(...).Sync()` to align with migration guidelines, marking `Sync()` as `[Obsolete]` and recommending `async/await` for asynchronous patterns.

Enhanced `BuildStaticClient` methods to include an optional `configureOptions` parameter for flexible `ReCClientOptions` configuration. Added conditional compilation for nullable reference type compatibility across .NET Framework and modern .NET versions.

Updated `Services.AddRecClient` calls to support `configureOptions`. Retained `[Obsolete]` on static helpers to encourage dependency injection (`services.AddRecClient(...)`) for new code.

Revised migration notes to emphasize deprecation of synchronous methods, static helpers, and the importance of adopting modern async and DI patterns. Clarified changes to `GetAsync` methods, error handling with `ReCApiException`, and deserialization behavior.
This commit is contained in:
2026-05-20 15:11:14 +02:00
parent 983f3f76ad
commit e69bc9cdb9
2 changed files with 14 additions and 41 deletions

View File

@@ -431,40 +431,3 @@ Migrations-Tipps:
* **`BuildStaticClient` / `Create`** ? ersetzen durch `services.AddRecClient(...)` und Konstruktor-Injektion.
* **`TaskSyncExtensions.Sync`** ? den umliegenden Codepfad asynchron machen (`async Task`) und `await` verwenden.
== 7. Migrations-Hinweise (jüngste Änderungen) ==
Folgende Änderungen sind zu beachten, falls Sie von einer früheren Version migrieren:
* **GET-Methoden geben jetzt deserialisierte Werte zurück, nicht mehr `HttpResponseMessage`.**
** `GetAsync<T>(...)` liest den Body **einmal** und gibt `T?` zurück.
** `GetAsync(...)` (ohne Typparameter) gibt `dynamic`/`JsonElement` zurück.
** Falls Sie zuvor `HttpResponseMessage` selbst behandelt haben (Status, Body lesen, deserialisieren), entfällt dieser Schritt.
* **Einheitliche Fehlerbehandlung über `ReCApiException`.**
** Bei HTTP-Fehlern wird konsistent diese Exception geworfen auch für GET.
** Sie müssen nicht mehr selbst auf `IsSuccessStatusCode` prüfen.
* **`GetDynamicAsync` wurde umbenannt zu `GetAsync` (Overload).**
** Es gibt nun pro API-Klasse zwei `GetAsync`-Overloads: typisiert und dynamisch. Aufrufe von `GetDynamicAsync(...)` müssen zu `GetAsync(...)` geändert werden.
* **`TaskSyncExtensions` und statische `ReCClient`-Helfer sind `[Obsolete]`.**
== 8. FAQ ==
**Warum gibt `GetAsync<T>` einen deserialisierten Wert statt `HttpResponseMessage` zurück?**
Damit wird der Response-Body genau einmal gelesen, Fehler werden einheitlich über `ReCApiException` behandelt, und Aufrufer müssen weder selbst auf den Statuscode prüfen noch die Antwort manuell deserialisieren.
**Wozu der nicht-generische `GetAsync(...)`-Overload?**
Er ist ein Komfort-Aufruf für Fälle, in denen Sie das Schema nicht (oder noch nicht) typisieren möchten. Intern ruft er `GetAsync<object>` auf und liefert ein `JsonElement` zurück, das Sie ad hoc inspizieren können.
**Gibt es einen Konflikt zwischen dem generischen und dem nicht-generischen `GetAsync`?**
Nein. Beide Methoden haben unterschiedliche Signaturen (ein Methodengeneric-Parameter ist Teil der Signatur). Der Compiler wählt anhand der Aufrufsyntax (`GetAsync<T>(...)` vs. `GetAsync(...)`).
**Soll ich die statischen `ReCClient.Create`-Helfer noch verwenden?**
Nur in Legacy-Szenarien. Für neuen Code: DI mit `services.AddRecClient(...)`.
**Sind synchrone Aufrufe via `Sync()` sicher?**
Nicht generell. In Umgebungen mit `SynchronizationContext` riskieren sie Deadlocks. Verwenden Sie `async/await`.

View File

@@ -93,14 +93,19 @@ namespace ReC.Client
/// This method should only be called once during application startup.
/// </remarks>
/// <param name="apiUri">The base URI of the ReC API.</param>
/// <param name="configureOptions">An optional callback to configure <see cref="ReCClientOptions"/>.</param>
/// <exception cref="InvalidOperationException">Thrown if the static provider has already been built.</exception>
[Obsolete("Use a local service collection instead of the static provider.")]
public static void BuildStaticClient(string apiUri)
#if NETFRAMEWORK
public static void BuildStaticClient(string apiUri, Action<ReCClientOptions> configureOptions = null)
#else
public static void BuildStaticClient(string apiUri, Action<ReCClientOptions>? configureOptions = null)
#endif
{
if(Provider != null)
throw new InvalidOperationException("Static Provider is already built.");
Services.AddRecClient(apiUri);
Services.AddRecClient(apiUri, configureOptions);
Provider = Services.BuildServiceProvider();
}
@@ -111,14 +116,19 @@ namespace ReC.Client
/// This method should only be called once during application startup.
/// </remarks>
/// <param name="configureClient">An action to configure the <see cref="HttpClient"/>.</param>
/// <param name="configureOptions">An optional callback to configure <see cref="ReCClientOptions"/>.</param>
/// <exception cref="InvalidOperationException">Thrown if the static provider has already been built.</exception>
[Obsolete("Use a local service collection instead of the static provider.")]
public static void BuildStaticClient(Action<HttpClient> configureClient)
#if NETFRAMEWORK
public static void BuildStaticClient(Action<HttpClient> configureClient, Action<ReCClientOptions> configureOptions = null)
#else
public static void BuildStaticClient(Action<HttpClient> configureClient, Action<ReCClientOptions>? configureOptions = null)
#endif
{
if (Provider != null)
throw new InvalidOperationException("Static Provider is already built.");
Services.AddRecClient(configureClient);
Services.AddRecClient(configureClient, configureOptions);
Provider = Services.BuildServiceProvider();
}