Browse Source

Load plugins modules.

pull/81/head
Halil İbrahim Kalkan 9 years ago
parent
commit
aa6c0461f6
  1. 38
      src/Volo.Abp/Volo/Abp/AbpApplication.cs
  2. 14
      src/Volo.Abp/Volo/Abp/AbpApplicationOptions.cs
  3. 23
      src/Volo.Abp/Volo/Abp/Modularity/AbpModule.cs
  4. 8
      src/Volo.Abp/Volo/Abp/Modularity/AbpModuleDescriptor.cs
  5. 58
      src/Volo.Abp/Volo/Abp/Modularity/AbpModuleFinder.cs
  6. 3
      src/Volo.Abp/Volo/Abp/Modularity/IModuleLoader.cs
  7. 92
      src/Volo.Abp/Volo/Abp/Modularity/ModuleLoader.cs
  8. 47
      src/Volo.Abp/Volo/Abp/Modularity/PlugIns/FolderPlugInSource.cs
  9. 10
      src/Volo.Abp/Volo/Abp/Modularity/PlugIns/IPlugInSource.cs
  10. 18
      src/Volo.Abp/Volo/Abp/Modularity/PlugIns/PlugInSourceExtensions.cs
  11. 17
      src/Volo.Abp/Volo/Abp/Modularity/PlugIns/PlugInSourceList.cs
  12. 18
      src/Volo.Abp/Volo/Abp/Modularity/PlugIns/PlugInSourceListExtensions.cs
  13. 21
      src/Volo.Abp/Volo/Abp/Modularity/PlugIns/PlugInTypeListSource.cs
  14. 22
      src/Volo.Abp/Volo/Abp/Reflection/AssemblyHelper.cs
  15. 3
      src/Volo.Abp/project.json
  16. 29
      test/Volo.Abp.Tests/AbpApplication_Tests.cs
  17. 56
      test/Volo.Abp.Tests/Volo/Abp/AbpApplication_Initialize_Tests.cs
  18. 21
      test/Volo.Abp.Tests/Volo/Abp/Modularity/IndependentEmptyPlugInModule.cs
  19. 3
      test/Volo.Abp.Tests/Volo/Abp/Modularity/ModuleLoader_Tests.cs

38
src/Volo.Abp/Volo/Abp/AbpApplication.cs

@ -1,4 +1,5 @@
using System;
using JetBrains.Annotations;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
@ -10,20 +11,32 @@ namespace Volo.Abp
public IServiceProvider ServiceProvider { get; private set; }
private AbpApplication(Type startupModuleType, IServiceCollection services)
private AbpApplication(Type startupModuleType, IServiceCollection services, Action<AbpApplicationOptions> optionsAction)
{
StartupModuleType = startupModuleType;
services.AddSingleton(this);
services.AddCoreAbpServices();
services.GetSingletonInstance<IModuleLoader>().LoadAll(services, StartupModuleType);
var options = new AbpApplicationOptions();
optionsAction?.Invoke(options);
AddServices(services);
LoadModules(services, options);
}
public static AbpApplication Create<TStartupModule>(IServiceCollection services)
public static AbpApplication Create<TStartupModule>([NotNull] IServiceCollection services)
where TStartupModule : IAbpModule
{
return new AbpApplication(typeof(TStartupModule), services);
return Create<TStartupModule>(services, null);
}
public static AbpApplication Create<TStartupModule>(
[NotNull] IServiceCollection services,
[CanBeNull] Action<AbpApplicationOptions> optionsAction)
where TStartupModule : IAbpModule
{
Check.NotNull(services, nameof(services));
return new AbpApplication(typeof(TStartupModule), services, optionsAction);
}
public void Initialize(IServiceProvider serviceProvider)
@ -32,6 +45,17 @@ namespace Volo.Abp
ServiceProvider.GetRequiredService<IModuleManager>().Initialize();
}
private void AddServices(IServiceCollection services)
{
services.AddSingleton(this);
services.AddCoreAbpServices();
}
private void LoadModules(IServiceCollection services, AbpApplicationOptions options)
{
services.GetSingletonInstance<IModuleLoader>().LoadAll(services, StartupModuleType, options.PlugInSources);
}
public void Dispose()
{

14
src/Volo.Abp/Volo/Abp/AbpApplicationOptions.cs

@ -0,0 +1,14 @@
using Volo.Abp.Modularity.PlugIns;
namespace Volo.Abp
{
public class AbpApplicationOptions
{
public PlugInSourceList PlugInSources { get; set; }
public AbpApplicationOptions()
{
PlugInSources = new PlugInSourceList();
}
}
}

23
src/Volo.Abp/Volo/Abp/Modularity/AbpModule.cs

@ -1,4 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.Modularity
{
@ -15,5 +17,24 @@ namespace Volo.Abp.Modularity
{
}
public static bool IsAbpModule(Type type)
{
var typeInfo = type.GetTypeInfo();
return
typeInfo.IsClass &&
!typeInfo.IsAbstract &&
!typeInfo.IsGenericType &&
typeof(IAbpModule).GetTypeInfo().IsAssignableFrom(type);
}
public static void CheckAbpModuleType(Type moduleType)
{
if (!IsAbpModule(moduleType))
{
throw new ArgumentException("Given type is not an ABP module: " + moduleType.AssemblyQualifiedName);
}
}
}
}

