Centralize API error handling in ApiClientHelper

Refactored error handling logic for API responses into a new static ApiClientHelper class, consolidating the ReadErrorAsync method and ProblemDetailsDto. Updated CatalogApiClient and MassDataApiClient to use the shared helper, removing redundant local methods and classes. This change improves consistency, reduces duplication, and streamlines error message formatting across the application.
This commit is contained in:
OlgunR
2026-04-15 13:55:57 +02:00
parent 39cb63a78d
commit a0e0d7ed03
3 changed files with 64 additions and 86 deletions

View File

@@ -0,0 +1,60 @@
using System.Net;
using System.Net.Http.Json;
namespace DbFirst.BlazorWebApp.Services;
internal sealed class ProblemDetailsDto
{
public string? Type { get; set; }
public string? Title { get; set; }
public string? Detail { get; set; }
}
internal static class ApiClientHelper
{
public static async Task<string> ReadErrorAsync(HttpResponseMessage response)
{
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 { }
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;
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)
{
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);
}
}

View File

@@ -34,7 +34,7 @@ public class CatalogApiClient
return ApiResult<CatalogReadDto?>.Ok(payload);
}
var error = await ReadErrorAsync(response);
var error = await ApiClientHelper.ReadErrorAsync(response);
return ApiResult<CatalogReadDto?>.Fail(error);
}
@@ -46,7 +46,7 @@ public class CatalogApiClient
return ApiResult<bool>.Ok(true);
}
var error = await ReadErrorAsync(response);
var error = await ApiClientHelper.ReadErrorAsync(response);
return ApiResult<bool>.Fail(error);
}
@@ -58,68 +58,9 @@ public class CatalogApiClient
return ApiResult<bool>.Ok(true);
}
var error = await ReadErrorAsync(response);
var error = await ApiClientHelper.ReadErrorAsync(response);
return ApiResult<bool>.Fail(error);
}
private static async Task<string> ReadErrorAsync(HttpResponseMessage response)
{
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
{
}
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;
}
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)
{
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)
@@ -128,9 +69,3 @@ public record ApiResult<T>(bool Success, T? Value, string? Error)
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; }
}

View File

@@ -45,27 +45,10 @@ public class MassDataApiClient
return ApiResult<MassDataReadDto?>.Ok(payload);
}
var error = await ReadErrorAsync(response);
var error = await ApiClientHelper.ReadErrorAsync(response);
return ApiResult<MassDataReadDto?>.Fail(error);
}
private static async Task<string> ReadErrorAsync(HttpResponseMessage response)
{
try
{
var problem = await response.Content.ReadFromJsonAsync<ProblemDetailsDto>();
if (problem != null && !string.IsNullOrWhiteSpace(problem.Title))
return problem.Detail ?? problem.Title ?? response.ReasonPhrase ?? "Unbekannter Fehler";
}
catch { }
var body = await response.Content.ReadAsStringAsync();
if (!string.IsNullOrWhiteSpace(body))
return body;
return $"Fehler {(int)response.StatusCode} {response.ReasonPhrase}".Trim();
}
public async Task<MassDataReadDto?> GetByCustomerNameAsync(string customerName)
{
if (string.IsNullOrWhiteSpace(customerName))