diff --git a/src/MultiShop/Client/App.razor b/src/MultiShop/Client/App.razor index 426024b..5b27bc4 100644 --- a/src/MultiShop/Client/App.razor +++ b/src/MultiShop/Client/App.razor @@ -1,5 +1,5 @@  - + diff --git a/src/MultiShop/Client/App.razor.cs b/src/MultiShop/Client/App.razor.cs new file mode 100644 index 0000000..fbfd49c --- /dev/null +++ b/src/MultiShop/Client/App.razor.cs @@ -0,0 +1,45 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Json; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Logging; +using MultiShop.Client.Module; +using MultiShop.Client.Shared; +using MultiShop.Shared.Models; +using MultiShop.Shop.Framework; + +namespace MultiShop.Client +{ + public partial class App + { + private ICollection dependencies = new List(); + + protected override void OnInitialized() + { + base.OnInitialized(); + dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IReadOnlyDictionary), "Shops", DownloadShops)); + dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(ApplicationProfile), "Application Profile", DownloadApplicationProfile)); + } + + private async ValueTask DownloadShops(HttpClient publicHttp, HttpClient http, AuthenticationState authState, ILogger logger) + { + ShopModuleLoader loader = new ShopModuleLoader(publicHttp, logger); + return await loader.GetShops(); + } + + private async ValueTask DownloadApplicationProfile(HttpClient publicHttp, HttpClient http, AuthenticationState authState, ILogger logger) + { + if (authState.User.Identity.IsAuthenticated) + { + logger.LogDebug($"User is logged in. Attempting to fetch application profile."); + HttpResponseMessage response = await http.GetAsync("Profile/Application"); + if (response.IsSuccessStatusCode) + { + return await response.Content.ReadFromJsonAsync(); + } + } + return new ApplicationProfile(); + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/Pages/Configuration.razor.cs b/src/MultiShop/Client/Pages/Configuration.razor.cs index 5f630ab..ced37e5 100644 --- a/src/MultiShop/Client/Pages/Configuration.razor.cs +++ b/src/MultiShop/Client/Pages/Configuration.razor.cs @@ -25,7 +25,9 @@ namespace MultiShop.Client.Pages [CascadingParameter] private Task AuthenticationStateTask { get; set; } - [CascadingParameter(Name = "ApplicationProfile")] + [CascadingParameter(Name = "RuntimeDependencyManager")] + private RuntimeDependencyManager RuntimeDependencyManager { get; set; } + private ApplicationProfile ApplicationProfile { get; set; } private bool collapseNavMenu; @@ -50,6 +52,12 @@ namespace MultiShop.Client.Pages Section.Search }; + protected override void OnInitialized() + { + base.OnInitialized(); + ApplicationProfile = RuntimeDependencyManager.Get(); + } + private string GetNavItemCssClass(Section section) { return "nav-item" + ((section == currentSection) ? " active" : null); diff --git a/src/MultiShop/Client/Pages/Search.razor.cs b/src/MultiShop/Client/Pages/Search.razor.cs index da5a9da..6eb35f9 100644 --- a/src/MultiShop/Client/Pages/Search.razor.cs +++ b/src/MultiShop/Client/Pages/Search.razor.cs @@ -29,8 +29,11 @@ namespace MultiShop.Client.Pages [CascadingParameter] private Task AuthenticationStateTask { get; set; } - [CascadingParameter(Name = "Shops")] - public IReadOnlyDictionary Shops { get; set; } + [CascadingParameter(Name = "RuntimeDependencyManager")] + public RuntimeDependencyManager RuntimeDependencyManager { get; set; } + + private IReadOnlyDictionary Shops { get; set; } + [Parameter] public string Query { get; set; } @@ -51,6 +54,7 @@ namespace MultiShop.Client.Pages { base.OnInitialized(); LayoutStateChangeNotifier.Notify += UpdateState; + Shops = RuntimeDependencyManager.Get>(); } protected override async Task OnInitializedAsync() diff --git a/src/MultiShop/Client/RuntimeDependencyManager.cs b/src/MultiShop/Client/RuntimeDependencyManager.cs new file mode 100644 index 0000000..c906e57 --- /dev/null +++ b/src/MultiShop/Client/RuntimeDependencyManager.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.Extensions.Logging; + +namespace MultiShop.Client +{ + public class RuntimeDependencyManager : IAsyncDisposable + { + private bool disposedValue; + private HttpClient authenticatedHttp; + private HttpClient publicHttp; + private Task authenticationStateTask; + private ILogger logger; + private Dictionary> RuntimeLoadedDependencies = new Dictionary>(); + + public RuntimeDependencyManager(HttpClient publicHttp, HttpClient authenticatedHttp, Task authenticationStateTask, ILogger logger) + { + this.publicHttp = publicHttp; + this.authenticatedHttp = authenticatedHttp; + this.authenticationStateTask = authenticationStateTask; + this.logger = logger; + } + + + public async ValueTask SetupDependency(Dependency dependency) + { + logger.LogDebug($"Setting up dependency of type \"{dependency.Type}\" named \"{dependency.Name}\"."); + Dictionary dependencies = RuntimeLoadedDependencies.GetValueOrDefault(dependency.Type, new Dictionary()); + dependencies.Add(dependency.Name, await dependency.LoadDependency.Invoke(publicHttp, authenticatedHttp, await authenticationStateTask, logger)); + RuntimeLoadedDependencies[dependency.Type] = dependencies; + } + + public T Get(string name = "") { + Type type = typeof(T); + if (!RuntimeLoadedDependencies.ContainsKey(typeof(T))) throw new InvalidOperationException($"No dependency of type {type}."); + if (!RuntimeLoadedDependencies[type].ContainsKey(name)) throw new InvalidOperationException($"No dependency of type {type} with name {name}."); + return (T) RuntimeLoadedDependencies[type][name]; + } + + public async ValueTask DisposeAsync() + { + if (!disposedValue) { + foreach (Dictionary dependencies in RuntimeLoadedDependencies.Values) + { + foreach (object dependency in dependencies.Values) + { + IDisposable disposableDep = dependency as IDisposable; + if (disposableDep != null) { + disposableDep.Dispose(); + } else { + IAsyncDisposable asyncDisposableDep = dependency as IAsyncDisposable; + if (asyncDisposableDep != null) { + await asyncDisposableDep.DisposeAsync(); + } + } + } + } + } + RuntimeLoadedDependencies.Clear(); + disposedValue = true; + } + + + public class Dependency + { + public Type Type { get; } + public string Name { get; } + public string DisplayName { get; } + public Func> LoadDependency { get; } + + public Dependency(Type type, string displayName, Func> LoadDependencyFunc, string name = null) + { + this.Type = type; + this.DisplayName = displayName; + this.Name = name ?? ""; + this.LoadDependency = LoadDependencyFunc; + } + } + } +} \ No newline at end of file diff --git a/src/MultiShop/Client/Shared/CascadingDependencies.razor b/src/MultiShop/Client/Shared/CascadingDependencies.razor index 27f77a6..b5c1bd0 100644 --- a/src/MultiShop/Client/Shared/CascadingDependencies.razor +++ b/src/MultiShop/Client/Shared/CascadingDependencies.razor @@ -4,10 +4,8 @@ @if (loadingDisplay == null) { - - - @Content - + + @Content } else { @LoadingContent(loadingDisplay) diff --git a/src/MultiShop/Client/Shared/CascadingDependencies.razor.cs b/src/MultiShop/Client/Shared/CascadingDependencies.razor.cs index 7be6426..eec2619 100644 --- a/src/MultiShop/Client/Shared/CascadingDependencies.razor.cs +++ b/src/MultiShop/Client/Shared/CascadingDependencies.razor.cs @@ -1,18 +1,16 @@ using System; using System.Collections.Generic; using System.Net.Http; -using System.Net.Http.Json; using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Authorization; using Microsoft.Extensions.Logging; -using MultiShop.Client.Module; using MultiShop.Shared.Models; using MultiShop.Shop.Framework; namespace MultiShop.Client.Shared { - public partial class CascadingDependencies : IDisposable + public partial class CascadingDependencies : IAsyncDisposable { [Inject] private ILogger Logger { get; set; } @@ -29,65 +27,36 @@ namespace MultiShop.Client.Shared [Parameter] public RenderFragment Content { get; set; } + [Parameter] + public ICollection Dependencies { get; set; } + + private RuntimeDependencyManager manager; private bool disposedValue; private string loadingDisplay; - private IReadOnlyDictionary shops; - - private ApplicationProfile applicationProfile; - protected override async Task OnInitializedAsync() { - loadingDisplay = ""; + loadingDisplay = "stuff"; await base.OnInitializedAsync(); - await DownloadShops(HttpClientFactory.CreateClient("Public-MultiShop.ServerAPI")); - await DownloadApplicationProfile(HttpClientFactory.CreateClient("MultiShop.ServerAPI")); + manager = new RuntimeDependencyManager(HttpClientFactory.CreateClient("Public-MultiShop.ServerAPI"), HttpClientFactory.CreateClient("MultiShop.ServerAPI"), AuthenticationStateTask, Logger); + foreach (RuntimeDependencyManager.Dependency dep in Dependencies) + { + loadingDisplay = dep.DisplayName; + await manager.SetupDependency(dep); + } loadingDisplay = null; } - private async Task DownloadShops(HttpClient http) - { - loadingDisplay = "shops"; - ShopModuleLoader loader = new ShopModuleLoader(http, Logger); - shops = await loader.GetShops(); - } + - private async Task DownloadApplicationProfile(HttpClient http) + public async ValueTask DisposeAsync() { - loadingDisplay = "profile"; - AuthenticationState authState = await AuthenticationStateTask; - if (authState.User.Identity.IsAuthenticated) - { - Logger.LogDebug($"User is logged in. Attempting to fetch application profile."); - HttpResponseMessage response = await http.GetAsync("Profile/Application"); - if (response.IsSuccessStatusCode) - { - applicationProfile = await response.Content.ReadFromJsonAsync(); - } + if (!disposedValue) { + await manager.DisposeAsync(); } - if (applicationProfile == null) applicationProfile = new ApplicationProfile(); + disposedValue = true; } - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - foreach (string shopName in shops.Keys) - { - shops[shopName].Dispose(); - } - } - disposedValue = true; - } - } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } } } \ No newline at end of file diff --git a/src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor b/src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor index fe67739..bed498c 100644 --- a/src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor +++ b/src/MultiShop/Client/Shared/HorizontalNavMenuTemplate.razor @@ -4,7 +4,7 @@ @implements IDisposable @inject LayoutStateChangeNotifier LayoutStateChangeNotifier -