8
src/Volo.Abp/Volo/Abp/Modularity/AbpModuleDescriptor.cs

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using JetBrains.Annotations;
namespace Volo.Abp.Modularity
@ -10,13 +11,18 @@ namespace Volo.Abp.Modularity
public IAbpModule Instance { get; }
public List<AbpModuleDescriptor> Dependencies { get; }
internal List<AbpModuleDescriptor> Dependencies { get; }
public AbpModuleDescriptor([NotNull] Type type, [NotNull] IAbpModule instance)
{
Check.NotNull(type, nameof(type));
Check.NotNull(instance, nameof(instance));
if (!type.GetTypeInfo().IsAssignableFrom(instance.GetType()))
{
throw new ArgumentException($"Given module instance ({instance.GetType().AssemblyQualifiedName}) is not an instance of given module type: {type.AssemblyQualifiedName}");
}
Type = type;
Instance = instance;

58
src/Volo.Abp/Volo/Abp/Modularity/AbpModuleFinder.cs

@ -0,0 +1,58 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Volo.ExtensionMethods.Collections.Generic;
namespace Volo.Abp.Modularity
{
public static class AbpModuleFinder
{
public static List<Type> FindAllModuleTypes(Type startupModuleType)
{
var moduleTypes = new List<Type>();
AddModuleAndDependenciesResursively(moduleTypes, startupModuleType);
moduleTypes.AddIfNotContains(typeof(AbpKernelModule));
return moduleTypes;
}
public static List<Type> FindDependedModuleTypes(Type moduleType)
{
AbpModule.CheckAbpModuleType(moduleType);
var dependencies = new List<Type>();
var dependencyDescriptors = moduleType
.GetTypeInfo()
.GetCustomAttributes()
.OfType<IDependedModuleTypesProvider>();
foreach (var descriptor in dependencyDescriptors)
{
foreach (var dependedModuleType in descriptor.GetDependedModuleTypes())
{
dependencies.AddIfNotContains(dependedModuleType);
}
}
return dependencies;
}
private static void AddModuleAndDependenciesResursively(List<Type> moduleTypes, Type moduleType)
{
AbpModule.CheckAbpModuleType(moduleType);
if (moduleTypes.Contains(moduleType))
{
return;
}
moduleTypes.Add(moduleType);
foreach (var dependedModuleType in FindDependedModuleTypes(moduleType))
{
AddModuleAndDependenciesResursively(moduleTypes, dependedModuleType);
}
}
}
}

3
src/Volo.Abp/Volo/Abp/Modularity/IModuleLoader.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity.PlugIns;
namespace Volo.Abp.Modularity
{
@ -8,6 +9,6 @@ namespace Volo.Abp.Modularity
{
IReadOnlyList<AbpModuleDescriptor> Modules { get; }
void LoadAll(IServiceCollection services, Type startupModuleType);
void LoadAll(IServiceCollection services, Type startupModuleType, PlugInSourceList plugInSources);
}
}

92
src/Volo.Abp/Volo/Abp/Modularity/ModuleLoader.cs

@ -2,8 +2,8 @@
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity.PlugIns;
using Volo.ExtensionMethods.Collections.Generic;
namespace Volo.Abp.Modularity
@ -18,22 +18,32 @@ namespace Volo.Abp.Modularity
_modules = new List<AbpModuleDescriptor>();
}
public virtual void LoadAll(IServiceCollection services, Type startupModuleType)
public virtual void LoadAll(IServiceCollection services, Type startupModuleType, PlugInSourceList plugInSources)
{
if (_modules.Any())
{
throw new InvalidOperationException($"{nameof(LoadAll)} should be called only once!");
}
FillModules(services, startupModuleType);
FillModules(services, startupModuleType, plugInSources);
SetModuleDependencies();
SortByDependency(startupModuleType);
ConfigureServices(services);
}
private void FillModules(IServiceCollection services, Type startupModuleType)
private void FillModules(IServiceCollection services, Type startupModuleType, PlugInSourceList plugInSources)
{
foreach (var moduleType in FindAllModuleTypes(startupModuleType).Distinct())
//All modules starting from the startup module
var moduleTypes = AbpModuleFinder.FindAllModuleTypes(startupModuleType);
//Add plugin modules
foreach (var moduleType in plugInSources.GetAllModules())
{
moduleTypes.AddIfNotContains(moduleType);
}
//Create all modules
foreach (var moduleType in moduleTypes)
{
_modules.Add(CreateModuleDescriptor(services, moduleType));
}
@ -54,20 +64,12 @@ namespace Volo.Abp.Modularity
_modules.MoveItem(m => m.Type == startupModuleType, _modules.Count - 1);
}
protected virtual IEnumerable<Type> FindAllModuleTypes(Type startupModuleType)
{
var moduleTypes = new List<Type>();
AddModuleAndDependenciesResursively(moduleTypes, startupModuleType);
moduleTypes.AddIfNotContains(typeof(AbpKernelModule));
return moduleTypes;
}
protected virtual AbpModuleDescriptor CreateModuleDescriptor(IServiceCollection services, Type moduleType)
{
return new AbpModuleDescriptor(moduleType, CreateAndRegisterModule(services, moduleType));
}
private IAbpModule CreateAndRegisterModule(IServiceCollection services, Type moduleType)
protected virtual IAbpModule CreateAndRegisterModule(IServiceCollection services, Type moduleType)
{
var module = (IAbpModule) Activator.CreateInstance(moduleType);
services.AddSingleton(moduleType, module);
@ -81,49 +83,10 @@ namespace Volo.Abp.Modularity
module.Instance.ConfigureServices(services);
}
}
protected virtual void AddModuleAndDependenciesResursively(List<Type> moduleTypes, Type moduleType)
{
CheckAbpModuleType(moduleType);
if (moduleTypes.Contains(moduleType))
{
return;
}
moduleTypes.Add(moduleType);
foreach (var dependedModuleType in FindDependedModuleTypes(moduleType))
{
AddModuleAndDependenciesResursively(moduleTypes, dependedModuleType);
}
}
protected virtual List<Type> FindDependedModuleTypes(Type moduleType)
{
CheckAbpModuleType(moduleType);
var dependencies = new List<Type>();
var dependencyDescriptors = moduleType
.GetTypeInfo()
.GetCustomAttributes()
.OfType<IDependedModuleTypesProvider>();
foreach (var descriptor in dependencyDescriptors)
{
foreach (var dependedModuleType in descriptor.GetDependedModuleTypes())
{
dependencies.AddIfNotContains(dependedModuleType);
}
}
return dependencies;
}
protected virtual void SetModuleDependencies(AbpModuleDescriptor module)
{
foreach (var dependedModuleType in FindDependedModuleTypes(module.Type))
foreach (var dependedModuleType in AbpModuleFinder.FindDependedModuleTypes(module.Type))
{
var dependedModule = _modules.FirstOrDefault(m => m.Type == dependedModuleType);
if (dependedModule == null)
@ -134,24 +97,5 @@ namespace Volo.Abp.Modularity
module.Dependencies.AddIfNotContains(dependedModule);
}
}
protected static void CheckAbpModuleType(Type moduleType)
{
if (!IsAbpModule(moduleType))
{
throw new ArgumentException("Given type is not an ABP module: " + moduleType.AssemblyQualifiedName);
}
}
protected static bool IsAbpModule(Type type)
{
var typeInfo = type.GetTypeInfo();
return
typeInfo.IsClass &&
!typeInfo.IsAbstract &&
!typeInfo.IsGenericType &&
typeof(IAbpModule).GetTypeInfo().IsAssignableFrom(type);
}
}
}

