From da73d519d78d6b8a123800aff9a8d40359ff74e1 Mon Sep 17 00:00:00 2001 From: Pavel Vesely Date: Mon, 11 May 2020 13:10:22 +0200 Subject: [PATCH] IClientStartup implementation --- Oqtane.Client/Program.cs | 35 +++++++++++++++---- .../Controllers/ModuleDefinitionController.cs | 10 ++++++ .../OqtaneServiceCollectionExtensions.cs | 15 ++++++-- Oqtane.Server/Startup.cs | 9 +++-- .../Extensions/AssemblyExtensions.cs | 11 ++++++ Oqtane.Shared/Interfaces/IClientStartup.cs | 11 ++++++ Oqtane.Shared/Oqtane.Shared.csproj | 1 + 7 files changed, 80 insertions(+), 12 deletions(-) rename {Oqtane.Server => Oqtane.Shared}/Extensions/AssemblyExtensions.cs (84%) create mode 100644 Oqtane.Shared/Interfaces/IClientStartup.cs diff --git a/Oqtane.Client/Program.cs b/Oqtane.Client/Program.cs index 74fa8de4..26bac719 100644 --- a/Oqtane.Client/Program.cs +++ b/Oqtane.Client/Program.cs @@ -4,8 +4,10 @@ using System.Threading.Tasks; using Oqtane.Services; using System.Reflection; using System; +using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Net.Http.Json; using Oqtane.Modules; using Oqtane.Shared; using Oqtane.Providers; @@ -19,10 +21,9 @@ namespace Oqtane.Client { var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("app"); + HttpClient httpClient = new HttpClient {BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)}; - builder.Services.AddSingleton( - new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) } - ); + builder.Services.AddSingleton(httpClient); builder.Services.AddOptions(); // register auth services @@ -57,14 +58,16 @@ namespace Oqtane.Client builder.Services.AddScoped(); builder.Services.AddScoped(); + await LoadClientAssemblies(httpClient); + // dynamically register module contexts and repository services Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach (Assembly assembly in assemblies) { - Type[] implementationtypes = assembly.GetTypes() - .Where(item => item.GetInterfaces().Contains(typeof(IService))) - .ToArray(); - foreach (Type implementationtype in implementationtypes) + var implementationTypes = assembly.GetTypes() + .Where(item => item.GetInterfaces().Contains(typeof(IService))); + + foreach (Type implementationtype in implementationTypes) { Type servicetype = Type.GetType(implementationtype.AssemblyQualifiedName.Replace(implementationtype.Name, "I" + implementationtype.Name)); if (servicetype != null) @@ -76,9 +79,27 @@ namespace Oqtane.Client builder.Services.AddScoped(implementationtype, implementationtype); // no interface defined for service } } + + assembly.GetInstances() + .ToList() + .ForEach(x => x.ConfigureServices(builder.Services)); } await builder.Build().RunAsync(); } + + private static async Task LoadClientAssemblies(HttpClient http) + { + var list = await http.GetFromJsonAsync>($"/~/api/ModuleDefinition/load"); + // get list of loaded assemblies on the client ( in the client-side hosting module the browser client has its own app domain ) + var assemblyList = AppDomain.CurrentDomain.GetAssemblies().Select(a => a.GetName().Name).ToList(); + foreach (var name in list) + { + if (assemblyList.Contains(name)) continue; + // download assembly from server and load + var bytes = await http.GetByteArrayAsync($"/~/api/ModuleDefinition/load/{name}.dll"); + Assembly.Load(bytes); + } + } } } diff --git a/Oqtane.Server/Controllers/ModuleDefinitionController.cs b/Oqtane.Server/Controllers/ModuleDefinitionController.cs index e1b9534c..fcd9dcd8 100644 --- a/Oqtane.Server/Controllers/ModuleDefinitionController.cs +++ b/Oqtane.Server/Controllers/ModuleDefinitionController.cs @@ -170,6 +170,16 @@ namespace Oqtane.Controllers return null; } } + // GET api//load/assembyname + [HttpGet("load")] + public List Load() + { + var assemblies = AppDomain.CurrentDomain.GetOqtaneClientAssemblies(); + var list = AppDomain.CurrentDomain.GetOqtaneClientAssemblies().Select(a => a.GetName().Name).ToList(); + var deps = assemblies.SelectMany(a => a.GetReferencedAssemblies()).Distinct(); + list.AddRange(deps.Where(a=>a.Name.EndsWith(".oqtane",StringComparison.OrdinalIgnoreCase)).Select(a=>a.Name)); + return list; + } // POST api/?moduleid=x [HttpPost] diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 64bffb8a..3e778869 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -8,21 +8,23 @@ using Microsoft.Extensions.Hosting; using Oqtane.Extensions; using Oqtane.Infrastructure; using Oqtane.Modules; +using Oqtane.Services; using Oqtane.Shared; +using Oqtane.UI; // ReSharper disable once CheckNamespace namespace Microsoft.Extensions.DependencyInjection { public static class OqtaneServiceCollectionExtensions { - public static IServiceCollection AddOqtaneParts(this IServiceCollection services) + public static IServiceCollection AddOqtaneParts(this IServiceCollection services, Runtime runtime) { LoadAssemblies(); - services.AddOqtaneServices(); + services.AddOqtaneServices(runtime); return services; } - private static IServiceCollection AddOqtaneServices(this IServiceCollection services) + private static IServiceCollection AddOqtaneServices(this IServiceCollection services, Runtime runtime) { if (services is null) { @@ -59,6 +61,13 @@ namespace Microsoft.Extensions.DependencyInjection { startup.ConfigureServices(services); } + + if (runtime == Runtime.Server) + { + assembly.GetInstances() + .ToList() + .ForEach(x => x.ConfigureServices(services)); + } } return services; } diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index d3ba352b..34f73ffe 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -19,7 +19,8 @@ using Oqtane.Infrastructure; using Oqtane.Repository; using Oqtane.Security; using Oqtane.Services; -using Oqtane.Shared; +using Oqtane.Shared; +using Oqtane.UI; namespace Oqtane { @@ -27,6 +28,7 @@ namespace Oqtane { public IConfigurationRoot Configuration { get; } private string _webRoot; + private Runtime _runtime; public Startup(IWebHostEnvironment env) { @@ -34,6 +36,9 @@ namespace Oqtane .SetBasePath(env.ContentRootPath) .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true); Configuration = builder.Build(); + + _runtime = (Configuration.GetSection("Runtime").Value == "WebAssembly") ? Runtime.WebAssembly : Runtime.Server; + _webRoot = env.WebRootPath; AppDomain.CurrentDomain.SetData("DataDirectory", Path.Combine(env.ContentRootPath, "Data")); } @@ -189,7 +194,7 @@ namespace Oqtane services.AddTransient(); // load the external assemblies into the app domain, install services - services.AddOqtaneParts(); + services.AddOqtaneParts(_runtime); services.AddMvc() .AddNewtonsoftJson() diff --git a/Oqtane.Server/Extensions/AssemblyExtensions.cs b/Oqtane.Shared/Extensions/AssemblyExtensions.cs similarity index 84% rename from Oqtane.Server/Extensions/AssemblyExtensions.cs rename to Oqtane.Shared/Extensions/AssemblyExtensions.cs index cc04b6da..e97de64f 100644 --- a/Oqtane.Server/Extensions/AssemblyExtensions.cs +++ b/Oqtane.Shared/Extensions/AssemblyExtensions.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; +using Oqtane.Services; using Oqtane.Shared; // ReSharper disable once CheckNamespace @@ -35,6 +36,11 @@ namespace System.Reflection .Where(x => interfaceType.IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract); } + public static IEnumerable GetTypes(this Assembly assembly) + { + return assembly.GetTypes(typeof(T)); + } + public static IEnumerable GetInstances(this Assembly assembly) where T : class { if (assembly is null) @@ -65,5 +71,10 @@ namespace System.Reflection { return appDomain.GetAssemblies().Where(a => a.IsOqtaneAssembly()); } + public static IEnumerable GetOqtaneClientAssemblies(this AppDomain appDomain) + { + return appDomain.GetOqtaneAssemblies() + .Where(a => a.GetTypes().Any()); + } } } diff --git a/Oqtane.Shared/Interfaces/IClientStartup.cs b/Oqtane.Shared/Interfaces/IClientStartup.cs new file mode 100644 index 00000000..c063431c --- /dev/null +++ b/Oqtane.Shared/Interfaces/IClientStartup.cs @@ -0,0 +1,11 @@ + +using Microsoft.Extensions.DependencyInjection; + +namespace Oqtane.Services +{ + public interface IClientStartup + { + // This method gets called by the runtime. Use this method to add services to the container. + void ConfigureServices(IServiceCollection services); + } +} diff --git a/Oqtane.Shared/Oqtane.Shared.csproj b/Oqtane.Shared/Oqtane.Shared.csproj index c50111cd..eca4e4ee 100644 --- a/Oqtane.Shared/Oqtane.Shared.csproj +++ b/Oqtane.Shared/Oqtane.Shared.csproj @@ -18,6 +18,7 @@ +