From 88c34ef94bcda169aad8139b84873032ff6d160e Mon Sep 17 00:00:00 2001 From: OlgunR Date: Wed, 4 Feb 2026 13:00:45 +0100 Subject: [PATCH] Add MassData feature with API, paging, and Blazor grid Introduces MassData management to backend and Blazor frontend: - Adds API endpoint for MassData count and paging - Updates repository and controller for count support - Implements MediatR query/handler for count - Adds Blazor page and grid for viewing/editing MassData - Registers MassDataApiClient and integrates with DI - Supports paging, upsert, and UI feedback in grid --- DbFirst.API/Controllers/MassDataController.cs | 7 + .../Queries/GetMassDataCountHandler.cs | 19 ++ .../MassData/Queries/GetMassDataCountQuery.cs | 5 + .../Repositories/IMassDataRepository.cs | 1 + .../Components/MassDataGrid.razor | 196 ++++++++++++++++++ DbFirst.BlazorWasm/Layout/NavMenu.razor | 5 + DbFirst.BlazorWasm/Models/MassDataReadDto.cs | 12 ++ DbFirst.BlazorWasm/Models/MassDataWriteDto.cs | 9 + DbFirst.BlazorWasm/Pages/Massdata.razor | 7 + DbFirst.BlazorWasm/Program.cs | 1 + .../Services/MassDataApiClient.cs | 35 ++++ .../Components/Layout/NavMenu.razor | 6 + .../Components/MassDataGrid.razor | 196 ++++++++++++++++++ .../Components/Pages/Massdata.razor | 7 + .../Models/MassDataReadDto.cs | 12 ++ .../Models/MassDataWriteDto.cs | 9 + DbFirst.BlazorWebApp/Program.cs | 5 + .../Services/MassDataApiClient.cs | 35 ++++ .../Repositories/MassDataRepository.cs | 5 + 19 files changed, 572 insertions(+) create mode 100644 DbFirst.Application/MassData/Queries/GetMassDataCountHandler.cs create mode 100644 DbFirst.Application/MassData/Queries/GetMassDataCountQuery.cs create mode 100644 DbFirst.BlazorWasm/Components/MassDataGrid.razor create mode 100644 DbFirst.BlazorWasm/Models/MassDataReadDto.cs create mode 100644 DbFirst.BlazorWasm/Models/MassDataWriteDto.cs create mode 100644 DbFirst.BlazorWasm/Pages/Massdata.razor create mode 100644 DbFirst.BlazorWasm/Services/MassDataApiClient.cs create mode 100644 DbFirst.BlazorWebApp/Components/MassDataGrid.razor create mode 100644 DbFirst.BlazorWebApp/Components/Pages/Massdata.razor create mode 100644 DbFirst.BlazorWebApp/Models/MassDataReadDto.cs create mode 100644 DbFirst.BlazorWebApp/Models/MassDataWriteDto.cs create mode 100644 DbFirst.BlazorWebApp/Services/MassDataApiClient.cs diff --git a/DbFirst.API/Controllers/MassDataController.cs b/DbFirst.API/Controllers/MassDataController.cs index 96d09f3..2dbe55a 100644 --- a/DbFirst.API/Controllers/MassDataController.cs +++ b/DbFirst.API/Controllers/MassDataController.cs @@ -17,6 +17,13 @@ public class MassDataController : ControllerBase _mediator = mediator; } + [HttpGet("count")] + public async Task> GetCount(CancellationToken cancellationToken) + { + var count = await _mediator.Send(new GetMassDataCountQuery(), cancellationToken); + return Ok(count); + } + [HttpGet] public async Task>> GetAll([FromQuery] int? skip, [FromQuery] int? take, CancellationToken cancellationToken) { diff --git a/DbFirst.Application/MassData/Queries/GetMassDataCountHandler.cs b/DbFirst.Application/MassData/Queries/GetMassDataCountHandler.cs new file mode 100644 index 0000000..dd75539 --- /dev/null +++ b/DbFirst.Application/MassData/Queries/GetMassDataCountHandler.cs @@ -0,0 +1,19 @@ +using DbFirst.Application.Repositories; +using MediatR; + +namespace DbFirst.Application.MassData.Queries; + +public class GetMassDataCountHandler : IRequestHandler +{ + private readonly IMassDataRepository _repository; + + public GetMassDataCountHandler(IMassDataRepository repository) + { + _repository = repository; + } + + public async Task Handle(GetMassDataCountQuery request, CancellationToken cancellationToken) + { + return await _repository.GetCountAsync(cancellationToken); + } +} diff --git a/DbFirst.Application/MassData/Queries/GetMassDataCountQuery.cs b/DbFirst.Application/MassData/Queries/GetMassDataCountQuery.cs new file mode 100644 index 0000000..0944a91 --- /dev/null +++ b/DbFirst.Application/MassData/Queries/GetMassDataCountQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace DbFirst.Application.MassData.Queries; + +public record GetMassDataCountQuery : IRequest; diff --git a/DbFirst.Application/Repositories/IMassDataRepository.cs b/DbFirst.Application/Repositories/IMassDataRepository.cs index b578dc9..f27112a 100644 --- a/DbFirst.Application/Repositories/IMassDataRepository.cs +++ b/DbFirst.Application/Repositories/IMassDataRepository.cs @@ -4,6 +4,7 @@ namespace DbFirst.Application.Repositories; public interface IMassDataRepository { + Task GetCountAsync(CancellationToken cancellationToken = default); Task> GetAllAsync(int? skip = null, int? take = null, CancellationToken cancellationToken = default); Task GetByCustomerNameAsync(string customerName, CancellationToken cancellationToken = default); Task UpsertByCustomerNameAsync(string customerName, decimal amount, bool statusFlag, string category, CancellationToken cancellationToken = default); diff --git a/DbFirst.BlazorWasm/Components/MassDataGrid.razor b/DbFirst.BlazorWasm/Components/MassDataGrid.razor new file mode 100644 index 0000000..2e1e6b5 --- /dev/null +++ b/DbFirst.BlazorWasm/Components/MassDataGrid.razor @@ -0,0 +1,196 @@ +@inject MassDataApiClient Api + + + +@if (!string.IsNullOrWhiteSpace(errorMessage)) +{ + +} +else if (!string.IsNullOrWhiteSpace(infoMessage)) +{ + +} + +
+ Neuen Eintrag anlegen +
+ +@if (showForm) +{ +
+ + + + + + + + + + + + + + + + + Speichern + Abbrechen + + + + +
+} + +@if (isLoading) +{ +

Lade Daten...

+} +else if (items.Count == 0) +{ +

Keine Einträge vorhanden.

+} +else +{ +
+ + + + + + + + + + + + @{ var item = (MassDataReadDto)cell.DataItem; } +
+ Bearbeiten + Löschen +
+
+
+
+
+ +
+ +
+
+} + +@code { + private const int PageSize = 100; + private List items = new(); + private MassDataWriteDto formModel = new(); + private string amountText = string.Empty; + private bool isLoading; + private bool showForm; + private string? errorMessage; + private string? infoMessage; + private int pageIndex; + private int pageCount = 1; + + protected override async Task OnInitializedAsync() + { + await LoadPage(0); + } + + private async Task LoadPage(int page) + { + isLoading = true; + errorMessage = null; + try + { + var total = await Api.GetCountAsync(); + pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)PageSize)); + pageIndex = Math.Clamp(page, 0, pageCount - 1); + + var skip = pageIndex * PageSize; + items = await Api.GetAllAsync(skip, PageSize); + } + catch (Exception ex) + { + errorMessage = $"MassData konnten nicht geladen werden: {ex.Message}"; + } + finally + { + isLoading = false; + StateHasChanged(); + } + } + + private async Task OnPageChanged(int index) + { + await LoadPage(index); + } + + private void StartCreate() + { + infoMessage = "Anlegen ist aktuell noch nicht verfügbar."; + } + + private void StartEdit(MassDataReadDto item) + { + formModel = new MassDataWriteDto + { + CustomerName = item.CustomerName, + Amount = item.Amount, + Category = item.Category, + StatusFlag = item.StatusFlag + }; + amountText = item.Amount.ToString("0.00"); + showForm = true; + infoMessage = null; + errorMessage = null; + } + + private async Task HandleSubmit() + { + errorMessage = null; + infoMessage = null; + + if (!decimal.TryParse(amountText, out var amount)) + { + errorMessage = "Amount ist ungültig."; + return; + } + + formModel.Amount = amount; + + try + { + await Api.UpsertAsync(formModel); + infoMessage = "MassData aktualisiert."; + showForm = false; + await LoadPage(pageIndex); + } + catch (Exception ex) + { + errorMessage = $"Fehler beim Speichern: {ex.Message}"; + } + } + + private void CancelEdit() + { + showForm = false; + infoMessage = null; + errorMessage = null; + } + + private void ShowDeleteNotReady() + { + infoMessage = "Löschen ist aktuell noch nicht verfügbar."; + } +} diff --git a/DbFirst.BlazorWasm/Layout/NavMenu.razor b/DbFirst.BlazorWasm/Layout/NavMenu.razor index 7a576d8..b8f8519 100644 --- a/DbFirst.BlazorWasm/Layout/NavMenu.razor +++ b/DbFirst.BlazorWasm/Layout/NavMenu.razor @@ -26,6 +26,11 @@ Dashboards + diff --git a/DbFirst.BlazorWasm/Models/MassDataReadDto.cs b/DbFirst.BlazorWasm/Models/MassDataReadDto.cs new file mode 100644 index 0000000..33c2495 --- /dev/null +++ b/DbFirst.BlazorWasm/Models/MassDataReadDto.cs @@ -0,0 +1,12 @@ +namespace DbFirst.BlazorWasm.Models; + +public class MassDataReadDto +{ + public int Id { get; set; } + public string CustomerName { get; set; } = string.Empty; + public decimal Amount { get; set; } + public string Category { get; set; } = string.Empty; + public bool StatusFlag { get; set; } + public DateTime AddedWhen { get; set; } + public DateTime? ChangedWhen { get; set; } +} diff --git a/DbFirst.BlazorWasm/Models/MassDataWriteDto.cs b/DbFirst.BlazorWasm/Models/MassDataWriteDto.cs new file mode 100644 index 0000000..d18a255 --- /dev/null +++ b/DbFirst.BlazorWasm/Models/MassDataWriteDto.cs @@ -0,0 +1,9 @@ +namespace DbFirst.BlazorWasm.Models; + +public class MassDataWriteDto +{ + public string CustomerName { get; set; } = string.Empty; + public decimal Amount { get; set; } + public string Category { get; set; } = string.Empty; + public bool StatusFlag { get; set; } +} diff --git a/DbFirst.BlazorWasm/Pages/Massdata.razor b/DbFirst.BlazorWasm/Pages/Massdata.razor new file mode 100644 index 0000000..6ad20c4 --- /dev/null +++ b/DbFirst.BlazorWasm/Pages/Massdata.razor @@ -0,0 +1,7 @@ +@page "/massdata" + +MassData + +

