From 2d71797143fd022c09ec8f3fda7e9f574b2256b9 Mon Sep 17 00:00:00 2001 From: Antonio Costa Date: Wed, 27 Aug 2025 11:10:14 +0100 Subject: [PATCH 1/3] Implement client --- POC.CodelessAPIClient.WebApp/Configs.cs | 6 +++ .../HttpClients/ClassicClient.cs | 48 +++++++++++++++++++ POC.CodelessAPIClient.WebApp/Program.cs | 10 +++- POC.CodelessAPIClient.WebApp/appsettings.json | 5 +- 4 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 POC.CodelessAPIClient.WebApp/Configs.cs create mode 100644 POC.CodelessAPIClient.WebApp/HttpClients/ClassicClient.cs diff --git a/POC.CodelessAPIClient.WebApp/Configs.cs b/POC.CodelessAPIClient.WebApp/Configs.cs new file mode 100644 index 0000000..cf8b560 --- /dev/null +++ b/POC.CodelessAPIClient.WebApp/Configs.cs @@ -0,0 +1,6 @@ +namespace POC.CodelessAPIClient.WebApp; + +public class Configs +{ + public string ApiUrl { get; set; } +} \ No newline at end of file diff --git a/POC.CodelessAPIClient.WebApp/HttpClients/ClassicClient.cs b/POC.CodelessAPIClient.WebApp/HttpClients/ClassicClient.cs new file mode 100644 index 0000000..2aa4e3d --- /dev/null +++ b/POC.CodelessAPIClient.WebApp/HttpClients/ClassicClient.cs @@ -0,0 +1,48 @@ +using POC.CodelessAPIClient.Models; + +namespace POC.CodelessAPIClient.WebApp.HttpClients; + +public class ClassicClient : IClient +{ + private readonly HttpClient _http; + + public ClassicClient(HttpClient http) + { + _http = http; + } + + public async Task Get() + { + var result = await _http.GetFromJsonAsync("ShoppingList"); + if (result is null) throw new InvalidOperationException("Empty response from GET /ShoppingList"); + return result; + } + + public async Task AddItem(ShoppingListItem item) + { + var resp = await _http.PostAsJsonAsync("ShoppingList", item); + resp.EnsureSuccessStatusCode(); + var list = await resp.Content.ReadFromJsonAsync(); + if (list is null) throw new InvalidOperationException("Empty response from POST /ShoppingList"); + return list; + } + + public async Task UpdateItem(ShoppingListItem item) + { + var resp = await _http.PutAsJsonAsync("ShoppingList", item); + resp.EnsureSuccessStatusCode(); + var list = await resp.Content.ReadFromJsonAsync(); + if (list is null) throw new InvalidOperationException("Empty response from PUT /ShoppingList"); + return list; + } + + public async Task RemoveItem(ShoppingListItem item) + { + if (item.Id is null) throw new ArgumentException("Item Id must be set for delete.", nameof(item)); + var resp = await _http.DeleteAsync($"ShoppingList/{item.Id}"); + resp.EnsureSuccessStatusCode(); + var list = await resp.Content.ReadFromJsonAsync(); + if (list is null) throw new InvalidOperationException("Empty response from DELETE /ShoppingList/{id}"); + return list; + } +} \ No newline at end of file diff --git a/POC.CodelessAPIClient.WebApp/Program.cs b/POC.CodelessAPIClient.WebApp/Program.cs index dd2bd98..7a83651 100644 --- a/POC.CodelessAPIClient.WebApp/Program.cs +++ b/POC.CodelessAPIClient.WebApp/Program.cs @@ -13,7 +13,15 @@ public static void Main(string[] args) builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); - builder.Services.AddSingleton(); + var configs = new Configs(); + builder.Configuration.GetSection("Configurations").Bind(configs); + + // Register typed HttpClient for ClassicClient using base URL from configs + builder.Services.AddHttpClient((sp, http) => + { + http.BaseAddress = new Uri(configs.ApiUrl.TrimEnd('/') + "/"); + }); + builder.Services.AddSingleton(); var app = builder.Build(); diff --git a/POC.CodelessAPIClient.WebApp/appsettings.json b/POC.CodelessAPIClient.WebApp/appsettings.json index 10f68b8..ad37993 100644 --- a/POC.CodelessAPIClient.WebApp/appsettings.json +++ b/POC.CodelessAPIClient.WebApp/appsettings.json @@ -5,5 +5,8 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "Configurations": { + "ApiUrl": "http://localhost:5052" + } } From 9e9b363eb166570f45462113424ffe6d2ca903f2 Mon Sep 17 00:00:00 2001 From: Antonio Costa Date: Wed, 27 Aug 2025 11:29:25 +0100 Subject: [PATCH 2/3] Implement Auth client --- POC.CodelessAPIClient.WebApp/Configs.cs | 1 + .../HttpClients/AuthClassicClient.cs | 18 ++++++++++++++++++ POC.CodelessAPIClient.WebApp/Program.cs | 8 +++++++- POC.CodelessAPIClient.WebApp/appsettings.json | 3 ++- 4 files changed, 28 insertions(+), 2 deletions(-) create mode 100644 POC.CodelessAPIClient.WebApp/HttpClients/AuthClassicClient.cs diff --git a/POC.CodelessAPIClient.WebApp/Configs.cs b/POC.CodelessAPIClient.WebApp/Configs.cs index cf8b560..ce4420d 100644 --- a/POC.CodelessAPIClient.WebApp/Configs.cs +++ b/POC.CodelessAPIClient.WebApp/Configs.cs @@ -3,4 +3,5 @@ namespace POC.CodelessAPIClient.WebApp; public class Configs { public string ApiUrl { get; set; } + public string ApiBearerToken { get; set; } } \ No newline at end of file diff --git a/POC.CodelessAPIClient.WebApp/HttpClients/AuthClassicClient.cs b/POC.CodelessAPIClient.WebApp/HttpClients/AuthClassicClient.cs new file mode 100644 index 0000000..b70da33 --- /dev/null +++ b/POC.CodelessAPIClient.WebApp/HttpClients/AuthClassicClient.cs @@ -0,0 +1,18 @@ +namespace POC.CodelessAPIClient.WebApp.HttpClients; + +public class AuthClassicClient : IAuthorizedClient +{ + private readonly HttpClient _http; + public AuthClassicClient(HttpClient http) + { + _http = http; + } + + public async Task GetAuthorization() + { + var result = await _http.GetAsync("SecureStatus"); + if (!result.IsSuccessStatusCode) + return "Not Authorized"; + return await result.Content.ReadAsStringAsync(); + } +} \ No newline at end of file diff --git a/POC.CodelessAPIClient.WebApp/Program.cs b/POC.CodelessAPIClient.WebApp/Program.cs index 7a83651..150cac9 100644 --- a/POC.CodelessAPIClient.WebApp/Program.cs +++ b/POC.CodelessAPIClient.WebApp/Program.cs @@ -22,7 +22,13 @@ public static void Main(string[] args) http.BaseAddress = new Uri(configs.ApiUrl.TrimEnd('/') + "/"); }); - builder.Services.AddSingleton(); + // Register typed HttpClient for AuthClassicClient using base URL from configs + builder.Services.AddHttpClient((sp, http) => + { + http.BaseAddress = new Uri(configs.ApiUrl.TrimEnd('/') + "/"); + // Add Authorisation header + http.DefaultRequestHeaders.Add("Authorization", $"Bearer {configs.ApiBearerToken}"); + }); var app = builder.Build(); diff --git a/POC.CodelessAPIClient.WebApp/appsettings.json b/POC.CodelessAPIClient.WebApp/appsettings.json index ad37993..dba5697 100644 --- a/POC.CodelessAPIClient.WebApp/appsettings.json +++ b/POC.CodelessAPIClient.WebApp/appsettings.json @@ -7,6 +7,7 @@ }, "AllowedHosts": "*", "Configurations": { - "ApiUrl": "http://localhost:5052" + "ApiUrl": "http://localhost:5052", + "ApiBearerToken": "your-secret-token" } } From d7bf7461910955370fb78c7f1e31d54c8e128da0 Mon Sep 17 00:00:00 2001 From: Antonio Costa Date: Wed, 27 Aug 2025 12:14:14 +0100 Subject: [PATCH 3/3] Improvements --- POC.CodelessAPIClient.WebApp/Program.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/POC.CodelessAPIClient.WebApp/Program.cs b/POC.CodelessAPIClient.WebApp/Program.cs index 150cac9..c6099ff 100644 --- a/POC.CodelessAPIClient.WebApp/Program.cs +++ b/POC.CodelessAPIClient.WebApp/Program.cs @@ -13,19 +13,18 @@ public static void Main(string[] args) builder.Services.AddRazorComponents() .AddInteractiveServerComponents(); - var configs = new Configs(); - builder.Configuration.GetSection("Configurations").Bind(configs); + var configs = builder.Configuration.GetSection("Configurations").Get(); // Register typed HttpClient for ClassicClient using base URL from configs builder.Services.AddHttpClient((sp, http) => { - http.BaseAddress = new Uri(configs.ApiUrl.TrimEnd('/') + "/"); + http.BaseAddress = new Uri(configs!.ApiUrl.TrimEnd('/') + "/"); }); // Register typed HttpClient for AuthClassicClient using base URL from configs builder.Services.AddHttpClient((sp, http) => { - http.BaseAddress = new Uri(configs.ApiUrl.TrimEnd('/') + "/"); + http.BaseAddress = new Uri(configs!.ApiUrl.TrimEnd('/') + "/"); // Add Authorisation header http.DefaultRequestHeaders.Add("Authorization", $"Bearer {configs.ApiBearerToken}"); });