diff --git a/demo/WasmHost/Pages/Counter.razor b/demo/WasmHost/Pages/Counter.razor index 7ad43fd..d5f2336 100644 --- a/demo/WasmHost/Pages/Counter.razor +++ b/demo/WasmHost/Pages/Counter.razor @@ -9,11 +9,18 @@ - + + + + +

Component loading errored...

+
- - + + + + @code { diff --git a/nuget/BlazorLazyLoading.Components/BlazorLazyLoading.Components.csproj b/nuget/BlazorLazyLoading.Components/BlazorLazyLoading.Components.csproj index 6dc1cf6..ff65dbf 100644 --- a/nuget/BlazorLazyLoading.Components/BlazorLazyLoading.Components.csproj +++ b/nuget/BlazorLazyLoading.Components/BlazorLazyLoading.Components.csproj @@ -3,6 +3,10 @@ netstandard2.1 3.0 + 8.0 + enable + nullable + true diff --git a/nuget/BlazorLazyLoading.Server/BlazorLazyLoading.Server.csproj b/nuget/BlazorLazyLoading.Server/BlazorLazyLoading.Server.csproj index ac0c216..bb76df5 100644 --- a/nuget/BlazorLazyLoading.Server/BlazorLazyLoading.Server.csproj +++ b/nuget/BlazorLazyLoading.Server/BlazorLazyLoading.Server.csproj @@ -5,7 +5,8 @@ Library 8.0 enable - true + nullable + true diff --git a/nuget/BlazorLazyLoading.Server/StartupExtensions.cs b/nuget/BlazorLazyLoading.Server/StartupExtensions.cs index 8eda079..6e42716 100644 --- a/nuget/BlazorLazyLoading.Server/StartupExtensions.cs +++ b/nuget/BlazorLazyLoading.Server/StartupExtensions.cs @@ -11,16 +11,30 @@ namespace BlazorLazyLoading.Server { + /// + /// Server startup extensions for BlazorLazyLoading + /// public static class BLLServerStartupExtensions { + /// + /// Registers BlazorLazyLoading services + /// public static IServiceCollection AddLazyLoading( this IServiceCollection services, LazyLoadingOptions options) { - services.AddScoped(); + if (options.UseAssemblyIsolation) + { + services.AddScoped(); + } + else + { + services.AddSingleton(); + } + services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(typeof(IAssemblyDataLocator), options.AssemblyDataLocator ?? typeof(AssemblyDataLocator)); services.AddSingleton( p => @@ -38,6 +52,9 @@ public static IServiceCollection AddLazyLoading( return services; } + /// + /// Configures the host to use BlazorLazyLoading + /// public static void UseLazyLoading( this IApplicationBuilder app) { @@ -53,13 +70,26 @@ public static void UseLazyLoading( } } + /// + /// BlazorLazyLoading options + /// public sealed class LazyLoadingOptions { /// - /// Specifies a list of Module Names (hints) to: - /// - Download DLLs from them - /// - Use their manifest to locate lazy resources + /// Specifies a list of Module Names (hints) to download DLLs from them and use their manifest to locate lazy resources /// public IEnumerable ModuleHints { get; set; } = Array.Empty(); + + /// + ///
Configures assembly isolation level. Do NOT set this to 'false' unless you want to share 'static' fields between users.
+ ///
Keeping this enabled ensures that the server can be scaled horizontally.
+ ///
default: true
+ ///
+ public bool UseAssemblyIsolation { get; set; } = true; + + /// + /// Configures a custom AssemblyDataLocator. The type must implement IAssemblyDataLocator. + /// + public Type? AssemblyDataLocator { get; set; } = null; } } diff --git a/nuget/BlazorLazyLoading.Wasm/BlazorLazyLoading.Wasm.csproj b/nuget/BlazorLazyLoading.Wasm/BlazorLazyLoading.Wasm.csproj index 998f853..22aecb4 100644 --- a/nuget/BlazorLazyLoading.Wasm/BlazorLazyLoading.Wasm.csproj +++ b/nuget/BlazorLazyLoading.Wasm/BlazorLazyLoading.Wasm.csproj @@ -4,7 +4,8 @@ netstandard2.1 8.0 enable - true + nullable + true diff --git a/nuget/BlazorLazyLoading.Wasm/StartupExtensions.cs b/nuget/BlazorLazyLoading.Wasm/StartupExtensions.cs index d5bd9ee..d88b75d 100644 --- a/nuget/BlazorLazyLoading.Wasm/StartupExtensions.cs +++ b/nuget/BlazorLazyLoading.Wasm/StartupExtensions.cs @@ -7,15 +7,21 @@ namespace BlazorLazyLoading.Wasm { + /// + /// WebAssembly startup extensions for BlazorLazyLoading + /// public static class BLLWasmStartupExtensions { + /// + /// Registers BlazorLazyLoading services + /// public static IServiceCollection AddLazyLoading( this IServiceCollection services, LazyLoadingOptions options) { services.AddSingleton(); services.AddSingleton(); - services.AddSingleton(); + services.AddSingleton(typeof(IAssemblyDataLocator), options.AssemblyDataLocator ?? typeof(AssemblyDataLocator)); services.AddSingleton(); services.AddSingleton(); @@ -29,13 +35,19 @@ public static IServiceCollection AddLazyLoading( } } + /// + /// BlazorLazyLoading options + /// public sealed class LazyLoadingOptions { /// - /// Specifies a list of Module Names (hints) to: - /// - Download DLLs from them - /// - Use their manifest to locate lazy resources + /// Specifies a list of Module Names (hints) to download DLLs from them and use their manifest to locate lazy resources /// public IEnumerable ModuleHints { get; set; } = Array.Empty(); + + /// + /// Configures a custom AssemblyDataLocator. The type must implement IAssemblyDataLocator. + /// + public Type? AssemblyDataLocator { get; set; } = null; } } diff --git a/src/AssemblyLoader.Server/AssemblyLoader.Server.csproj b/src/AssemblyLoader.Server/AssemblyLoader.Server.csproj index 89f0568..a889bae 100644 --- a/src/AssemblyLoader.Server/AssemblyLoader.Server.csproj +++ b/src/AssemblyLoader.Server/AssemblyLoader.Server.csproj @@ -5,7 +5,8 @@ Library 8.0 enable - true + nullable + true diff --git a/src/AssemblyLoader.Wasm/AssemblyLoader.Wasm.csproj b/src/AssemblyLoader.Wasm/AssemblyLoader.Wasm.csproj index 3a9f085..01771f8 100644 --- a/src/AssemblyLoader.Wasm/AssemblyLoader.Wasm.csproj +++ b/src/AssemblyLoader.Wasm/AssemblyLoader.Wasm.csproj @@ -4,7 +4,8 @@ netstandard2.1 8.0 enable - true + nullable + true diff --git a/src/AssemblyLoader.Wasm/Services/AppDomainAssemblyLoadContext.cs b/src/AssemblyLoader.Wasm/Services/AppDomainAssemblyLoadContext.cs index e8f6fc5..bfd6ec7 100644 --- a/src/AssemblyLoader.Wasm/Services/AppDomainAssemblyLoadContext.cs +++ b/src/AssemblyLoader.Wasm/Services/AppDomainAssemblyLoadContext.cs @@ -9,7 +9,7 @@ namespace BlazorLazyLoading.Wasm.Services { - public sealed class AppDomainAssemblyLoadContext : IAssemblyLoadContext + internal sealed class AppDomainAssemblyLoadContext : IAssemblyLoadContext { private readonly object _domainLock = new object(); private readonly AppDomain _baseDomain; diff --git a/src/AssemblyLoader/Abstractions/IAssemblyDataLocator.cs b/src/AssemblyLoader/Abstractions/IAssemblyDataLocator.cs index 87c5154..8440d4d 100644 --- a/src/AssemblyLoader/Abstractions/IAssemblyDataLocator.cs +++ b/src/AssemblyLoader/Abstractions/IAssemblyDataLocator.cs @@ -4,10 +4,13 @@ namespace BlazorLazyLoading.Abstractions { + /// + /// Locates assembly DLLs based on context and hints + /// public interface IAssemblyDataLocator { /// - /// Returns a list of possible paths where the assembly data is + /// Returns a list of possible paths to look for the assembly data (dll) /// public IEnumerable GetFindPaths(AssemblyName assemblyName, AssemblyLoaderContext context); } diff --git a/src/AssemblyLoader/AssemblyLoader.csproj b/src/AssemblyLoader/AssemblyLoader.csproj index 762f34f..d03d89d 100644 --- a/src/AssemblyLoader/AssemblyLoader.csproj +++ b/src/AssemblyLoader/AssemblyLoader.csproj @@ -4,7 +4,8 @@ netstandard2.1 8.0 enable - true + nullable + true diff --git a/src/AssemblyLoader/Comparers/AssemblyByNameAndVersionComparer.cs b/src/AssemblyLoader/Comparers/AssemblyByNameAndVersionComparer.cs index 9566e5e..023013a 100644 --- a/src/AssemblyLoader/Comparers/AssemblyByNameAndVersionComparer.cs +++ b/src/AssemblyLoader/Comparers/AssemblyByNameAndVersionComparer.cs @@ -1,6 +1,5 @@ using System; using System.Reflection; -using BlazorLazyLoading.Helpers; namespace BlazorLazyLoading.Comparers { diff --git a/src/AssemblyLoader/Extensions/EnumerableExtensions.cs b/src/AssemblyLoader/Extensions/EnumerableExtensions.cs new file mode 100644 index 0000000..f8db812 --- /dev/null +++ b/src/AssemblyLoader/Extensions/EnumerableExtensions.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BlazorLazyLoading.Extensions +{ + public static class EnumerableExtensions + { + public static IEnumerable NotNull( + this IEnumerable source) + where T : class + { + return source.Where(i => i != null).Cast(); + } + + public static IEnumerable DistinctBy( + this IEnumerable source, + Func selector) + { + return source + .GroupBy(m => selector(m)) + .Select(g => g.First()); + } + } +} diff --git a/src/AssemblyLoader/Helpers/HashCode.cs b/src/AssemblyLoader/Helpers/HashCode.cs deleted file mode 100644 index 8a6fdb0..0000000 --- a/src/AssemblyLoader/Helpers/HashCode.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System.Collections.Generic; - -namespace BlazorLazyLoading.Helpers -{ - //public static class HashCode - //{ - // public static int Combine(params object[] instances) - // { - // int hash = 17; - - // foreach (var i in instances) - // { - // hash = unchecked((hash * 31) + (i?.GetHashCode() ?? 0)); - // } - - // return hash; - // } - //} -} diff --git a/src/AssemblyLoader/Models/AssemblyData.cs b/src/AssemblyLoader/Models/AssemblyData.cs index 57620d9..5223bc9 100644 --- a/src/AssemblyLoader/Models/AssemblyData.cs +++ b/src/AssemblyLoader/Models/AssemblyData.cs @@ -1,10 +1,17 @@ namespace BlazorLazyLoading.Models { + /// + /// Contains the data required to load an assembly + /// public sealed class AssemblyData { + /// Bytes from the DLL public readonly byte[] DllBytes; + + /// Bytes from the PDB public readonly byte[]? PdbBytes; + /// Constructs AssemblyData public AssemblyData( byte[] dllBytes, byte[]? pdbBytes) diff --git a/src/AssemblyLoader/Models/AssemblyLoaderContext.cs b/src/AssemblyLoader/Models/AssemblyLoaderContext.cs index fe11308..287ee59 100644 --- a/src/AssemblyLoader/Models/AssemblyLoaderContext.cs +++ b/src/AssemblyLoader/Models/AssemblyLoaderContext.cs @@ -3,13 +3,23 @@ namespace BlazorLazyLoading.Models { + /// + /// Describes the tree of loaded assembly dependencies + /// public sealed class AssemblyLoaderContext { + /// + /// Assembly Name + /// public readonly AssemblyName AssemblyName; + /// Parent Node public readonly AssemblyLoaderContext? Parent = null; + + /// Children Nodes public readonly List Children = new List(); + /// Constructs the AssemblyLoaderContext public AssemblyLoaderContext(AssemblyName name) { AssemblyName = name; @@ -21,6 +31,9 @@ private AssemblyLoaderContext(AssemblyName name, AssemblyLoaderContext parent) AssemblyName = name; } + /// + /// Creates a new scope for the current AssemblyLoaderContext + /// public AssemblyLoaderContext NewScope(AssemblyName name) { var scope = new AssemblyLoaderContext(name, this); diff --git a/src/AssemblyLoader/Services/AssemblyDataLocator.cs b/src/AssemblyLoader/Services/AssemblyDataLocator.cs index 41812e6..a3a721f 100644 --- a/src/AssemblyLoader/Services/AssemblyDataLocator.cs +++ b/src/AssemblyLoader/Services/AssemblyDataLocator.cs @@ -5,16 +5,21 @@ namespace BlazorLazyLoading.Services { + /// + /// Locates assembly DLLs based on context and hints + /// public sealed class AssemblyDataLocator : IAssemblyDataLocator { private readonly ILazyModuleHintsProvider _lazyModuleNamesProvider; + /// public AssemblyDataLocator( ILazyModuleHintsProvider lazyModuleProvider) { _lazyModuleNamesProvider = lazyModuleProvider; } + /// public IEnumerable GetFindPaths( AssemblyName assemblyName, AssemblyLoaderContext context) diff --git a/src/LazyComponents/LazyComponent/Lazy.cs b/src/LazyComponents/LazyComponent/Lazy.cs index 42a2185..f757813 100644 --- a/src/LazyComponents/LazyComponent/Lazy.cs +++ b/src/LazyComponents/LazyComponent/Lazy.cs @@ -1,10 +1,10 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Threading.Tasks; using BlazorLazyLoading.Abstractions; +using BlazorLazyLoading.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Newtonsoft.Json.Linq; @@ -17,7 +17,13 @@ public class Lazy : ComponentBase public string Name { get; set; } = null!; [Parameter] - public RenderFragment? ChildContent { get; set; } = null; + public bool Required { get; set; } = false; + + [Parameter] + public RenderFragment? Loading { get; set; } = null; + + [Parameter] + public RenderFragment? Error { get; set; } = null; [Parameter] public Func? OnBeforeLoadAsync { get; set; } = null; @@ -35,10 +41,23 @@ public class Lazy : ComponentBase [Inject] private IManifestRepository _manifestRepository { get; set; } = null!; + private RenderFragment? _currentFallbackBuilder = null; + + protected override void OnInitialized() + { + _currentFallbackBuilder = Loading; + base.OnInitialized(); // trigger initial render + } + protected override async Task OnInitializedAsync() { await base.OnInitializedAsync().ConfigureAwait(false); + if (OnBeforeLoadAsync != null) + { + await OnBeforeLoadAsync(this); + } + var allManifests = await _manifestRepository.GetAllAsync().ConfigureAwait(false); var manifests = allManifests @@ -71,6 +90,7 @@ protected override async Task OnInitializedAsync() })); var bestMatches = manifests + .Where(i => i.Score > 0) .GroupBy(i => i.Score) .OrderByDescending(i => i.Key) .FirstOrDefault() @@ -78,13 +98,16 @@ protected override async Task OnInitializedAsync() if (bestMatches == null || !bestMatches.Any()) { - Debug.WriteLine($"Unable to find lazy component '{Name}'"); + DisplayErrorView(); + ThrowIfRequired($"Unable to find lazy component '{Name}'. Required: {(Required ? "true" : "false")}"); return; } if (bestMatches.Count > 1) { - throw new NotSupportedException($"Multiple matches for Component with name '{Name}': '{string.Join(";", bestMatches.Select(m => m.Match.TypeFullName))}'"); + DisplayErrorView(); + ThrowIfRequired($"Multiple matches for Component with name '{Name}': '{string.Join(";", bestMatches.Select(m => m.Match.TypeFullName))}'"); + return; } var bestMatch = bestMatches.First(); @@ -97,12 +120,13 @@ protected override async Task OnInitializedAsync() }) .ConfigureAwait(false); - if (OnBeforeLoadAsync != null) + Type = componentAssembly?.GetType(bestMatch.Match.TypeFullName); + + if (Type == null) { - await OnBeforeLoadAsync(this); + DisplayErrorView(false); } - Type = componentAssembly?.GetType(bestMatch.Match.TypeFullName); StateHasChanged(); } @@ -125,34 +149,25 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) private void BuildFallbackComponent(RenderTreeBuilder builder) { - if (ChildContent != null) - { - ChildContent.Invoke(builder); - return; - } - - builder.OpenElement(0, "div"); - builder.AddAttribute(1, "class", "bll-loading"); - builder.AddContent(2, "Loading..."); - builder.CloseElement(); + _currentFallbackBuilder?.Invoke(builder); } - } - public static class X - { - public static IEnumerable NotNull(this IEnumerable source) - where T : class + private void DisplayErrorView(bool render = true) { - return source.Where(i => i != null).Cast(); + _currentFallbackBuilder = Error; + if (render) StateHasChanged(); } - public static IEnumerable DistinctBy( - this IEnumerable source, - Func selector) + private void ThrowIfRequired(string errorMessage) { - return source - .GroupBy(m => selector(m)) - .Select(g => g.First()); + if (Required) + { + throw new NotSupportedException(errorMessage); + } + else + { + Debug.WriteLine(errorMessage); + } } } } diff --git a/src/LazyComponents/LazyComponents.csproj b/src/LazyComponents/LazyComponents.csproj index c9447c9..641bc3e 100644 --- a/src/LazyComponents/LazyComponents.csproj +++ b/src/LazyComponents/LazyComponents.csproj @@ -4,7 +4,8 @@ netstandard2.1 8.0 enable - true + nullable + true diff --git a/src/LazyComponents/LazyRoute/LazyRouter.cs b/src/LazyComponents/LazyRoute/LazyRouter.cs index c0b9261..abf5a65 100644 --- a/src/LazyComponents/LazyRoute/LazyRouter.cs +++ b/src/LazyComponents/LazyRoute/LazyRouter.cs @@ -5,6 +5,7 @@ using System.Reflection; using System.Threading.Tasks; using BlazorLazyLoading.Abstractions; +using BlazorLazyLoading.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Routing; using Microsoft.Extensions.Logging; diff --git a/src/ManifestGenerator/ManifestGenerator.csproj b/src/ManifestGenerator/ManifestGenerator.csproj index 17bfa71..d2ee05c 100644 --- a/src/ManifestGenerator/ManifestGenerator.csproj +++ b/src/ManifestGenerator/ManifestGenerator.csproj @@ -4,7 +4,8 @@ netstandard2.0 8.0 enable - true + nullable + true diff --git a/src/ManifestReader/Abstractions/IManifestLocator.cs b/src/ManifestReader/Abstractions/IManifestLocator.cs index 5953775..b021736 100644 --- a/src/ManifestReader/Abstractions/IManifestLocator.cs +++ b/src/ManifestReader/Abstractions/IManifestLocator.cs @@ -2,6 +2,9 @@ namespace BlazorLazyLoading.Abstractions { + /// + /// Locates _lazy.json manifests + /// public interface IManifestLocator { /// diff --git a/src/ManifestReader/ManifestReader.csproj b/src/ManifestReader/ManifestReader.csproj index a1afc28..275aead 100644 --- a/src/ManifestReader/ManifestReader.csproj +++ b/src/ManifestReader/ManifestReader.csproj @@ -4,7 +4,8 @@ netstandard2.1 8.0 enable - true + nullable + true diff --git a/src/ManifestReader/Services/ManifestLocator.cs b/src/ManifestReader/Services/ManifestLocator.cs index 9787ef9..22f0274 100644 --- a/src/ManifestReader/Services/ManifestLocator.cs +++ b/src/ManifestReader/Services/ManifestLocator.cs @@ -4,16 +4,21 @@ namespace BlazorLazyLoading.Services { + /// + /// Locates _lazy.json manifests based on hints + /// public sealed class ManifestLocator : IManifestLocator { private readonly ILazyModuleHintsProvider _moduleHintsProvider; + /// public ManifestLocator( ILazyModuleHintsProvider moduleHintsProvider) { _moduleHintsProvider = moduleHintsProvider; } + /// public IEnumerable GetManifestPaths() { return _moduleHintsProvider.ModuleNameHints diff --git a/src/ManifestReader/Services/ManifestRepository.cs b/src/ManifestReader/Services/ManifestRepository.cs index afa5275..00043a6 100644 --- a/src/ManifestReader/Services/ManifestRepository.cs +++ b/src/ManifestReader/Services/ManifestRepository.cs @@ -4,6 +4,7 @@ using System.Text; using System.Threading.Tasks; using BlazorLazyLoading.Abstractions; +using BlazorLazyLoading.Extensions; using BlazorLazyLoading.Models; using Newtonsoft.Json.Linq; @@ -70,7 +71,7 @@ private async Task FetchAllManifests() private async Task ReadAllManifests() { var manifestDataTasks = new Dictionary>(); - var manifestPaths = _manifestLocator.GetManifestPaths(); + var manifestPaths = _manifestLocator.GetManifestPaths().Except(_loadedPaths.Keys); foreach (var path in manifestPaths) { @@ -79,11 +80,16 @@ private async Task ReadAllManifests() await Task.WhenAll(manifestDataTasks.Values.ToArray()).ConfigureAwait(false); - var manifestData = manifestDataTasks + var pathData = manifestDataTasks .Select(i => new { Path = i.Key, Data = i.Value.Result }) .ToList(); - var manifestModels = manifestData + foreach (var path in pathData) + { + _loadedPaths.TryAdd(path.Path, path.Data); + } + + var manifestModels = pathData .Where(i => i.Data != null) .ToDictionary(i => i.Path, i => JObject.Parse(Encoding.UTF8.GetString(i.Data!)));