MassData

+ + diff --git a/DbFirst.BlazorWasm/Program.cs b/DbFirst.BlazorWasm/Program.cs index fa74d9d..bb2ee57 100644 --- a/DbFirst.BlazorWasm/Program.cs +++ b/DbFirst.BlazorWasm/Program.cs @@ -18,5 +18,6 @@ var apiBaseUrl = builder.Configuration["ApiBaseUrl"] ?? builder.HostEnvironment. builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(apiBaseUrl) }); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); await builder.Build().RunAsync(); diff --git a/DbFirst.BlazorWasm/Services/MassDataApiClient.cs b/DbFirst.BlazorWasm/Services/MassDataApiClient.cs new file mode 100644 index 0000000..43f69bc --- /dev/null +++ b/DbFirst.BlazorWasm/Services/MassDataApiClient.cs @@ -0,0 +1,35 @@ +using System.Net.Http.Json; +using DbFirst.BlazorWasm.Models; + +namespace DbFirst.BlazorWasm.Services; + +public class MassDataApiClient +{ + private readonly HttpClient _httpClient; + private const string Endpoint = "api/massdata"; + + public MassDataApiClient(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task GetCountAsync() + { + var result = await _httpClient.GetFromJsonAsync("api/massdata/count"); + return result ?? 0; + } + + public async Task> GetAllAsync(int skip, int take) + { + var result = await _httpClient.GetFromJsonAsync>($"{Endpoint}?skip={skip}&take={take}"); + return result ?? new List(); + } + + public async Task UpsertAsync(MassDataWriteDto dto) + { + var response = await _httpClient.PostAsJsonAsync($"{Endpoint}/upsert", dto); + response.EnsureSuccessStatusCode(); + var payload = await response.Content.ReadFromJsonAsync(); + return payload ?? new MassDataReadDto(); + } +} diff --git a/DbFirst.BlazorWebApp/Components/Layout/NavMenu.razor b/DbFirst.BlazorWebApp/Components/Layout/NavMenu.razor index 0f5b8e8..ec7c0b4 100644 --- a/DbFirst.BlazorWebApp/Components/Layout/NavMenu.razor +++ b/DbFirst.BlazorWebApp/Components/Layout/NavMenu.razor @@ -36,6 +36,12 @@ Dashboards + + diff --git a/DbFirst.BlazorWebApp/Components/MassDataGrid.razor b/DbFirst.BlazorWebApp/Components/MassDataGrid.razor new file mode 100644 index 0000000..2e1e6b5 --- /dev/null +++ b/DbFirst.BlazorWebApp/Components/MassDataGrid.razor @@ -0,0 +1,196 @@ +@inject MassDataApiClient Api + + + +@if (!string.IsNullOrWhiteSpace(errorMessage)) +{ + +} +else if (!string.IsNullOrWhiteSpace(infoMessage)) +{ + +} + +
+ Neuen Eintrag anlegen +
+ +@if (showForm) +{ +
+ + + + + + + + + + + + + + + + + Speichern + Abbrechen + + + + +
+} + +@if (isLoading) +{ +

Lade Daten...

+} +else if (items.Count == 0) +{ +

Keine Einträge vorhanden.

+} +else +{ +
+ + + + + + + + + + + + @{ var item = (MassDataReadDto)cell.DataItem; } +
+ Bearbeiten + Löschen +
+
+
+
+
+ +
+ +
+
+} + +@code { + private const int PageSize = 100; + private List items = new(); + private MassDataWriteDto formModel = new(); + private string amountText = string.Empty; + private bool isLoading; + private bool showForm; + private string? errorMessage; + private string? infoMessage; + private int pageIndex; + private int pageCount = 1; + + protected override async Task OnInitializedAsync() + { + await LoadPage(0); + } + + private async Task LoadPage(int page) + { + isLoading = true; + errorMessage = null; + try + { + var total = await Api.GetCountAsync(); + pageCount = Math.Max(1, (int)Math.Ceiling(total / (double)PageSize)); + pageIndex = Math.Clamp(page, 0, pageCount - 1); + + var skip = pageIndex * PageSize; + items = await Api.GetAllAsync(skip, PageSize); + } + catch (Exception ex) + { + errorMessage = $"MassData konnten nicht geladen werden: {ex.Message}"; + } + finally + { + isLoading = false; + StateHasChanged(); + } + } + + private async Task OnPageChanged(int index) + { + await LoadPage(index); + } + + private void StartCreate() + { + infoMessage = "Anlegen ist aktuell noch nicht verfügbar."; + } + + private void StartEdit(MassDataReadDto item) + { + formModel = new MassDataWriteDto + { + CustomerName = item.CustomerName, + Amount = item.Amount, + Category = item.Category, + StatusFlag = item.StatusFlag + }; + amountText = item.Amount.ToString("0.00"); + showForm = true; + infoMessage = null; + errorMessage = null; + } + + private async Task HandleSubmit() + { + errorMessage = null; + infoMessage = null; + + if (!decimal.TryParse(amountText, out var amount)) + { + errorMessage = "Amount ist ungültig."; + return; + } + + formModel.Amount = amount; + + try + { + await Api.UpsertAsync(formModel); + infoMessage = "MassData aktualisiert."; + showForm = false; + await LoadPage(pageIndex); + } + catch (Exception ex) + { + errorMessage = $"Fehler beim Speichern: {ex.Message}"; + } + } + + private void CancelEdit() + { + showForm = false; + infoMessage = null; + errorMessage = null; + } + + private void ShowDeleteNotReady() + { + infoMessage = "Löschen ist aktuell noch nicht verfügbar."; + } +} diff --git a/DbFirst.BlazorWebApp/Components/Pages/Massdata.razor b/DbFirst.BlazorWebApp/Components/Pages/Massdata.razor new file mode 100644 index 0000000..6ad20c4 --- /dev/null +++ b/DbFirst.BlazorWebApp/Components/Pages/Massdata.razor @@ -0,0 +1,7 @@ +@page "/massdata" + +MassData + +

MassData

+ + diff --git a/DbFirst.BlazorWebApp/Models/MassDataReadDto.cs b/DbFirst.BlazorWebApp/Models/MassDataReadDto.cs new file mode 100644 index 0000000..f7ab6db --- /dev/null +++ b/DbFirst.BlazorWebApp/Models/MassDataReadDto.cs @@ -0,0 +1,12 @@ +namespace DbFirst.BlazorWebApp.Models; + +public class MassDataReadDto +{ + public int Id { get; set; } + public string CustomerName { get; set; } = string.Empty; + public decimal Amount { get; set; } + public string Category { get; set; } = string.Empty; + public bool StatusFlag { get; set; } + public DateTime AddedWhen { get; set; } + public DateTime? ChangedWhen { get; set; } +} diff --git a/DbFirst.BlazorWebApp/Models/MassDataWriteDto.cs b/DbFirst.BlazorWebApp/Models/MassDataWriteDto.cs new file mode 100644 index 0000000..81af9d5 --- /dev/null +++ b/DbFirst.BlazorWebApp/Models/MassDataWriteDto.cs @@ -0,0 +1,9 @@ +namespace DbFirst.BlazorWebApp.Models; + +public class MassDataWriteDto +{ + public string CustomerName { get; set; } = string.Empty; + public decimal Amount { get; set; } + public string Category { get; set; } = string.Empty; + public bool StatusFlag { get; set; } +} diff --git a/DbFirst.BlazorWebApp/Program.cs b/DbFirst.BlazorWebApp/Program.cs index b08475c..c838164 100644 --- a/DbFirst.BlazorWebApp/Program.cs +++ b/DbFirst.BlazorWebApp/Program.cs @@ -21,11 +21,16 @@ if (!string.IsNullOrWhiteSpace(apiBaseUrl)) { client.BaseAddress = new Uri(apiBaseUrl); }); + builder.Services.AddHttpClient(client => + { + client.BaseAddress = new Uri(apiBaseUrl); + }); } else { builder.Services.AddHttpClient(); builder.Services.AddHttpClient(); + builder.Services.AddHttpClient(); } var app = builder.Build(); diff --git a/DbFirst.BlazorWebApp/Services/MassDataApiClient.cs b/DbFirst.BlazorWebApp/Services/MassDataApiClient.cs new file mode 100644 index 0000000..11d4832 --- /dev/null +++ b/DbFirst.BlazorWebApp/Services/MassDataApiClient.cs @@ -0,0 +1,35 @@ +using System.Net.Http.Json; +using DbFirst.BlazorWebApp.Models; + +namespace DbFirst.BlazorWebApp.Services; + +public class MassDataApiClient +{ + private readonly HttpClient _httpClient; + private const string Endpoint = "api/massdata"; + + public MassDataApiClient(HttpClient httpClient) + { + _httpClient = httpClient; + } + + public async Task GetCountAsync() + { + var result = await _httpClient.GetFromJsonAsync("api/massdata/count"); + return result ?? 0; + } + + public async Task> GetAllAsync(int skip, int take) + { + var result = await _httpClient.GetFromJsonAsync>($"{Endpoint}?skip={skip}&take={take}"); + return result ?? new List(); + } + + public async Task UpsertAsync(MassDataWriteDto dto) + { + var response = await _httpClient.PostAsJsonAsync($"{Endpoint}/upsert", dto); + response.EnsureSuccessStatusCode(); + var payload = await response.Content.ReadFromJsonAsync(); + return payload ?? new MassDataReadDto(); + } +} diff --git a/DbFirst.Infrastructure/Repositories/MassDataRepository.cs b/DbFirst.Infrastructure/Repositories/MassDataRepository.cs index e054a98..ba754cc 100644 --- a/DbFirst.Infrastructure/Repositories/MassDataRepository.cs +++ b/DbFirst.Infrastructure/Repositories/MassDataRepository.cs @@ -15,6 +15,11 @@ public class MassDataRepository : IMassDataRepository _db = db; } + public async Task GetCountAsync(CancellationToken cancellationToken = default) + { + return await _db.Massdata.AsNoTracking().CountAsync(cancellationToken); + } + public async Task> GetAllAsync(CancellationToken cancellationToken = default) { return await _db.Massdata.AsNoTracking().ToListAsync(cancellationToken);