Compare commits
5 Commits
910b0e4aaa
...
353611d400
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
353611d400 | ||
|
|
8c175de953 | ||
|
|
1fd776bc29 | ||
|
|
904e6e20f0 | ||
|
|
215e526230 |
@@ -36,12 +36,26 @@ public class CatalogsController : ControllerBase
|
|||||||
public async Task<ActionResult<CatalogReadDto>> Create(CatalogWriteDto dto, CancellationToken cancellationToken)
|
public async Task<ActionResult<CatalogReadDto>> Create(CatalogWriteDto dto, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var created = await _service.CreateAsync(dto, cancellationToken);
|
var created = await _service.CreateAsync(dto, cancellationToken);
|
||||||
|
if (created == null)
|
||||||
|
{
|
||||||
|
return Conflict();
|
||||||
|
}
|
||||||
return CreatedAtAction(nameof(GetById), new { id = created.Guid }, created);
|
return CreatedAtAction(nameof(GetById), new { id = created.Guid }, created);
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPut("{id:int}")]
|
[HttpPut("{id:int}")]
|
||||||
public async Task<ActionResult<CatalogReadDto>> Update(int id, CatalogWriteDto dto, CancellationToken cancellationToken)
|
public async Task<ActionResult<CatalogReadDto>> Update(int id, CatalogWriteDto dto, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
var current = await _service.GetByIdAsync(id, cancellationToken);
|
||||||
|
if (current == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
if (!string.Equals(current.CatTitle, dto.CatTitle, StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
return BadRequest("CatTitle cannot be changed.");
|
||||||
|
}
|
||||||
|
|
||||||
var updated = await _service.UpdateAsync(id, dto, cancellationToken);
|
var updated = await _service.UpdateAsync(id, dto, cancellationToken);
|
||||||
if (updated == null)
|
if (updated == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,8 +29,14 @@ public class CatalogService : ICatalogService
|
|||||||
return item == null ? null : _mapper.Map<CatalogReadDto>(item);
|
return item == null ? null : _mapper.Map<CatalogReadDto>(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CatalogReadDto> CreateAsync(CatalogWriteDto dto, CancellationToken cancellationToken = default)
|
public async Task<CatalogReadDto?> CreateAsync(CatalogWriteDto dto, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
var existing = await _repository.GetByTitleAsync(dto.CatTitle, cancellationToken);
|
||||||
|
if (existing != null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
var entity = _mapper.Map<VwmyCatalog>(dto);
|
var entity = _mapper.Map<VwmyCatalog>(dto);
|
||||||
entity.AddedWho = "system";
|
entity.AddedWho = "system";
|
||||||
entity.AddedWhen = DateTime.UtcNow;
|
entity.AddedWhen = DateTime.UtcNow;
|
||||||
@@ -51,6 +57,7 @@ public class CatalogService : ICatalogService
|
|||||||
|
|
||||||
var entity = _mapper.Map<VwmyCatalog>(dto);
|
var entity = _mapper.Map<VwmyCatalog>(dto);
|
||||||
entity.Guid = id;
|
entity.Guid = id;
|
||||||
|
entity.CatTitle = existing.CatTitle;
|
||||||
entity.AddedWho = existing.AddedWho;
|
entity.AddedWho = existing.AddedWho;
|
||||||
entity.AddedWhen = existing.AddedWhen;
|
entity.AddedWhen = existing.AddedWhen;
|
||||||
entity.ChangedWho = "system";
|
entity.ChangedWho = "system";
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ public interface ICatalogService
|
|||||||
{
|
{
|
||||||
Task<List<CatalogReadDto>> GetAllAsync(CancellationToken cancellationToken = default);
|
Task<List<CatalogReadDto>> GetAllAsync(CancellationToken cancellationToken = default);
|
||||||
Task<CatalogReadDto?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
Task<CatalogReadDto?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||||
Task<CatalogReadDto> CreateAsync(CatalogWriteDto dto, CancellationToken cancellationToken = default);
|
Task<CatalogReadDto?> CreateAsync(CatalogWriteDto dto, CancellationToken cancellationToken = default);
|
||||||
Task<CatalogReadDto?> UpdateAsync(int id, CatalogWriteDto dto, CancellationToken cancellationToken = default);
|
Task<CatalogReadDto?> UpdateAsync(int id, CatalogWriteDto dto, CancellationToken cancellationToken = default);
|
||||||
Task<bool> DeleteAsync(int id, CancellationToken cancellationToken = default);
|
Task<bool> DeleteAsync(int id, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -151,9 +151,9 @@ else
|
|||||||
if (isEditing)
|
if (isEditing)
|
||||||
{
|
{
|
||||||
var updated = await Api.UpdateAsync(editingId, formModel);
|
var updated = await Api.UpdateAsync(editingId, formModel);
|
||||||
if (!updated)
|
if (!updated.Success)
|
||||||
{
|
{
|
||||||
errorMessage = "Aktualisierung fehlgeschlagen.";
|
errorMessage = updated.Error ?? "Aktualisierung fehlgeschlagen.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,9 +162,9 @@ else
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var created = await Api.CreateAsync(formModel);
|
var created = await Api.CreateAsync(formModel);
|
||||||
if (created == null)
|
if (!created.Success || created.Value == null)
|
||||||
{
|
{
|
||||||
errorMessage = "Anlegen fehlgeschlagen.";
|
errorMessage = created.Error ?? "Anlegen fehlgeschlagen.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,9 +195,9 @@ else
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var deleted = await Api.DeleteAsync(id);
|
var deleted = await Api.DeleteAsync(id);
|
||||||
if (!deleted)
|
if (!deleted.Success)
|
||||||
{
|
{
|
||||||
errorMessage = "Löschen fehlgeschlagen.";
|
errorMessage = deleted.Error ?? "Löschen fehlgeschlagen.";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
|
using System.Net;
|
||||||
using System.Net.Http.Json;
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json;
|
||||||
using DbFirst.BlazorWasm.Models;
|
using DbFirst.BlazorWasm.Models;
|
||||||
|
|
||||||
namespace DbFirst.BlazorWasm.Services;
|
namespace DbFirst.BlazorWasm.Services;
|
||||||
@@ -24,26 +26,114 @@ public class CatalogApiClient
|
|||||||
return await _httpClient.GetFromJsonAsync<CatalogReadDto>($"{Endpoint}/{id}");
|
return await _httpClient.GetFromJsonAsync<CatalogReadDto>($"{Endpoint}/{id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<CatalogReadDto?> CreateAsync(CatalogWriteDto dto)
|
public async Task<ApiResult<CatalogReadDto?>> CreateAsync(CatalogWriteDto dto)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.PostAsJsonAsync(Endpoint, dto);
|
var response = await _httpClient.PostAsJsonAsync(Endpoint, dto);
|
||||||
if (!response.IsSuccessStatusCode)
|
if (response.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
return null;
|
var payload = await response.Content.ReadFromJsonAsync<CatalogReadDto>();
|
||||||
|
return ApiResult<CatalogReadDto?>.Ok(payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
return await response.Content.ReadFromJsonAsync<CatalogReadDto>();
|
var error = await ReadErrorAsync(response);
|
||||||
|
return ApiResult<CatalogReadDto?>.Fail(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> UpdateAsync(int id, CatalogWriteDto dto)
|
public async Task<ApiResult<bool>> UpdateAsync(int id, CatalogWriteDto dto)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.PutAsJsonAsync($"{Endpoint}/{id}", dto);
|
var response = await _httpClient.PutAsJsonAsync($"{Endpoint}/{id}", dto);
|
||||||
return response.IsSuccessStatusCode;
|
if (response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
return ApiResult<bool>.Ok(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
var error = await ReadErrorAsync(response);
|
||||||
|
return ApiResult<bool>.Fail(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> DeleteAsync(int id)
|
public async Task<ApiResult<bool>> DeleteAsync(int id)
|
||||||
{
|
{
|
||||||
var response = await _httpClient.DeleteAsync($"{Endpoint}/{id}");
|
var response = await _httpClient.DeleteAsync($"{Endpoint}/{id}");
|
||||||
return response.IsSuccessStatusCode;
|
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)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ public interface ICatalogRepository
|
|||||||
{
|
{
|
||||||
Task<List<VwmyCatalog>> GetAllAsync(CancellationToken cancellationToken = default);
|
Task<List<VwmyCatalog>> GetAllAsync(CancellationToken cancellationToken = default);
|
||||||
Task<VwmyCatalog?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
Task<VwmyCatalog?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||||
|
Task<VwmyCatalog?> GetByTitleAsync(string title, CancellationToken cancellationToken = default);
|
||||||
Task<VwmyCatalog> InsertAsync(VwmyCatalog catalog, CancellationToken cancellationToken = default);
|
Task<VwmyCatalog> InsertAsync(VwmyCatalog catalog, CancellationToken cancellationToken = default);
|
||||||
Task<VwmyCatalog?> UpdateAsync(int id, VwmyCatalog catalog, CancellationToken cancellationToken = default);
|
Task<VwmyCatalog?> UpdateAsync(int id, VwmyCatalog catalog, CancellationToken cancellationToken = default);
|
||||||
Task<bool> DeleteAsync(int id, CancellationToken cancellationToken = default);
|
Task<bool> DeleteAsync(int id, CancellationToken cancellationToken = default);
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ public class CatalogRepository : ICatalogRepository
|
|||||||
return await _db.VwmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.Guid == id, cancellationToken);
|
return await _db.VwmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.Guid == id, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<VwmyCatalog?> GetByTitleAsync(string title, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
return await _db.VwmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.CatTitle == title, cancellationToken);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<VwmyCatalog> InsertAsync(VwmyCatalog catalog, CancellationToken cancellationToken = default)
|
public async Task<VwmyCatalog> InsertAsync(VwmyCatalog catalog, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
var guidParam = new SqlParameter("@GUID", SqlDbType.Int)
|
var guidParam = new SqlParameter("@GUID", SqlDbType.Int)
|
||||||
@@ -63,8 +68,7 @@ public class CatalogRepository : ICatalogRepository
|
|||||||
|
|
||||||
var guidParam = new SqlParameter("@GUID", SqlDbType.Int)
|
var guidParam = new SqlParameter("@GUID", SqlDbType.Int)
|
||||||
{
|
{
|
||||||
Direction = ParameterDirection.Input,
|
Direction = ParameterDirection.Output
|
||||||
Value = id
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var catTitleParam = new SqlParameter("@CAT_TITLE", catalog.CatTitle);
|
var catTitleParam = new SqlParameter("@CAT_TITLE", catalog.CatTitle);
|
||||||
@@ -72,11 +76,22 @@ public class CatalogRepository : ICatalogRepository
|
|||||||
var changedWhoParam = new SqlParameter("@CHANGED_WHO", (object?)catalog.ChangedWho ?? DBNull.Value);
|
var changedWhoParam = new SqlParameter("@CHANGED_WHO", (object?)catalog.ChangedWho ?? DBNull.Value);
|
||||||
|
|
||||||
await _db.Database.ExecuteSqlRawAsync(
|
await _db.Database.ExecuteSqlRawAsync(
|
||||||
"EXEC dbo.PRTBMY_CATALOG_UPDATE @CAT_TITLE, @CAT_STRING, @CHANGED_WHO, @GUID",
|
"EXEC dbo.PRTBMY_CATALOG_UPDATE @CAT_TITLE, @CAT_STRING, @CHANGED_WHO, @GUID OUTPUT",
|
||||||
parameters: new[] { catTitleParam, catStringParam, changedWhoParam, guidParam },
|
parameters: new[] { catTitleParam, catStringParam, changedWhoParam, guidParam },
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
return await _db.VwmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.Guid == id, cancellationToken);
|
if (guidParam.Value == DBNull.Value)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var guid = (int)guidParam.Value;
|
||||||
|
if (guid == 0)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _db.VwmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.Guid == guid, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> DeleteAsync(int id, CancellationToken cancellationToken = default)
|
public async Task<bool> DeleteAsync(int id, CancellationToken cancellationToken = default)
|
||||||
|
|||||||
Reference in New Issue
Block a user