Changed CascadingDependencies to be more modular.

This commit is contained in:
Harrison Deng 2021-06-02 14:41:32 -05:00
parent d57a61d5ca
commit 3957d65370
10 changed files with 174 additions and 65 deletions

View File

@ -1,5 +1,5 @@
<CascadingAuthenticationState>
<CascadingDependencies>
<CascadingDependencies Dependencies="@dependencies">
<Content>
<Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
<Found Context="routeData">

View File

@ -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<RuntimeDependencyManager.Dependency> dependencies = new List<RuntimeDependencyManager.Dependency>();
protected override void OnInitialized()
{
base.OnInitialized();
dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(IReadOnlyDictionary<string, IShop>), "Shops", DownloadShops));
dependencies.Add(new RuntimeDependencyManager.Dependency(typeof(ApplicationProfile), "Application Profile", DownloadApplicationProfile));
}
private async ValueTask<object> DownloadShops(HttpClient publicHttp, HttpClient http, AuthenticationState authState, ILogger logger)
{
ShopModuleLoader loader = new ShopModuleLoader(publicHttp, logger);
return await loader.GetShops();
}
private async ValueTask<object> 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<ApplicationProfile>();
}
}
return new ApplicationProfile();
}
}
}

View File

@ -25,7 +25,9 @@ namespace MultiShop.Client.Pages
[CascadingParameter]
private Task<AuthenticationState> 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<ApplicationProfile>();
}
private string GetNavItemCssClass(Section section)
{
return "nav-item" + ((section == currentSection) ? " active" : null);

View File

@ -29,8 +29,11 @@ namespace MultiShop.Client.Pages
[CascadingParameter]
private Task<AuthenticationState> AuthenticationStateTask { get; set; }
[CascadingParameter(Name = "Shops")]
public IReadOnlyDictionary<string, IShop> Shops { get; set; }
[CascadingParameter(Name = "RuntimeDependencyManager")]
public RuntimeDependencyManager RuntimeDependencyManager { get; set; }
private IReadOnlyDictionary<string, IShop> 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<IReadOnlyDictionary<string, IShop>>();
}
protected override async Task OnInitializedAsync()

View File

@ -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<AuthenticationState> authenticationStateTask;
private ILogger logger;
private Dictionary<Type, Dictionary<string, object>> RuntimeLoadedDependencies = new Dictionary<Type, Dictionary<string, object>>();
public RuntimeDependencyManager(HttpClient publicHttp, HttpClient authenticatedHttp, Task<AuthenticationState> 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<string, object> dependencies = RuntimeLoadedDependencies.GetValueOrDefault(dependency.Type, new Dictionary<string, object>());
dependencies.Add(dependency.Name, await dependency.LoadDependency.Invoke(publicHttp, authenticatedHttp, await authenticationStateTask, logger));
RuntimeLoadedDependencies[dependency.Type] = dependencies;
}
public T Get<T>(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<string, object> 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<HttpClient, HttpClient, AuthenticationState, ILogger, ValueTask<object>> LoadDependency { get; }
public Dependency(Type type, string displayName, Func<HttpClient, HttpClient, AuthenticationState, ILogger, ValueTask<object>> LoadDependencyFunc, string name = null)
{
this.Type = type;
this.DisplayName = displayName;
this.Name = name ?? "";
this.LoadDependency = LoadDependencyFunc;
}
}
}
}

View File

@ -4,10 +4,8 @@
@if (loadingDisplay == null)
{
<CascadingValue Value="@applicationProfile" Name="ApplicationProfile">
<CascadingValue Value="@shops" Name="Shops">
@Content
</CascadingValue>
<CascadingValue Value="@manager" Name="RuntimeDependencyManager">
@Content
</CascadingValue>
} else {
@LoadingContent(loadingDisplay)

View File

@ -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<CascadingDependencies> Logger { get; set; }
@ -29,65 +27,36 @@ namespace MultiShop.Client.Shared
[Parameter]
public RenderFragment Content { get; set; }
[Parameter]
public ICollection<RuntimeDependencyManager.Dependency> Dependencies { get; set; }
private RuntimeDependencyManager manager;
private bool disposedValue;
private string loadingDisplay;
private IReadOnlyDictionary<string, IShop> 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<ApplicationProfile>();
}
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);
}
}
}

View File

@ -4,7 +4,7 @@
@implements IDisposable
@inject LayoutStateChangeNotifier LayoutStateChangeNotifier
<nav class=@ApplicationProfile.GetNavCssClass("navbar navbar-expand-lg")>
<nav class=@(RuntimeDependencyManager.Get<ApplicationProfile>().GetNavCssClass("navbar navbar-expand-lg"))>
<a class="navbar-brand" href="">
@BrandContent
</a>
@ -14,7 +14,7 @@
<div class=@NavMenuCssClass>
<ul class="navbar-nav mr-auto">
@foreach (string item in Items)
@foreach (string item in Places)
{
<li class="nav-item">
<NavLink class="nav-link" href=@item Match=@(string.IsNullOrEmpty(item) ? NavLinkMatch.All : NavLinkMatch.Prefix)>
@ -28,15 +28,15 @@
</nav>
@code {
[CascadingParameter(Name = "ApplicationProfile")]
private ApplicationProfile ApplicationProfile { get; set; }
[CascadingParameter(Name = "RuntimeDependencyManager")]
private RuntimeDependencyManager RuntimeDependencyManager { get; set; }
private bool collapseNavMenu = true;
private string NavMenuCssClass => (collapseNavMenu ? "collapse " : " ") + "navbar-collapse";
[Parameter]
public IList<string> Items { get; set; }
public IList<string> Places { get; set; }
[Parameter]
public RenderFragment BrandContent { get; set; }

View File

@ -5,7 +5,7 @@
@inject SignOutSessionStateManager SignOutManager
@inject IConfiguration Configuration
<HorizontalNavMenuTemplate Items=@PlaceOrder>
<HorizontalNavMenuTemplate Places=@PlaceOrder>
<BrandContent>
<img src="images/100x100.png" width="30" height="30" class="d-inline-block align-top">
MultiShop

View File

@ -5,7 +5,7 @@
@implements IDisposable
@inject LayoutStateChangeNotifier LayoutStateChangeNotifier
<div class=@ApplicationProfile.GetPageCssClass("page")>
<div class=@(dependencyManager.Get<ApplicationProfile>("").GetPageCssClass("page"))>
<HorizontalSiteNav />
<div class="content">
@Body
@ -13,14 +13,16 @@
</div>
@code {
[CascadingParameter(Name = "ApplicationProfile")]
private ApplicationProfile ApplicationProfile { get; set; }
[CascadingParameter(Name = "RuntimeDependencyManager")]
private RuntimeDependencyManager dependencyManager { get; set; }
private bool disposed;
protected override void OnInitialized()
{
base.OnInitialized();
LayoutStateChangeNotifier.Notify += UpdateState;
}
private async Task UpdateState()