47
src/Volo.Abp/Volo/Abp/Modularity/PlugIns/FolderPlugInSource.cs

@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.IO;
using Volo.Abp.Reflection;
using Volo.ExtensionMethods.Collections.Generic;
namespace Volo.Abp.Modularity.PlugIns
{
public class FolderPlugInSource : IPlugInSource
{
public string Folder { get; }
public SearchOption SearchOption { get; set; }
public FolderPlugInSource(string folder, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
Folder = folder;
SearchOption = searchOption;
}
public List<Type> GetModules()
{
var modules = new List<Type>();
var assemblies = AssemblyHelper.GetAllAssembliesInFolder(Folder, SearchOption);
foreach (var assembly in assemblies)
{
try
{
foreach (var type in assembly.GetTypes())
{
if (AbpModule.IsAbpModule(type))
{
modules.AddIfNotContains(type);
}
}
}
catch (Exception ex)
{
throw new AbpException("Could not get module types from assembly: " + assembly.FullName, ex);
}
}
return modules;
}
}
}

10
src/Volo.Abp/Volo/Abp/Modularity/PlugIns/IPlugInSource.cs

@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
namespace Volo.Abp.Modularity.PlugIns
{
public interface IPlugInSource
{
List<Type> GetModules();
}
}

18
src/Volo.Abp/Volo/Abp/Modularity/PlugIns/PlugInSourceExtensions.cs

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Volo.Abp.Modularity.PlugIns
{
public static class PlugInSourceExtensions
{
public static List<Type> GetModulesWithAllDependencies(this IPlugInSource plugInSource)
{
return plugInSource
.GetModules()
.SelectMany(AbpModuleFinder.FindAllModuleTypes)
.Distinct()
.ToList();
}
}
}

