Added comprehensive inline comments (mainly in German) to key files (index.html, Program.cs, App.razor, MainLayout.razor, NavMenu.razor, Catalogs.razor, CatalogApiClient.cs) to clarify their roles and the overall application flow. Updated Home.razor with a clearer heading and intro. Introduced Ablauf.cs, which documents the loading order and responsibilities of each major component. These changes enhance codebase clarity and maintainability, especially for German-speaking developers.
149 lines
5.1 KiB
C#
149 lines
5.1 KiB
C#
/* Kapselt die Kommunikation mit der API für den Catalog-Endpunkt.
|
|
Bietet Methoden für CRUD-Operationen auf Catalog-Daten */
|
|
|
|
using System.Net;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using DbFirst.BlazorWasm.Models;
|
|
|
|
namespace DbFirst.BlazorWasm.Services;
|
|
|
|
public class CatalogApiClient
|
|
{
|
|
private readonly HttpClient _httpClient;
|
|
private const string Endpoint = "api/catalogs";
|
|
|
|
public CatalogApiClient(HttpClient httpClient)
|
|
{
|
|
_httpClient = httpClient;
|
|
}
|
|
|
|
public async Task<List<CatalogReadDto>> GetAllAsync()
|
|
{
|
|
var result = await _httpClient.GetFromJsonAsync<List<CatalogReadDto>>(Endpoint);
|
|
return result ?? new List<CatalogReadDto>();
|
|
}
|
|
|
|
public async Task<CatalogReadDto?> GetByIdAsync(int id)
|
|
{
|
|
return await _httpClient.GetFromJsonAsync<CatalogReadDto>($"{Endpoint}/{id}");
|
|
}
|
|
|
|
public async Task<ApiResult<CatalogReadDto?>> CreateAsync(CatalogWriteDto dto)
|
|
{
|
|
var response = await _httpClient.PostAsJsonAsync(Endpoint, dto);
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var payload = await response.Content.ReadFromJsonAsync<CatalogReadDto>();
|
|
return ApiResult<CatalogReadDto?>.Ok(payload);
|
|
}
|
|
|
|
var error = await ReadErrorAsync(response);
|
|
return ApiResult<CatalogReadDto?>.Fail(error);
|
|
}
|
|
|
|
public async Task<ApiResult<bool>> UpdateAsync(int id, CatalogWriteDto dto)
|
|
{
|
|
var response = await _httpClient.PutAsJsonAsync($"{Endpoint}/{id}", dto);
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
return ApiResult<bool>.Ok(true);
|
|
}
|
|
|
|
var error = await ReadErrorAsync(response);
|
|
return ApiResult<bool>.Fail(error);
|
|
}
|
|
|
|
public async Task<ApiResult<bool>> DeleteAsync(int id)
|
|
{
|
|
var response = await _httpClient.DeleteAsync($"{Endpoint}/{id}");
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
return ApiResult<bool>.Ok(true);
|
|
}
|
|
|
|
var error = await ReadErrorAsync(response);
|
|
return ApiResult<bool>.Fail(error);
|
|
}
|
|
|
|
private static async Task<string> ReadErrorAsync(HttpResponseMessage response)
|
|
{
|
|
// Liest und analysiert Fehlerdetails aus der API-Antwort.
|
|
// Gibt eine benutzerfreundliche Fehlermeldung zurück.
|
|
|
|
string? problemTitle = null;
|
|
string? problemDetail = null;
|
|
|
|
try
|
|
{
|
|
var problem = await response.Content.ReadFromJsonAsync<ProblemDetailsDto>();
|
|
if (problem != null)
|
|
{
|
|
problemTitle = problem.Title;
|
|
problemDetail = problem.Detail ?? problem.Type;
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
// ignore parse errors
|
|
}
|
|
|
|
var status = response.StatusCode;
|
|
var reason = response.ReasonPhrase;
|
|
var body = await response.Content.ReadAsStringAsync();
|
|
|
|
string detail = problemDetail;
|
|
if (string.IsNullOrWhiteSpace(detail) && !string.IsNullOrWhiteSpace(body))
|
|
{
|
|
detail = body;
|
|
}
|
|
|
|
// Friendly overrides
|
|
if (status == HttpStatusCode.Conflict)
|
|
{
|
|
return "Datensatz existiert bereits. Bitte wählen Sie einen anderen Titel.";
|
|
}
|
|
if (status == HttpStatusCode.BadRequest && (detail?.Contains("CatTitle cannot be changed", StringComparison.OrdinalIgnoreCase) ?? false))
|
|
{
|
|
return "Titel kann nicht geändert werden.";
|
|
}
|
|
|
|
return status switch
|
|
{
|
|
HttpStatusCode.BadRequest => $"Eingabe ungültig{FormatSuffix(problemTitle, detail, reason)}",
|
|
HttpStatusCode.NotFound => $"Nicht gefunden{FormatSuffix(problemTitle, detail, reason)}",
|
|
HttpStatusCode.Conflict => $"Konflikt{FormatSuffix(problemTitle, detail, reason)}",
|
|
HttpStatusCode.Unauthorized => $"Nicht autorisiert{FormatSuffix(problemTitle, detail, reason)}",
|
|
HttpStatusCode.Forbidden => $"Nicht erlaubt{FormatSuffix(problemTitle, detail, reason)}",
|
|
HttpStatusCode.InternalServerError => $"Serverfehler{FormatSuffix(problemTitle, detail, reason)}",
|
|
_ => $"Fehler {(int)status} {reason ?? string.Empty}{FormatSuffix(problemTitle, detail, reason)}"
|
|
};
|
|
}
|
|
|
|
private static string FormatSuffix(string? title, string? detail, string? reason)
|
|
{
|
|
// Formatiert zusätzliche Informationen für Fehlermeldungen.
|
|
// Kombiniert Titel, Details und Grund in einer lesbaren Form.
|
|
|
|
var parts = new List<string>();
|
|
if (!string.IsNullOrWhiteSpace(title)) parts.Add(title);
|
|
if (!string.IsNullOrWhiteSpace(detail)) parts.Add(detail);
|
|
if (parts.Count == 0 && !string.IsNullOrWhiteSpace(reason)) parts.Add(reason);
|
|
if (parts.Count == 0) return string.Empty;
|
|
return ": " + string.Join(" | ", parts);
|
|
}
|
|
}
|
|
|
|
public record ApiResult<T>(bool Success, T? Value, string? Error)
|
|
{
|
|
public static ApiResult<T> Ok(T? value) => new(true, value, null);
|
|
public static ApiResult<T> Fail(string? error) => new(false, default, error);
|
|
}
|
|
|
|
internal sealed class ProblemDetailsDto
|
|
{
|
|
public string? Type { get; set; }
|
|
public string? Title { get; set; }
|
|
public string? Detail { get; set; }
|
|
}
|