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.
553 lines
21 KiB
Plaintext
553 lines
21 KiB
Plaintext
== 1. Einleitung ==
|
||
|
||
**ReC.Client** ist eine .NET-Client-Bibliothek für den typisierten und bequemen Zugriff auf die **ReC.API**. Anstatt direkt mit `HttpClient` zu arbeiten, bietet die Bibliothek thematisch geordnete API-Klassen (z. B. `RecActionApi`, `ResultApi`, `ProfileApi`, `EndpointAuthApi`, `EndpointParamsApi`, `EndpointsApi`, `CommonApi`) und integriert sich nahtlos in **Microsoft.Extensions.DependencyInjection**.
|
||
|
||
Die Bibliothek unterstützt sowohl **.NET 8** als auch **.NET Framework 4.6.2** (Multi-Targeting).
|
||
|
||
=== 1.1 Kernmerkmale ===
|
||
|
||
* **DI-orientiert**: Registrierung über `services.AddRecClient(...)`.
|
||
* **Typisierte API-Klassen**: jede Domäne hat eine eigene API-Klasse als Eigenschaft auf `ReCClient`.
|
||
* **Konsistente Fehlerbehandlung**: Bei HTTP-Fehlerstatus wird einheitlich eine `ReCApiException` geworfen, inklusive Statuscode, Methode, URI, Body usw.
|
||
* **Flexibles Lesen**: GET-Endpunkte unterstützen sowohl typisierte (`GetAsync<T>(...)`) als auch dynamische (`GetAsync(...)` ohne Typparameter, liefert `dynamic` / `JsonElement`) Abfragen.
|
||
* **Optionen**: Logging und Verhalten lassen sich über `ReCClientOptions` steuern.
|
||
|
||
== 2. Installation und Setup ==
|
||
|
||
=== 2.1 Konfiguration mit Dependency Injection (empfohlen) ===
|
||
|
||
Registrieren Sie den Client in `Program.cs` / `Startup.cs` über `AddRecClient`. Sie können entweder eine Basis-URL als String oder einen Konfigurations-Delegate für den zugrunde liegenden `HttpClient` übergeben.
|
||
|
||
{{code language="vb.net"}}
|
||
Imports Microsoft.Extensions.Hosting
|
||
Imports Microsoft.Extensions.DependencyInjection
|
||
Imports ReC.Client
|
||
|
||
Module Program
|
||
Sub Main(args As String())
|
||
Dim builder = Host.CreateDefaultBuilder(args)
|
||
|
||
builder.ConfigureServices(
|
||
Sub(services)
|
||
' Variante A: Basis-URL als String
|
||
services.AddRecClient("https://ihre-rec-api-adresse.com/")
|
||
|
||
' Variante B: HttpClient feinkonfigurieren
|
||
' services.AddRecClient(Sub(client)
|
||
' client.BaseAddress = New Uri("https://ihre-rec-api-adresse.com/")
|
||
' client.Timeout = TimeSpan.FromSeconds(30)
|
||
' End Sub)
|
||
End Sub)
|
||
|
||
Dim app = builder.Build()
|
||
app.Run()
|
||
End Sub
|
||
End Module
|
||
{{/code}}
|
||
|
||
{{code language="csharp"}}
|
||
using Microsoft.Extensions.Hosting;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using ReC.Client;
|
||
|
||
var builder = Host.CreateDefaultBuilder(args);
|
||
builder.ConfigureServices(services =>
|
||
{
|
||
// Variant A: base URL as string
|
||
services.AddRecClient("https://ihre-rec-api-adresse.com/");
|
||
|
||
// Variant B: configure HttpClient explicitly
|
||
// services.AddRecClient(client =>
|
||
// {
|
||
// client.BaseAddress = new Uri("https://ihre-rec-api-adresse.com/");
|
||
// client.Timeout = TimeSpan.FromSeconds(30);
|
||
// });
|
||
});
|
||
|
||
var app = builder.Build();
|
||
app.Run();
|
||
{{/code}}
|
||
|
||
=== 2.2 Konstruktor-Injektion ===
|
||
|
||
Sobald registriert, kann `ReCClient` per Konstruktor in jeden Dienst injiziert werden.
|
||
|
||
{{code language="vb.net"}}
|
||
Imports ReC.Client
|
||
|
||
Public Class MeinDienst
|
||
Private ReadOnly _recClient As ReCClient
|
||
|
||
Public Sub New(recClient As ReCClient)
|
||
_recClient = recClient
|
||
End Sub
|
||
End Class
|
||
{{/code}}
|
||
|
||
{{code language="csharp"}}
|
||
using ReC.Client;
|
||
|
||
public class MeinDienst
|
||
{
|
||
private readonly ReCClient _recClient;
|
||
|
||
public MeinDienst(ReCClient recClient)
|
||
{
|
||
_recClient = recClient;
|
||
}
|
||
}
|
||
{{/code}}
|
||
|
||
=== 2.3 Optionen über ReCClientOptions ===
|
||
|
||
Über `ReCClientOptions` lässt sich das Verhalten des Clients steuern, z. B. ob erfolgreiche Anfragen geloggt werden sollen.
|
||
|
||
{{code language="vb.net"}}
|
||
services.AddRecClient("https://ihre-rec-api-adresse.com/")
|
||
services.Configure(Of ReCClientOptions)(
|
||
Sub(opt)
|
||
opt.LogSuccessfulRequests = True
|
||
End Sub)
|
||
{{/code}}
|
||
|
||
{{code language="csharp"}}
|
||
services.AddRecClient("https://ihre-rec-api-adresse.com/");
|
||
services.Configure<ReCClientOptions>(opt =>
|
||
{
|
||
opt.LogSuccessfulRequests = true;
|
||
});
|
||
{{/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:
|
||
|
||
* `RecActions` (`RecActionApi`) – Verwaltung und Auslösen von RecActions (CRUD + Invoke)
|
||
* `Results` (`ResultApi`) – Lesen, Anlegen, Aktualisieren und Löschen von Result-Datensätzen
|
||
* `Profiles` (`ProfileApi`) – Verwaltung der Profile
|
||
* `EndpointAuth` (`EndpointAuthApi`) – Verwaltung der Endpoint-Authentifizierungsdaten
|
||
* `EndpointParams` (`EndpointParamsApi`) – Verwaltung der Endpoint-Parameter
|
||
* `Endpoints` (`EndpointsApi`) – Verwaltung der Endpoints
|
||
* `Common` (`CommonApi`) – Gemeinsame Operationen, die nicht entitätsspezifisch sind
|
||
|
||
Alle entitätsspezifischen Klassen erben von `BaseCrudApi` und bieten ein konsistentes CRUD-Schema.
|
||
|
||
== 4. Verwendung ==
|
||
|
||
=== 4.1 GET-Endpunkte: typisiert oder dynamisch ===
|
||
|
||
Die GET-Methoden in `RecActionApi`, `ProfileApi` und `ResultApi` existieren jeweils als **zwei Overloads**:
|
||
|
||
* **Generisch**: `GetAsync<T>(...)` – führt die Anfrage aus, liest den Response-Body **einmal** und deserialisiert ihn in den Typ `T`.
|
||
* **Nicht-generisch**: `GetAsync(...)` – identische Parameterliste, gibt aber ein `dynamic` (in der Praxis `System.Text.Json.JsonElement`) zurück. Intern wird `GetAsync<object>(...)` aufgerufen.
|
||
|
||
Beide Overloads teilen sich Implementierung und Fehlerbehandlung: bei HTTP-Fehlerstatus wird **`ReCApiException`** geworfen.
|
||
|
||
{{info}}
|
||
Da der nicht-generische Overload eine andere Signatur als der generische besitzt (kein Typparameter), gibt es **keinen Konflikt**. Welcher Overload aufgerufen wird, hängt davon ab, ob Sie einen Typparameter angeben oder nicht.
|
||
{{/info}}
|
||
|
||
==== 4.1.1 Typisiertes Lesen ====
|
||
|
||
{{code language="vb.net"}}
|
||
Imports ReC.Application.Common.Dto
|
||
|
||
' Alle Actions für ein Profil als typisiertes Array
|
||
Dim actions As RecActionViewDto() =
|
||
Await recClient.RecActions.GetAsync(Of RecActionViewDto())(profileId:=42)
|
||
|
||
For Each a In actions
|
||
Console.WriteLine($"Action {a.Id} -> Endpoint {a.EndpointUri}")
|
||
Next
|
||
{{/code}}
|
||
|
||
{{code language="csharp"}}
|
||
using ReC.Application.Common.Dto;
|
||
|
||
// All actions for a profile as a typed array
|
||
var actions = await recClient.RecActions.GetAsync<RecActionViewDto[]>(profileId: 42);
|
||
|
||
foreach (var a in actions!)
|
||
{
|
||
Console.WriteLine($"Action {a.Id} -> Endpoint {a.EndpointUri}");
|
||
}
|
||
{{/code}}
|
||
|
||
==== 4.1.2 Dynamisches Lesen ====
|
||
|
||
Wenn das Schema flexibel ist oder Sie das Ergebnis nur weiterleiten möchten, können Sie den nicht-generischen Overload verwenden:
|
||
|
||
{{code language="vb.net"}}
|
||
Imports System.Text.Json
|
||
|
||
Dim payload As Object = Await recClient.RecActions.GetAsync(profileId:=42)
|
||
Dim element As JsonElement = CType(payload, JsonElement)
|
||
|
||
If element.ValueKind = JsonValueKind.Array Then
|
||
For Each item In element.EnumerateArray()
|
||
Console.WriteLine(item.GetProperty("id").GetInt64())
|
||
Next
|
||
End If
|
||
{{/code}}
|
||
|
||
{{code language="csharp"}}
|
||
using System.Text.Json;
|
||
|
||
dynamic? payload = await recClient.RecActions.GetAsync(profileId: 42);
|
||
var element = (JsonElement)payload!;
|
||
|
||
if (element.ValueKind == JsonValueKind.Array)
|
||
{
|
||
foreach (var item in element.EnumerateArray())
|
||
{
|
||
Console.WriteLine(item.GetProperty("id").GetInt64());
|
||
}
|
||
}
|
||
{{/code}}
|
||
|
||
=== 4.2 Eine RecAction auslösen (Invoke) ===
|
||
|
||
`RecActionApi.InvokeAsync` startet die Stapelverarbeitung der Actions eines Profils. Es gibt zwei Overloads: einen mit `InvokeReferences`-Objekt und einen Komfort-Overload mit nur einer Batch-ID.
|
||
|
||
{{code language="vb.net"}}
|
||
Imports ReC.Client.Api
|
||
|
||
Public Async Function FuehreProfilAktionenAus(recClient As ReCClient, profilId As Long) As Task
|
||
Try
|
||
Await recClient.RecActions.InvokeAsync(
|
||
profilId,
|
||
New InvokeReferences With {.BatchId = "batch-" & Guid.NewGuid().ToString("N")})
|
||
Catch ex As ReC.Client.ReCApiException
|
||
' Auswertung von ex.StatusCode, ex.Method, ex.RequestUri, ex.ResponseBody
|
||
Throw
|
||
End Try
|
||
End Function
|
||
{{/code}}
|
||
|
||
{{code language="csharp"}}
|
||
using ReC.Client;
|
||
using ReC.Client.Api;
|
||
|
||
public async Task ExecuteProfileActionsAsync(ReCClient recClient, long profileId)
|
||
{
|
||
try
|
||
{
|
||
await recClient.RecActions.InvokeAsync(
|
||
profileId,
|
||
new InvokeReferences { BatchId = $"batch-{Guid.NewGuid():N}" });
|
||
}
|
||
catch (ReCApiException ex)
|
||
{
|
||
// Inspect ex.StatusCode, ex.Method, ex.RequestUri, ex.ResponseBody
|
||
throw;
|
||
}
|
||
}
|
||
{{/code}}
|
||
|
||
Komfort-Overload nur mit Batch-ID:
|
||
|
||
{{code language="vb.net"}}
|
||
Await recClient.RecActions.InvokeAsync(profilId, "batch-001")
|
||
{{/code}}
|
||
|
||
{{code language="csharp"}}
|
||
await recClient.RecActions.InvokeAsync(profileId, "batch-001");
|
||
{{/code}}
|
||
|
||
=== 4.3 Anlegen, Aktualisieren, Löschen (CRUD) ===
|
||
|
||
Alle CRUD-Operationen sind asynchron und werfen bei Fehlern eine `ReCApiException`.
|
||
|
||
* `CreateAsync(payload)` – HTTP POST mit JSON-Body.
|
||
* `UpdateAsync(id, payload)` – HTTP PUT auf `/{ResourcePath}/{id}` mit JSON-Body.
|
||
* `DeleteAsync(payload)` – HTTP DELETE; das Payload wird in den **Query-String** serialisiert (die API bindet Delete-Parameter aus der URL).
|
||
|
||
{{code language="vb.net"}}
|
||
Imports ReC.Application.RecActions.Commands
|
||
Imports ReC.Application.Common.Procedures.UpdateProcedure.Dto
|
||
|
||
' POST
|
||
Await recClient.RecActions.CreateAsync(New InsertActionCommand With {
|
||
.ProfileId = 1,
|
||
.EndpointId = 1,
|
||
.Active = True,
|
||
.Sequence = 1
|
||
})
|
||
|
||
' PUT
|
||
Await recClient.RecActions.UpdateAsync(123, New UpdateActionDto With {
|
||
.Active = False,
|
||
.Sequence = 2
|
||
})
|
||
|
||
' DELETE (Payload wird zu Query-String)
|
||
Await recClient.RecActions.DeleteAsync(New DeleteActionCommand With {
|
||
.Start = 100,
|
||
.End = 110,
|
||
.Force = False
|
||
})
|
||
{{/code}}
|
||
|
||
{{code language="csharp"}}
|
||
using ReC.Application.RecActions.Commands;
|
||
using ReC.Application.Common.Procedures.UpdateProcedure.Dto;
|
||
|
||
// POST
|
||
await recClient.RecActions.CreateAsync(new InsertActionCommand
|
||
{
|
||
ProfileId = 1,
|
||
EndpointId = 1,
|
||
Active = true,
|
||
Sequence = 1
|
||
});
|
||
|
||
// PUT
|
||
await recClient.RecActions.UpdateAsync(123, new UpdateActionDto
|
||
{
|
||
Active = false,
|
||
Sequence = 2
|
||
});
|
||
|
||
// DELETE (payload becomes query string)
|
||
await recClient.RecActions.DeleteAsync(new DeleteActionCommand
|
||
{
|
||
Start = 100,
|
||
End = 110,
|
||
Force = false
|
||
});
|
||
{{/code}}
|
||
|
||
=== 4.4 Fehlerbehandlung mit ReCApiException ===
|
||
|
||
Sobald die API einen Statuscode außerhalb von 2xx zurückgibt, wirft die Bibliothek eine `ReCApiException`. Diese enthält folgende Informationen:
|
||
|
||
* `StatusCode` – `HttpStatusCode` der Antwort (z. B. 404, 400, 500)
|
||
* `ReasonPhrase` – Optionaler HTTP-Reason-Phrase
|
||
* `ResponseBody` – Roher Response-Body als String (sofern lesbar)
|
||
* `Method` – HTTP-Methode der ursprünglichen Anfrage (z. B. `GET`, `POST`)
|
||
* `RequestUri` – Aufgerufene URI mit Pfad und Query
|
||
|
||
{{code language="vb.net"}}
|
||
Try
|
||
Dim profile = Await recClient.Profiles.GetAsync(Of ProfileViewDto)(id:=42)
|
||
Catch ex As ReCApiException
|
||
If ex.StatusCode = Net.HttpStatusCode.NotFound Then
|
||
' Profil existiert nicht
|
||
Else
|
||
' Allgemeiner Fehler
|
||
Console.WriteLine($"{ex.Method} {ex.RequestUri} -> {ex.StatusCode}: {ex.ResponseBody}")
|
||
Throw
|
||
End If
|
||
End Try
|
||
{{/code}}
|
||
|
||
{{code language="csharp"}}
|
||
try
|
||
{
|
||
var profile = await recClient.Profiles.GetAsync<ProfileViewDto>(id: 42);
|
||
}
|
||
catch (ReCApiException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||
{
|
||
// Profile does not exist
|
||
}
|
||
catch (ReCApiException ex)
|
||
{
|
||
Console.WriteLine($"{ex.Method} {ex.RequestUri} -> {ex.StatusCode}: {ex.ResponseBody}");
|
||
throw;
|
||
}
|
||
{{/code}}
|
||
|
||
== 5. Testen ==
|
||
|
||
Das Testprojekt verwendet `Microsoft.AspNetCore.Mvc.Testing`, um die `ReC.API` mit `WebApplicationFactory<Program>` **in-process** zu starten. Der `ReCClient` wird über DI konfiguriert und auf den in-process HTTP-Handler verdrahtet. So müssen Tests die API nicht extern starten.
|
||
|
||
Empfehlungen:
|
||
|
||
* Schreiben Sie Tests als `async Task` und verwenden Sie `await` – **vermeiden** Sie `GetAwaiter().GetResult()` oder `TaskSyncExtensions.Sync(...)`.
|
||
* 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.
|
||
|
||
== 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. Sie sind jedoch bewusst mit `[Obsolete]` markiert, damit Aufrufer sie nicht „aus Versehen" auswählen, sondern eine bewusste Entscheidung treffen.
|
||
|
||
Hintergrund
|
||
|
||
* 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.
|
||
* 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.
|
||
|
||
Status
|
||
|
||
* **Aktiv gepflegt**: Beide Pfade erhalten weiterhin Funktionalitäts- und Komfort-Updates.
|
||
* **`[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.
|
||
|
||
Wann der statische Pfad sinnvoll ist
|
||
|
||
* 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 besser DI verwenden
|
||
|
||
* Lang laufende Prozesse, Serveranwendungen, Tests.
|
||
* Sobald in der Anwendung ohnehin `IServiceCollection` / `IHostBuilder` vorhanden ist.
|
||
* Wenn `HttpClient`-Lebenszyklen, Logging-Scopes oder Optionen sauber verwaltet werden sollen.
|
||
|
||
=== 6.1 Statischer Client mit BuildStaticClient / Create ===
|
||
|
||
`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. 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"}}
|
||
Imports Microsoft.Extensions.DependencyInjection
|
||
Imports Microsoft.Extensions.Logging
|
||
|
||
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"}}
|
||
var client = ReCClient.Create();
|
||
await client.RecActions.InvokeAsync(profileId, "batch-001");
|
||
{{/code}}
|
||
|
||
==== 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"}}
|
||
' 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"}}
|
||
// Variant with base URL string
|
||
ReCClient.BuildStaticClient("https://ihre-rec-api-adresse.com/");
|
||
|
||
// With options callback and optional logger
|
||
ReCClient.BuildStaticClient(
|
||
"https://ihre-rec-api-adresse.com/",
|
||
opt => opt.LogSuccessfulRequests = true,
|
||
myLogger);
|
||
{{/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"}}
|
||
Imports ReC.Client
|
||
|
||
' Blockiert bis die Task fertig ist
|
||
recClient.RecActions.InvokeAsync(profilId, "batch-001").Sync()
|
||
{{/code}}
|
||
|
||
{{code language="csharp"}}
|
||
using ReC.Client;
|
||
|
||
// Blocks until the task completes
|
||
recClient.RecActions.InvokeAsync(profileId, "batch-001").Sync();
|
||
{{/code}}
|
||
|
||
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.
|
||
|
||
=== 6.3 Mittelfristige Empfehlung ===
|
||
|
||
* **`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.
|