17
src/Volo.Abp/Volo/Abp/Modularity/PlugIns/PlugInSourceList.cs

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Volo.Abp.Modularity.PlugIns
{
public class PlugInSourceList : List<IPlugInSource>
{
internal List<Type> GetAllModules()
{
return this
.SelectMany(pluginSource => pluginSource.GetModulesWithAllDependencies())
.Distinct()
.ToList();
}
}
}

18
src/Volo.Abp/Volo/Abp/Modularity/PlugIns/PlugInSourceListExtensions.cs

@ -0,0 +1,18 @@
using System;
using System.IO;
namespace Volo.Abp.Modularity.PlugIns
{
public static class PlugInSourceListExtensions
{
public static void AddFolder(this PlugInSourceList list, string folder, SearchOption searchOption = SearchOption.TopDirectoryOnly)
{
list.Add(new FolderPlugInSource(folder, searchOption));
}
public static void AddTypes(this PlugInSourceList list, params Type[] moduleTypes)
{
list.Add(new PlugInTypeListSource(moduleTypes));
}
}
}

21
src/Volo.Abp/Volo/Abp/Modularity/PlugIns/PlugInTypeListSource.cs

@ -0,0 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Volo.Abp.Modularity.PlugIns
{
public class PlugInTypeListSource : IPlugInSource
{
private readonly Type[] _moduleTypes;
public PlugInTypeListSource(params Type[] moduleTypes)
{
_moduleTypes = moduleTypes;
}
public List<Type> GetModules()
{
return _moduleTypes.ToList();
}
}
}

22
src/Volo.Abp/Volo/Abp/Reflection/AssemblyHelper.cs

@ -0,0 +1,22 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.Loader;
namespace Volo.Abp.Reflection
{
internal static class AssemblyHelper
{
public static List<Assembly> GetAllAssembliesInFolder(string folderPath, SearchOption searchOption)
{
var assemblyFiles = Directory
.EnumerateFiles(folderPath, "*.*", searchOption)
.Where(s => s.EndsWith(".dll") || s.EndsWith(".exe"));
//TODO: AssemblyLoadContext not tested
return assemblyFiles.Select(AssemblyLoadContext.Default.LoadFromAssemblyPath).ToList();
}
}
}

