From 35c55750bbf0ce459ba11c06005b7dd4f28a4f00 Mon Sep 17 00:00:00 2001 From: Hisham Bin Ateya Date: Fri, 3 Jan 2020 20:01:08 +0300 Subject: [PATCH 1/4] Add ServiceCollection extensions for Oqtane --- .../OqtaneServiceCollectionExtensions.cs | 110 +++++++++++++ Oqtane.Server/Modules/MVCModuleExtension.cs | 8 +- Oqtane.Server/Startup.cs | 147 ++---------------- 3 files changed, 127 insertions(+), 138 deletions(-) create mode 100644 Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs new file mode 100644 index 00000000..1cc50c8b --- /dev/null +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using Microsoft.Extensions.Hosting; +using Oqtane.Infrastructure; +using Oqtane.Modules; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class OqtaneServiceCollectionExtensions + { + private static readonly IList _oqtaneModuleAssemblies = new List(); + + private static Assembly[] Assemblies => AppDomain.CurrentDomain.GetAssemblies(); + + internal static IEnumerable GetOqtaneModuleAssemblies() => _oqtaneModuleAssemblies; + + public static IServiceCollection AddOqtaneModules(this IServiceCollection services) + { + var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + var assembliesFolder = new DirectoryInfo(assemblyPath); + + // iterate through Oqtane module assemblies in /bin ( filter is narrow to optimize loading process ) + foreach (var file in assembliesFolder.EnumerateFiles("*.Module.*.dll")) + { + // check if assembly is already loaded + var assembly = Assemblies.Where(a => a.Location == file.FullName).FirstOrDefault(); + if (assembly == null) + { + // load assembly from stream to prevent locking file ( as long as dependencies are in /bin they will load as well ) + assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(file.FullName))); + _oqtaneModuleAssemblies.Add(assembly); + } + } + + return services; + } + + public static IServiceCollection AddOqtaneThemes(this IServiceCollection services) + { + var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + var assembliesFolder = new DirectoryInfo(assemblyPath); + + // iterate through Oqtane theme assemblies in /bin ( filter is narrow to optimize loading process ) + foreach (var file in assembliesFolder.EnumerateFiles("*.Theme.*.dll")) + { + // check if assembly is already loaded + var assembly = Assemblies.Where(a => a.Location == file.FullName).FirstOrDefault(); + if (assembly == null) + { + // load assembly from stream to prevent locking file ( as long as dependencies are in /bin they will load as well ) + assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(file.FullName))); + _oqtaneModuleAssemblies.Add(assembly); + } + } + + return services; + } + + public static IServiceCollection AddOqtaneServices(this IServiceCollection services) + { + // dynamically register module services, contexts, and repository classes + var assemblies = Assemblies. + Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module.")).ToArray(); + foreach (var assembly in assemblies) + { + var implementationTypes = assembly.GetTypes() + .Where(t => t.GetInterfaces().Contains(typeof(IService))) + .ToArray(); + foreach (var implementationType in implementationTypes) + { + var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, "I" + implementationType.Name)); + if (serviceType != null) + { + services.AddScoped(serviceType, implementationType); // traditional service interface + } + else + { + services.AddScoped(implementationType, implementationType); // no interface defined for service + } + } + } + + return services; + } + + public static IServiceCollection AddOqtaneHostedServices(this IServiceCollection services) + { + // dynamically register hosted services + foreach (var assembly in Assemblies) + { + var serviceTypes = assembly.GetTypes() + .Where(t => t.GetInterfaces().Contains(typeof(IHostedService))) + .ToArray(); + foreach (var serviceType in serviceTypes) + { + if (serviceType.Name != nameof(HostedServiceBase)) + { + services.AddSingleton(typeof(IHostedService), serviceType); + } + } + } + + return services; + } + } +} diff --git a/Oqtane.Server/Modules/MVCModuleExtension.cs b/Oqtane.Server/Modules/MVCModuleExtension.cs index fdbaec4a..bab0f869 100644 --- a/Oqtane.Server/Modules/MVCModuleExtension.cs +++ b/Oqtane.Server/Modules/MVCModuleExtension.cs @@ -1,6 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; +using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationParts; @@ -9,10 +7,10 @@ namespace Microsoft.Extensions.DependencyInjection { public static class MvcModuleExtensions { - public static IMvcBuilder AddModuleAssemblies(this IMvcBuilder mvcBuilder, List assemblies) + public static IMvcBuilder AddOqtaneApplicationParts(this IMvcBuilder mvcBuilder) { // load MVC application parts from module assemblies - foreach (Assembly assembly in assemblies) + foreach (Assembly assembly in OqtaneServiceCollectionExtensions.GetOqtaneModuleAssemblies()) { // check if assembly contains MVC Controllers if (assembly.GetTypes().Where(item => item.IsSubclassOf(typeof(Controller))).ToArray().Length > 0) diff --git a/Oqtane.Server/Startup.cs b/Oqtane.Server/Startup.cs index bea44bd2..9739eade 100644 --- a/Oqtane.Server/Startup.cs +++ b/Oqtane.Server/Startup.cs @@ -180,75 +180,15 @@ namespace Oqtane.Server services.AddTransient(); services.AddTransient(); - // get list of loaded assemblies - Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); - string path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - DirectoryInfo folder = new DirectoryInfo(path); - List moduleassemblies = new List(); + services.AddOqtaneModules(); + services.AddOqtaneThemes(); - // iterate through Oqtane module assemblies in /bin ( filter is narrow to optimize loading process ) - foreach (FileInfo file in folder.EnumerateFiles("*.Module.*.dll")) - { - // check if assembly is already loaded - Assembly assembly = assemblies.Where(item => item.Location == file.FullName).FirstOrDefault(); - if (assembly == null) - { - // load assembly from stream to prevent locking file ( as long as dependencies are in /bin they will load as well ) - assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(file.FullName))); - moduleassemblies.Add(assembly); - } - } + services.AddMvc() + .AddOqtaneApplicationParts() + .AddNewtonsoftJson(); - // iterate through Oqtane theme assemblies in /bin ( filter is narrow to optimize loading process ) - foreach (FileInfo file in folder.EnumerateFiles("*.Theme.*.dll")) - { - // check if assembly is already loaded - Assembly assembly = assemblies.Where(item => item.Location == file.FullName).FirstOrDefault(); - if (assembly == null) - { - // load assembly from stream to prevent locking file ( as long as dependencies are in /bin they will load as well ) - assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(file.FullName))); - } - } - - services.AddMvc().AddModuleAssemblies(moduleassemblies).AddNewtonsoftJson(); - - // dynamically register module services, contexts, and repository classes - assemblies = AppDomain.CurrentDomain.GetAssemblies() - .Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module.")).ToArray(); - foreach (Assembly assembly in assemblies) - { - Type[] implementationtypes = assembly.GetTypes() - .Where(item => item.GetInterfaces().Contains(typeof(IService))) - .ToArray(); - foreach (Type implementationtype in implementationtypes) - { - Type servicetype = Type.GetType(implementationtype.AssemblyQualifiedName.Replace(implementationtype.Name, "I" + implementationtype.Name)); - if (servicetype != null) - { - services.AddScoped(servicetype, implementationtype); // traditional service interface - } - else - { - services.AddScoped(implementationtype, implementationtype); // no interface defined for service - } - } - } - - // dynamically register hosted services - foreach (Assembly assembly in assemblies) - { - Type[] servicetypes = assembly.GetTypes() - .Where(item => item.GetInterfaces().Contains(typeof(IHostedService))) - .ToArray(); - foreach (Type servicetype in servicetypes) - { - if (servicetype.Name != "HostedServiceBase") - { - services.AddSingleton(typeof(IHostedService), servicetype); - } - } - } + services.AddOqtaneServices(); + services.AddOqtaneHostedServices(); services.AddSwaggerGen(c => { @@ -387,74 +327,15 @@ namespace Oqtane.Server services.AddTransient(); services.AddTransient(); - // get list of loaded assemblies - Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); - string path = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - DirectoryInfo folder = new DirectoryInfo(path); - List moduleassemblies = new List(); + services.AddOqtaneModules(); + services.AddOqtaneThemes(); - // iterate through Oqtane module assemblies in /bin ( filter is narrow to optimize loading process ) - foreach (FileInfo file in folder.EnumerateFiles("*.Module.*.dll")) - { - // check if assembly is already loaded - Assembly assembly = assemblies.Where(item => item.Location == file.FullName).FirstOrDefault(); - if (assembly == null) - { - // load assembly from stream to prevent locking file ( as long as dependencies are in /bin they will load as well ) - assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(file.FullName))); - } - } + services.AddMvc() + .AddOqtaneApplicationParts() + .AddNewtonsoftJson(); - // iterate through Oqtane theme assemblies in /bin ( filter is narrow to optimize loading process ) - foreach (FileInfo file in folder.EnumerateFiles("*.Theme.*.dll")) - { - // check if assembly is already loaded - Assembly assembly = assemblies.Where(item => item.Location == file.FullName).FirstOrDefault(); - if (assembly == null) - { - // load assembly from stream to prevent locking file ( as long as dependencies are in /bin they will load as well ) - assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(file.FullName))); - } - } - - services.AddMvc().AddModuleAssemblies(moduleassemblies).AddNewtonsoftJson(); - - // dynamically register module services, contexts, and repository classes - assemblies = AppDomain.CurrentDomain.GetAssemblies() - .Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module.")).ToArray(); - foreach (Assembly assembly in assemblies) - { - Type[] implementationtypes = assembly.GetTypes() - .Where(item => item.GetInterfaces().Contains(typeof(IService))) - .ToArray(); - foreach (Type implementationtype in implementationtypes) - { - Type servicetype = Type.GetType(implementationtype.AssemblyQualifiedName.Replace(implementationtype.Name, "I" + implementationtype.Name)); - if (servicetype != null) - { - services.AddScoped(servicetype, implementationtype); // traditional service interface - } - else - { - services.AddScoped(implementationtype, implementationtype); // no interface defined for service - } - } - } - - // dynamically register hosted services - foreach (Assembly assembly in assemblies) - { - Type[] servicetypes = assembly.GetTypes() - .Where(item => item.GetInterfaces().Contains(typeof(IHostedService))) - .ToArray(); - foreach (Type servicetype in servicetypes) - { - if (servicetype.Name != "HostedServiceBase") - { - services.AddSingleton(typeof(IHostedService), servicetype); - } - } - } + services.AddOqtaneServices(); + services.AddOqtaneHostedServices(); services.AddSwaggerGen(c => { From cbe33c560f29c23b36532ae1a5ea82d18b38e634 Mon Sep 17 00:00:00 2001 From: Hisham Bin Ateya Date: Fri, 3 Jan 2020 20:04:56 +0300 Subject: [PATCH 2/4] Move MvcBuilderExtensions to Extensions folder --- .../OqtaneMvcBuilderExtensions.cs} | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) rename Oqtane.Server/{Modules/MVCModuleExtension.cs => Extensions/OqtaneMvcBuilderExtensions.cs} (73%) diff --git a/Oqtane.Server/Modules/MVCModuleExtension.cs b/Oqtane.Server/Extensions/OqtaneMvcBuilderExtensions.cs similarity index 73% rename from Oqtane.Server/Modules/MVCModuleExtension.cs rename to Oqtane.Server/Extensions/OqtaneMvcBuilderExtensions.cs index bab0f869..948e1120 100644 --- a/Oqtane.Server/Modules/MVCModuleExtension.cs +++ b/Oqtane.Server/Extensions/OqtaneMvcBuilderExtensions.cs @@ -1,19 +1,18 @@ using System.Linq; -using System.Reflection; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationParts; namespace Microsoft.Extensions.DependencyInjection { - public static class MvcModuleExtensions + public static class OqtaneMvcBuilderExtensions { public static IMvcBuilder AddOqtaneApplicationParts(this IMvcBuilder mvcBuilder) { // load MVC application parts from module assemblies - foreach (Assembly assembly in OqtaneServiceCollectionExtensions.GetOqtaneModuleAssemblies()) + foreach (var assembly in OqtaneServiceCollectionExtensions.GetOqtaneModuleAssemblies()) { // check if assembly contains MVC Controllers - if (assembly.GetTypes().Where(item => item.IsSubclassOf(typeof(Controller))).ToArray().Length > 0) + if (assembly.GetTypes().Where(t => t.IsSubclassOf(typeof(Controller))).ToArray().Length > 0) { var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly); foreach (var part in partFactory.GetApplicationParts(assembly)) From 2fdc01644e2886eaea6a5b1753b45fa643da7fe8 Mon Sep 17 00:00:00 2001 From: Hisham Bin Ateya Date: Fri, 3 Jan 2020 20:34:33 +0300 Subject: [PATCH 3/4] Refactoring --- .../Extensions/AssemblyExtensions.cs | 35 +++++++++ .../OqtaneServiceCollectionExtensions.cs | 73 +++++++------------ 2 files changed, 62 insertions(+), 46 deletions(-) create mode 100644 Oqtane.Server/Extensions/AssemblyExtensions.cs diff --git a/Oqtane.Server/Extensions/AssemblyExtensions.cs b/Oqtane.Server/Extensions/AssemblyExtensions.cs new file mode 100644 index 00000000..7c3bb924 --- /dev/null +++ b/Oqtane.Server/Extensions/AssemblyExtensions.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace System.Reflection +{ + public static class AssemblyExtensions + { + public static IEnumerable GetInterfaces(this Assembly assembly) + { + if (assembly is null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + return assembly.GetTypes(typeof(TInterfaceType)); + } + + public static IEnumerable GetTypes(this Assembly assembly, Type interfaceType) + { + if (assembly is null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + if (interfaceType is null) + { + throw new ArgumentNullException(nameof(interfaceType)); + } + + return assembly.GetTypes() + .Where(t => t.GetInterfaces().Contains(interfaceType)); + } + } +} diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index 1cc50c8b..bbde6cde 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -20,42 +20,14 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection AddOqtaneModules(this IServiceCollection services) { - var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - var assembliesFolder = new DirectoryInfo(assemblyPath); - - // iterate through Oqtane module assemblies in /bin ( filter is narrow to optimize loading process ) - foreach (var file in assembliesFolder.EnumerateFiles("*.Module.*.dll")) - { - // check if assembly is already loaded - var assembly = Assemblies.Where(a => a.Location == file.FullName).FirstOrDefault(); - if (assembly == null) - { - // load assembly from stream to prevent locking file ( as long as dependencies are in /bin they will load as well ) - assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(file.FullName))); - _oqtaneModuleAssemblies.Add(assembly); - } - } + LoadAssemblies("Module"); return services; } public static IServiceCollection AddOqtaneThemes(this IServiceCollection services) { - var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); - var assembliesFolder = new DirectoryInfo(assemblyPath); - - // iterate through Oqtane theme assemblies in /bin ( filter is narrow to optimize loading process ) - foreach (var file in assembliesFolder.EnumerateFiles("*.Theme.*.dll")) - { - // check if assembly is already loaded - var assembly = Assemblies.Where(a => a.Location == file.FullName).FirstOrDefault(); - if (assembly == null) - { - // load assembly from stream to prevent locking file ( as long as dependencies are in /bin they will load as well ) - assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(file.FullName))); - _oqtaneModuleAssemblies.Add(assembly); - } - } + LoadAssemblies("Theme"); return services; } @@ -67,20 +39,11 @@ namespace Microsoft.Extensions.DependencyInjection Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module.")).ToArray(); foreach (var assembly in assemblies) { - var implementationTypes = assembly.GetTypes() - .Where(t => t.GetInterfaces().Contains(typeof(IService))) - .ToArray(); + var implementationTypes = assembly.GetInterfaces(); foreach (var implementationType in implementationTypes) { - var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, "I" + implementationType.Name)); - if (serviceType != null) - { - services.AddScoped(serviceType, implementationType); // traditional service interface - } - else - { - services.AddScoped(implementationType, implementationType); // no interface defined for service - } + var serviceType = Type.GetType(implementationType.AssemblyQualifiedName.Replace(implementationType.Name, $"I{implementationType.Name}")); + services.AddScoped(serviceType ?? implementationType, implementationType); } } @@ -90,21 +53,39 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection AddOqtaneHostedServices(this IServiceCollection services) { // dynamically register hosted services + var hostedServiceType = typeof(IHostedService); foreach (var assembly in Assemblies) { - var serviceTypes = assembly.GetTypes() - .Where(t => t.GetInterfaces().Contains(typeof(IHostedService))) - .ToArray(); + var serviceTypes = assembly.GetTypes(hostedServiceType); foreach (var serviceType in serviceTypes) { if (serviceType.Name != nameof(HostedServiceBase)) { - services.AddSingleton(typeof(IHostedService), serviceType); + services.AddSingleton(hostedServiceType, serviceType); } } } return services; } + + private static void LoadAssemblies(string pattern) + { + var assemblyPath = Path.GetDirectoryName(Assembly.GetEntryAssembly().Location); + var assembliesFolder = new DirectoryInfo(assemblyPath); + + // iterate through Oqtane theme assemblies in /bin ( filter is narrow to optimize loading process ) + foreach (var file in assembliesFolder.EnumerateFiles($"*.{pattern}.*.dll")) + { + // check if assembly is already loaded + var assembly = Assemblies.Where(a => a.Location == file.FullName).FirstOrDefault(); + if (assembly == null) + { + // load assembly from stream to prevent locking file ( as long as dependencies are in /bin they will load as well ) + assembly = AssemblyLoadContext.Default.LoadFromStream(new MemoryStream(File.ReadAllBytes(file.FullName))); + _oqtaneModuleAssemblies.Add(assembly); + } + } + } } } From 675b2a911099890f74353d5584abea1541b048ac Mon Sep 17 00:00:00 2001 From: Hisham Bin Ateya Date: Fri, 3 Jan 2020 20:35:55 +0300 Subject: [PATCH 4/4] Add null checks --- .../Extensions/OqtaneMvcBuilderExtensions.cs | 8 +++++++- .../OqtaneServiceCollectionExtensions.cs | 20 +++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/Oqtane.Server/Extensions/OqtaneMvcBuilderExtensions.cs b/Oqtane.Server/Extensions/OqtaneMvcBuilderExtensions.cs index 948e1120..61a3103c 100644 --- a/Oqtane.Server/Extensions/OqtaneMvcBuilderExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneMvcBuilderExtensions.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationParts; @@ -8,6 +9,11 @@ namespace Microsoft.Extensions.DependencyInjection { public static IMvcBuilder AddOqtaneApplicationParts(this IMvcBuilder mvcBuilder) { + if (mvcBuilder is null) + { + throw new ArgumentNullException(nameof(mvcBuilder)); + } + // load MVC application parts from module assemblies foreach (var assembly in OqtaneServiceCollectionExtensions.GetOqtaneModuleAssemblies()) { diff --git a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs index bbde6cde..2eec6534 100644 --- a/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs +++ b/Oqtane.Server/Extensions/OqtaneServiceCollectionExtensions.cs @@ -20,6 +20,11 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection AddOqtaneModules(this IServiceCollection services) { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + LoadAssemblies("Module"); return services; @@ -27,6 +32,11 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection AddOqtaneThemes(this IServiceCollection services) { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + LoadAssemblies("Theme"); return services; @@ -34,6 +44,11 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection AddOqtaneServices(this IServiceCollection services) { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + // dynamically register module services, contexts, and repository classes var assemblies = Assemblies. Where(item => item.FullName.StartsWith("Oqtane.") || item.FullName.Contains(".Module.")).ToArray(); @@ -52,6 +67,11 @@ namespace Microsoft.Extensions.DependencyInjection public static IServiceCollection AddOqtaneHostedServices(this IServiceCollection services) { + if (services is null) + { + throw new ArgumentNullException(nameof(services)); + } + // dynamically register hosted services var hostedServiceType = typeof(IHostedService); foreach (var assembly in Assemblies)