From a05f2486b9fd59edd122bbde06212e4da472878f Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Wed, 17 Jan 2024 17:37:27 +0100 Subject: [PATCH] Show server errors --- Pages/UserListPanel.razor.cs | 53 ++++++++++++++++++--------- Program.cs | 2 +- Properties/launchSettings.json | 8 ++-- Services/HttpUsersService.cs | 67 ++++++++++++++++++++++++---------- Services/ServiceException.cs | 18 +++++++++ 5 files changed, 106 insertions(+), 42 deletions(-) create mode 100644 Services/ServiceException.cs diff --git a/Pages/UserListPanel.razor.cs b/Pages/UserListPanel.razor.cs index c9ebb9b..f4eff47 100644 --- a/Pages/UserListPanel.razor.cs +++ b/Pages/UserListPanel.razor.cs @@ -1,7 +1,9 @@ -using AdminPanel.Models; +using System.Text; +using AdminPanel.Models; using AdminPanel.Services; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; +using Microsoft.Extensions.Primitives; using MudBlazor; namespace AdminPanel.Pages @@ -13,10 +15,10 @@ namespace AdminPanel.Pages [Inject] private ILogger Logger { get; init; } private MudDataGrid Grid { get; set; } - - + + private string? SearchString { get; set; } - + private HashSet SelectedItems { get; set; } = new(); @@ -31,7 +33,8 @@ namespace AdminPanel.Pages private async Task> LoadUsersFromServer(GridState state) { Logger.LogDebug($"Loading users from server, state = {state} searchString = {SearchString}"); - var (count, users) = await Service.ListUsers((uint)(state.Page * state.PageSize), (uint)state.PageSize, SearchString); + var (count, users) = + await Service.ListUsers((uint)(state.Page * state.PageSize), (uint)state.PageSize, SearchString); return new GridData { TotalItems = (int)count, @@ -47,10 +50,9 @@ namespace AdminPanel.Pages { await Service.UpdateUser(user); } - catch (Exception) + catch (ServiceException err) { - Snackbar.Add( - "Server responded with errors, your given input may be incorrect.\nIf you entered a new email, verify that the email is not used by another member."); + ShowErrors(err); } } @@ -66,9 +68,25 @@ namespace AdminPanel.Pages Logger.LogDebug($"Added user : {user}"); await Grid.ReloadServerData(); } - catch (Exception) + catch (ServiceException err) { - Snackbar.Add("Server responded with errors, your given input may be incorrect."); + ShowErrors(err); + } + } + + private void ShowErrors(ServiceException e) + { + foreach (var erronedArgument in e.ArgumentMessages) + { + var sb = new StringBuilder(erronedArgument.Key); + sb.Append(" :"); + + foreach (var message in erronedArgument.Value) + { + sb.Append("\n\t-"); + sb.Append(message); + } + Snackbar.Add(sb.ToString()); } } @@ -81,24 +99,23 @@ namespace AdminPanel.Pages await Service.RemoveUsers(users); await Grid.ReloadServerData(); } - catch (Exception) + catch (ServiceException err) { - Snackbar.Add("Server responded with errors"); + ShowErrors(err); } } private Func VerifyLength(uint min, uint max) { - return s => s.Length >= min && s.Length <= max ? null : $"length is incorrect (must be between {min} and {max})"; + return s => s.Length >= min && s.Length <= max + ? null + : $"length is incorrect (must be between {min} and {max})"; } private void ValidateSearch(KeyboardEventArgs e) { - if (e.Key == "Enter") - { - Grid.ReloadServerData(); - Logger.LogDebug($"Searching for {SearchString}"); - } + Grid.ReloadServerData(); + Logger.LogDebug($"Searching for {SearchString}"); } } } \ No newline at end of file diff --git a/Program.cs b/Program.cs index b8036fc..91dd8e2 100644 --- a/Program.cs +++ b/Program.cs @@ -12,7 +12,7 @@ builder.RootComponents.Add("head::after"); builder.Services .AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); -builder.Logging.SetMinimumLevel(LogLevel.Debug); +// builder.Logging.SetMinimumLevel(LogLevel.Debug); var client = new HttpClient { diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index db1c6be..d5df975 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -2,8 +2,8 @@ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:18284", + "iisExpress": { + "applicationUrl": "http://localhost:18284", "sslPort": 0 } }, @@ -12,8 +12,8 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5081", + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "http://localhost:5081", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/Services/HttpUsersService.cs b/Services/HttpUsersService.cs index f7621f7..824fefd 100644 --- a/Services/HttpUsersService.cs +++ b/Services/HttpUsersService.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; +using System.Net; using System.Net.Http.Json; using AdminPanel.Models; @@ -12,30 +14,54 @@ public class HttpUsersService : IUsersService this.Client = client; } - private record ListUsersResponse(List Users, uint TotalCount); + private record ServerErrorMessageDto(string? Field, string? Error, string Message); - public async Task<(uint, List)> ListUsers(uint from, uint len, string? searchString = null) + private async Task EnsureResponseIsOk(HttpResponseMessage response) { - var httpResponse = await Client.GetAsync($"/api/admin/list-users?start={from}&n={len}&search={searchString ?? ""}"); - httpResponse.EnsureSuccessStatusCode(); + + if (response.StatusCode == HttpStatusCode.OK) + { + return; + } - var response = await httpResponse.Content.ReadFromJsonAsync() - ?? throw new Exception("Received non-parseable json from server"); + var content = await response.Content.ReadFromJsonAsync(); + var messages = content! + .GroupBy(e => e.Field ?? e.Error!) + .ToDictionary( + g => g.Key, + g => g.ToList().ConvertAll(e => e.Message) + ); + + throw new ServiceException("Server refused request", messages); + } + + private record ListUsersResponseDto(List Users, uint TotalCount); + + public async Task<(uint, List)> ListUsers(uint from, uint len, string? searchString = null) + { + var httpResponse = + await Client.GetAsync($"/api/admin/list-users?start={from}&n={len}&search={searchString ?? ""}"); + await EnsureResponseIsOk(httpResponse); + + var response = await httpResponse.Content.ReadFromJsonAsync() + ?? throw new Exception("Received non-parseable json from server"); return (response.TotalCount, response.Users); } - private record AddUserRequest(string Username, string Email, string Password, bool IsAdmin); + private record AddUserRequestDto(string Username, string Email, string Password, bool IsAdmin); + + private record AddUserResponseDto(uint Id); - private record AddUserResponse(uint Id); - public async Task AddUser(string username, string email, string password, bool isAdmin) { - var httpResponse = await Client.PostAsJsonAsync("/api/admin/user/add", new AddUserRequest(username, email, password, isAdmin)); - httpResponse.EnsureSuccessStatusCode(); + var httpResponse = await Client.PostAsJsonAsync("/api/admin/user/add", + new AddUserRequestDto(username, email, password, isAdmin)); + + await EnsureResponseIsOk(httpResponse); - var response = await httpResponse.Content.ReadFromJsonAsync() - ?? throw new Exception("Received non-parseable json from server"); + var response = await httpResponse.Content.ReadFromJsonAsync() + ?? throw new Exception("Received non-parseable json from server"); return new User { @@ -46,19 +72,22 @@ public class HttpUsersService : IUsersService }; } - private record RemoveUsersRequest(uint[] Identifiers); + private record RemoveUsersRequestDto(uint[] Identifiers); public async Task RemoveUsers(IEnumerable userIds) { - var httpResponse = await Client.PostAsJsonAsync("/api/admin/user/remove-all", new RemoveUsersRequest(userIds.ToArray())); - httpResponse.EnsureSuccessStatusCode(); + var httpResponse = + await Client.PostAsJsonAsync("/api/admin/user/remove-all", new RemoveUsersRequestDto(userIds.ToArray())); + + await EnsureResponseIsOk(httpResponse); } - private record UpdateUserRequest(string Email, string Username, bool IsAdmin); + private record UpdateUserRequestDto(string Email, string Username, bool IsAdmin); public async Task UpdateUser(User user) { - var httpResponse = await Client.PostAsJsonAsync($"/api/admin/user/{user.Id}/update", new UpdateUserRequest(user.Email, user.Name, user.IsAdmin)); - httpResponse.EnsureSuccessStatusCode(); + var httpResponse = await Client.PostAsJsonAsync($"/api/admin/user/{user.Id}/update", + new UpdateUserRequestDto(user.Email, user.Name, user.IsAdmin)); + await EnsureResponseIsOk(httpResponse); } } \ No newline at end of file diff --git a/Services/ServiceException.cs b/Services/ServiceException.cs new file mode 100644 index 0000000..8dbcb00 --- /dev/null +++ b/Services/ServiceException.cs @@ -0,0 +1,18 @@ +namespace AdminPanel.Services; + +class ServiceException : Exception +{ + public Dictionary> ArgumentMessages { get; init; } + + + + public ServiceException(string? message, Dictionary> arguments) : base(message) + { + ArgumentMessages = arguments; + } + + public ServiceException(string? message, Dictionary> arguments, Exception? innerException) : base(message, innerException) + { + ArgumentMessages = arguments; + } +} \ No newline at end of file