using System.Net.Http.Json; using Microsoft.Extensions.Logging; namespace EnvelopeGenerator.ReceiverUI.Client.Services.Base; /// /// Basisklasse für ALLE API-Services. /// /// WARUM eine Basisklasse? /// - Einheitliches Error-Handling: Jeder API-Aufruf wird gleich behandelt /// - DRY (Don't Repeat Yourself): Logging, Fehlerbehandlung, Serialisierung nur einmal /// - Einfache Erweiterung: Retry-Logik, Token-Refresh etc. nur hier ändern /// public abstract class ApiServiceBase { protected readonly HttpClient Http; protected readonly ILogger Logger; protected ApiServiceBase(HttpClient http, ILogger logger) { Http = http; Logger = logger; } /// /// GET-Request mit Deserialisierung. /// Alle API GET-Aufrufe gehen durch diese Methode. /// protected async Task> GetAsync(string endpoint, CancellationToken ct = default) { try { var response = await Http.GetAsync(endpoint, ct); if (!response.IsSuccessStatusCode) { var errorBody = await response.Content.ReadAsStringAsync(ct); Logger.LogWarning("GET {Endpoint} failed: {Status} - {Body}", endpoint, (int)response.StatusCode, errorBody); return ApiResponse.Failure((int)response.StatusCode, errorBody); } var data = await response.Content.ReadFromJsonAsync(cancellationToken: ct); return ApiResponse.Success(data!, (int)response.StatusCode); } catch (HttpRequestException ex) { Logger.LogError(ex, "HTTP error calling GET {Endpoint}", endpoint); return ApiResponse.Failure(0, "Verbindung zum Server fehlgeschlagen."); } catch (TaskCanceledException) { Logger.LogWarning("GET {Endpoint} was cancelled", endpoint); return ApiResponse.Failure(0, "Anfrage abgebrochen."); } } /// /// POST-Request mit Body und Response-Deserialisierung. /// protected async Task> PostAsync( string endpoint, TRequest body, CancellationToken ct = default) { try { var response = await Http.PostAsJsonAsync(endpoint, body, ct); if (!response.IsSuccessStatusCode) { var errorBody = await response.Content.ReadAsStringAsync(ct); Logger.LogWarning("POST {Endpoint} failed: {Status} - {Body}", endpoint, (int)response.StatusCode, errorBody); return ApiResponse.Failure((int)response.StatusCode, errorBody); } var data = await response.Content.ReadFromJsonAsync(cancellationToken: ct); return ApiResponse.Success(data!, (int)response.StatusCode); } catch (HttpRequestException ex) { Logger.LogError(ex, "HTTP error calling POST {Endpoint}", endpoint); return ApiResponse.Failure(0, "Verbindung zum Server fehlgeschlagen."); } } /// /// POST-Request ohne Response-Body (z.B. Logout). /// protected async Task PostAsync( string endpoint, TRequest body, CancellationToken ct = default) { try { var response = await Http.PostAsJsonAsync(endpoint, body, ct); if (!response.IsSuccessStatusCode) { var errorBody = await response.Content.ReadAsStringAsync(ct); return ApiResponse.Failure((int)response.StatusCode, errorBody); } return ApiResponse.Success((int)response.StatusCode); } catch (HttpRequestException ex) { Logger.LogError(ex, "HTTP error calling POST {Endpoint}", endpoint); return ApiResponse.Failure(0, "Verbindung zum Server fehlgeschlagen."); } } }