3
src/Volo.Abp/project.json

@ -7,7 +7,8 @@
"System.Collections.Immutable": "1.3.0",
"Volo.ExtensionMethods": "1.0.0-*",
"Newtonsoft.Json": "9.0.1",
"Nito.AsyncEx.Context": "1.1.0"
"Nito.AsyncEx.Context": "1.1.0",
"System.Runtime.Loader": "4.3.0"
},
"frameworks": {

29
test/Volo.Abp.Tests/AbpApplication_Tests.cs

@ -1,29 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp;
using Volo.Abp.Modularity;
using Xunit;
public class AbpApplication_Tests
{
[Fact]
public void Should_Initialize_SingleModule_Application()
{
//Arrange
var services = new ServiceCollection();
using (var application = AbpApplication.Create<IndependentEmptyModule>(services))
{
//Act
application.Initialize(services.BuildServiceProvider());
//Assert
var module = application.ServiceProvider.GetRequiredService<IndependentEmptyModule>();
module.ConfigureServicesIsCalled.ShouldBeTrue();
module.OnApplicationInitializeIsCalled.ShouldBeTrue();
}
}
}

56
test/Volo.Abp.Tests/Volo/Abp/AbpApplication_Initialize_Tests.cs

@ -0,0 +1,56 @@
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Modularity;
using Volo.Abp.Modularity.PlugIns;
using Xunit;
namespace Volo.Abp
{
public class AbpApplication_Initialize_Tests
{
[Fact]
public void Should_Initialize_Single_Module()
{
//Arrange
var services = new ServiceCollection();
using (var application = AbpApplication.Create<IndependentEmptyModule>(services))
{
//Act
application.Initialize(services.BuildServiceProvider());
//Assert
var module = application.ServiceProvider.GetRequiredService<IndependentEmptyModule>();
module.ConfigureServicesIsCalled.ShouldBeTrue();
module.OnApplicationInitializeIsCalled.ShouldBeTrue();
}
}
[Fact]
public void Should_Initialize_PlugIn()
{
//Arrange
var services = new ServiceCollection();
using (var application = AbpApplication.Create<IndependentEmptyModule>(services, options =>
{
options.PlugInSources.AddTypes(typeof(IndependentEmptyPlugInModule));
}))
{
//Act
application.Initialize(services.BuildServiceProvider());
//Assert
var plugInModule = application.ServiceProvider.GetRequiredService<IndependentEmptyPlugInModule>();
plugInModule.ConfigureServicesIsCalled.ShouldBeTrue();
plugInModule.OnApplicationInitializeIsCalled.ShouldBeTrue();
}
}
}
}

21
test/Volo.Abp.Tests/Volo/Abp/Modularity/IndependentEmptyPlugInModule.cs

@ -0,0 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.Modularity
{
public class IndependentEmptyPlugInModule : AbpModule
{
public bool ConfigureServicesIsCalled { get; set; }
public bool OnApplicationInitializeIsCalled { get; set; }
public override void ConfigureServices(IServiceCollection services)
{
ConfigureServicesIsCalled = true;
}
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
OnApplicationInitializeIsCalled = true;
}
}
}

3
test/Volo.Abp.Tests/Volo/Abp/Modularity/ModuleLoader_Tests.cs

@ -1,5 +1,6 @@
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.Modularity.PlugIns;
using Xunit;
namespace Volo.Abp.Modularity
@ -10,7 +11,7 @@ namespace Volo.Abp.Modularity
public void Should_Load_Modules_By_Dependency_Order()
{
var moduleLoader = new ModuleLoader();
moduleLoader.LoadAll(new ServiceCollection(), typeof(MyStartupModule));
moduleLoader.LoadAll(new ServiceCollection(), typeof(MyStartupModule), new PlugInSourceList());
moduleLoader.Modules.Count.ShouldBe(3);
moduleLoader.Modules[0].Type.ShouldBe(typeof(AbpKernelModule));
moduleLoader.Modules[1].Type.ShouldBe(typeof(IndependentEmptyModule));

Loading…
Cancel
Save