Implemented plugin interface loading via di

1^
This commit is contained in:
2025-02-24 17:20:16 +01:00
parent 4a571e1944
commit 69df761bf4
6 changed files with 45 additions and 99 deletions

View File

@@ -1,7 +0,0 @@
namespace Moonlight.Client.Interfaces;
public interface IAppLoader
{
public int Priority { get; }
public Task Load(IServiceProvider serviceProvider);
}

View File

@@ -1,10 +0,0 @@
using Microsoft.AspNetCore.Components;
namespace Moonlight.Client.Interfaces;
public interface IAppScreen
{
public int Priority { get; }
public Task<bool> ShouldRender(IServiceProvider serviceProvider);
public RenderFragment Render();
}

View File

@@ -1,9 +0,0 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
namespace Moonlight.Client.Interfaces;
public interface IAppStartup
{
public Task BuildApp(WebAssemblyHostBuilder builder);
public Task ConfigureApp(WebAssemblyHost app);
}

View File

@@ -0,0 +1,9 @@
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
namespace Moonlight.Client.Interfaces;
public interface IPluginStartup
{
public Task BuildApplication(WebAssemblyHostBuilder builder);
public Task ConfigureApplication(WebAssemblyHost app);
}

View File

@@ -38,7 +38,7 @@ public class Startup
private PluginLoaderService PluginLoaderService;
private ApplicationAssemblyService ApplicationAssemblyService;
private IAppStartup[] PluginAppStartups;
private IPluginStartup[] PluginStartups;
public async Task Run(string[] args, Assembly[]? assemblies = null)
{
@@ -182,8 +182,6 @@ public class Startup
configuration.AddAssemblies(ApplicationAssemblyService.AdditionalAssemblies);
configuration.AddAssemblies(ApplicationAssemblyService.PluginAssemblies);
configuration.AddInterface<IAppLoader>();
configuration.AddInterface<IAppScreen>();
configuration.AddInterface<ISidebarItemProvider>();
});
@@ -226,25 +224,42 @@ public class Startup
private Task InitializePlugins()
{
var initialisationServiceCollection = new ServiceCollection();
// Define minimal service collection
var startupSc = new ServiceCollection();
initialisationServiceCollection.AddLogging(builder => { builder.AddProviders(LoggerProviders); });
// Configure plugin loading by using the interface service
initialisationServiceCollection.AddInterfaces(configuration =>
// Create logging proxy
startupSc.AddLogging(builder =>
{
// We use moonlight itself as a plugin assembly
configuration.AddAssembly(typeof(Startup).Assembly);
configuration.AddAssemblies(ApplicationAssemblyService.AdditionalAssemblies);
configuration.AddAssemblies(ApplicationAssemblyService.PluginAssemblies);
configuration.AddInterface<IAppStartup>();
builder.ClearProviders();
builder.AddProviders(LoggerProviders);
});
var initialisationServiceProvider = initialisationServiceCollection.BuildServiceProvider();
//
var startupSp = startupSc.BuildServiceProvider();
// Initialize plugin startups
var startups = new List<IPluginStartup>();
var startupType = typeof(IPluginStartup);
PluginAppStartups = initialisationServiceProvider.GetRequiredService<IAppStartup[]>();
foreach (var pluginAssembly in ApplicationAssemblyService.PluginAssemblies)
{
var startupTypes = pluginAssembly
.ExportedTypes
.Where(x => x.IsAbstract && x.IsInterface && x.IsAssignableTo(startupType))
.ToArray();
foreach (var type in startupTypes)
{
var startup = ActivatorUtilities.CreateInstance(startupSp, type) as IPluginStartup;
if(startup == null)
continue;
startups.Add(startup);
}
}
PluginStartups = startups.ToArray();
return Task.CompletedTask;
}
@@ -253,11 +268,11 @@ public class Startup
private async Task HookPluginBuild()
{
foreach (var pluginAppStartup in PluginAppStartups)
foreach (var pluginAppStartup in PluginStartups)
{
try
{
await pluginAppStartup.BuildApp(WebAssemblyHostBuilder);
await pluginAppStartup.BuildApplication(WebAssemblyHostBuilder);
}
catch (Exception e)
{
@@ -272,11 +287,11 @@ public class Startup
private async Task HookPluginConfigure()
{
foreach (var pluginAppStartup in PluginAppStartups)
foreach (var pluginAppStartup in PluginStartups)
{
try
{
await pluginAppStartup.ConfigureApp(WebAssemblyHost);
await pluginAppStartup.ConfigureApplication(WebAssemblyHost);
}
catch (Exception e)
{

View File

@@ -9,8 +9,6 @@
@inject IServiceProvider ServiceProvider
@inject ILogger<MainLayout> Logger
@inject IAppLoader[] AppLoaders
@inject IAppScreen[] AppScreens
@inject FrontendConfiguration Configuration
<PageTitle>@Configuration.Title</PageTitle>
@@ -83,58 +81,8 @@ else
IsLoading = true;
await InvokeAsync(StateHasChanged);
//
await RunLoaders();
// Screens
await RenderScreens();
//
IsLoading = false;
await InvokeAsync(StateHasChanged);
}
private async Task RunLoaders()
{
var appLoaders = AppLoaders
.OrderBy(x => x.Priority);
foreach (var loader in appLoaders) //TODO: Measure performance of every loader?
{
try
{
Logger.LogDebug("Running application loader '{name}'", loader.GetType().Name);
await loader.Load(ServiceProvider);
}
catch (HttpApiException)
{
throw;
}
catch (Exception e)
{
Logger.LogCritical("An app loader threw an unhandled exception: {e}", e);
}
}
}
public async Task RenderScreens()
{
CurrentScreen = null;
var appScreens = AppScreens
.OrderBy(x => x.Priority);
foreach (var screen in appScreens)
{
if (!await screen.ShouldRender(ServiceProvider))
continue;
CurrentScreen = screen.Render();
await InvokeAsync(StateHasChanged);
return;
}
await InvokeAsync(StateHasChanged);
}
}