Browse Source

Merge pull request #17026 from abpframework/additional-module-assemblies

Introduce AdditionalModuleAssemblyAttribute
pull/17035/head
Halil İbrahim Kalkan 3 years ago
committed by GitHub
parent
commit
0e948efb3a
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 23
      docs/en/Module-Development-Basics.md
  2. 2
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
  3. 4
      framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs
  4. 2
      framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs
  5. 20
      framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs
  6. 4
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleDescriptor.cs
  7. 21
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs
  8. 24
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AdditionalAssemblyAttribute.cs
  9. 4
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/DependsOnAttribute.cs
  10. 23
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAbpModuleDescriptor.cs
  11. 8
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAdditionalModuleAssemblyProvider.cs
  12. 2
      framework/src/Volo.Abp.Core/Volo/Abp/Reflection/AssemblyFinder.cs
  13. 2
      framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/AbpHttpClientWebModule.cs
  14. 5
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/Modularity/ModuleLoader_Tests.cs
  15. 2
      framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/AssemblyFinder_Tests.cs

23
docs/en/Module-Development-Basics.md

@ -147,7 +147,7 @@ Lastly, you can override ``OnApplicationShutdown`` method if you want to execute
## Module Dependencies
In a modular application, it's not unusual for one module to depend upon another module(s). An Abp module must declare ``[DependsOn]`` attribute if it does have a dependency upon another module, as shown below:
In a modular application, it's not unusual for one module to depend upon another module(s). An ABP module must declare a ``[DependsOn]`` attribute if it does have a dependency upon another module, as shown below:
````C#
[DependsOn(typeof(AbpAspNetCoreMvcModule))]
@ -162,6 +162,27 @@ You can use multiple ``DependsOn`` attribute or pass multiple module types to a
A depended module may depend on another module, but you only need to define your direct dependencies. ABP investigates the dependency graph for the application at startup and initializes/shutdowns modules in the correct order.
## Additional Module Assemblies
ABP automatically registers all the services of your module to the [dependency injection](Dependency-Injection.md) system. It finds the service types by scanning types in the assembly that defines your module class. That assembly is considered as the main assembly of your module.
Typically, every assembly contains a separate module class definition. Then modules depend on each other using the `DependsOn` attribute as explained in the previous section. However, in some rare cases, your module may consist of multiple assemblies, and only one of them defines a module class, and you want to make the other assemblies parts of your module. In that case, you can use the `AdditionalAssembly` attribute as shown below:
````csharp
[DependsOn(...)] // Your module dependencies as you normally do
[AdditionalAssembly(typeof(BlogService))] // A type in the target assembly
public class BlogModule
{
//...
}
````
In this example, we assume that the `BlogService` class is inside one assembly (`csproj`) and the `BlogModule` class is inside another assembly (`csproj`). With the `AdditionalAssembly` definition, ABP will load the assembly containing the `BlogService` class as a part of the blog module.
Notice that `BlogService` is only an arbitrary selected type in the target assembly. It is just used to indicate the related assembly. You could use any type in the assembly.
> WARNING: If you need to use the `AdditionalAssembly`, be sure that you don't design your system in a wrong way. With this example above, the `BlogService` class' assembly should normally have its own module class and the `BlogModule` should depend on it using the `DependsOn` attribute. Do not use the `AdditionalAssembly` attribute when you can already use the `DependsOn` attribute.
## Framework Modules vs Application Modules
There are **two types of modules.** They don't have any structural difference but categorized by functionality and purpose:

2
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs

@ -233,7 +233,7 @@ public class AbpAspNetCoreMvcModule : AbpModule
.GetRequiredService<IModuleContainer>()
.Modules
.Where(m => m.IsLoadedAsPlugIn)
.Select(m => m.Type.Assembly)
.SelectMany(m => m.AllAssemblies)
.Distinct();
AddToApplicationParts(partManager, moduleAssemblies);

4
framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationPartSorter.cs

@ -125,7 +125,7 @@ public static class ApplicationPartSorter
var moduleDependedAssemblies = moduleDescriptor
.Dependencies
.Select(d => d.Assembly)
.SelectMany(d => d.AllAssemblies)
.ToArray();
return partManager.ApplicationParts
@ -161,6 +161,6 @@ public static class ApplicationPartSorter
{
return moduleContainer
.Modules
.FirstOrDefault(m => m.Assembly == assembly);
.FirstOrDefault(m => m.AllAssemblies.Contains(assembly));
}
}

2
framework/src/Volo.Abp.Autofac/Autofac/Builder/AbpRegistrationBuilderExtensions.cs

@ -69,7 +69,7 @@ public static class AbpRegistrationBuilderExtensions
where TActivatorData : ReflectionActivatorData
{
// Enable Property Injection only for types in an assembly containing an AbpModule and without a DisablePropertyInjection attribute on class or properties.
if (moduleContainer.Modules.Any(m => m.Assembly == implementationType.Assembly) &&
if (moduleContainer.Modules.Any(m => m.AllAssemblies.Contains(implementationType.Assembly)) &&
implementationType.GetCustomAttributes(typeof(DisablePropertyInjectionAttribute), true).IsNullOrEmpty())
{
registrationBuilder = registrationBuilder.PropertiesAutowired(new AbpPropertySelector(false));

20
framework/src/Volo.Abp.Core/Volo/Abp/AbpApplicationBase.cs

@ -188,11 +188,13 @@ public abstract class AbpApplicationBase : IAbpApplication
{
if (!abpModule.SkipAutoServiceRegistration)
{
var assembly = module.Type.Assembly;
if (!assemblies.Contains(assembly))
foreach (var assembly in module.AllAssemblies)
{
Services.AddAssembly(assembly);
assemblies.Add(assembly);
if (!assemblies.Contains(assembly))
{
Services.AddAssembly(assembly);
assemblies.Add(assembly);
}
}
}
}
@ -279,11 +281,13 @@ public abstract class AbpApplicationBase : IAbpApplication
{
if (!abpModule.SkipAutoServiceRegistration)
{
var assembly = module.Type.Assembly;
if (!assemblies.Contains(assembly))
foreach (var assembly in module.AllAssemblies)
{
Services.AddAssembly(assembly);
assemblies.Add(assembly);
if (!assemblies.Contains(assembly))
{
Services.AddAssembly(assembly);
assemblies.Add(assembly);
}
}
}
}

4
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleDescriptor.cs

@ -11,6 +11,8 @@ public class AbpModuleDescriptor : IAbpModuleDescriptor
public Type Type { get; }
public Assembly Assembly { get; }
public List<Assembly> AllAssemblies { get; }
public IAbpModule Instance { get; }
@ -26,6 +28,7 @@ public class AbpModuleDescriptor : IAbpModuleDescriptor
{
Check.NotNull(type, nameof(type));
Check.NotNull(instance, nameof(instance));
AbpModule.CheckAbpModuleType(type);
if (!type.GetTypeInfo().IsAssignableFrom(instance.GetType()))
{
@ -34,6 +37,7 @@ public class AbpModuleDescriptor : IAbpModuleDescriptor
Type = type;
Assembly = type.Assembly;
AllAssemblies = AbpModuleHelper.GetAllAssemblies(type);
Instance = instance;
IsLoadedAsPlugIn = isLoadedAsPlugIn;

21
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModuleHelper.cs

@ -36,6 +36,27 @@ internal static class AbpModuleHelper
return dependencies;
}
public static List<Assembly> GetAllAssemblies(Type moduleType)
{
var assemblies = new List<Assembly>();
var additionalAssemblyDescriptors = moduleType
.GetCustomAttributes()
.OfType<IAdditionalModuleAssemblyProvider>();
foreach (var descriptor in additionalAssemblyDescriptors)
{
foreach (var assembly in descriptor.GetAssemblies())
{
assemblies.AddIfNotContains(assembly);
}
}
assemblies.Add(moduleType.Assembly);
return assemblies;
}
private static void AddModuleAndDependenciesRecursively(
List<Type> moduleTypes,

24
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AdditionalAssemblyAttribute.cs

@ -0,0 +1,24 @@
using System;
using System.Linq;
using System.Reflection;
namespace Volo.Abp.Modularity;
/// <summary>
/// Used to define additional assemblies for a module.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class AdditionalAssemblyAttribute : Attribute, IAdditionalModuleAssemblyProvider
{
public Type[] TypesInAssemblies { get; }
public AdditionalAssemblyAttribute(params Type[]? typesInAssemblies)
{
TypesInAssemblies = typesInAssemblies ?? Type.EmptyTypes;
}
public virtual Assembly[] GetAssemblies()
{
return TypesInAssemblies.Select(t => t.Assembly).Distinct().ToArray();
}
}

4
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/DependsOnAttribute.cs

@ -1,5 +1,4 @@
using System;
using JetBrains.Annotations;
namespace Volo.Abp.Modularity;
@ -9,12 +8,11 @@ namespace Volo.Abp.Modularity;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
public class DependsOnAttribute : Attribute, IDependedTypesProvider
{
[NotNull]
public Type[] DependedTypes { get; }
public DependsOnAttribute(params Type[]? dependedTypes)
{
DependedTypes = dependedTypes ?? new Type[0];
DependedTypes = dependedTypes ?? Type.EmptyTypes;
}
public virtual Type[] GetDependedTypes()

23
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAbpModuleDescriptor.cs

@ -6,13 +6,36 @@ namespace Volo.Abp.Modularity;
public interface IAbpModuleDescriptor
{
/// <summary>
/// Type of the module class.
/// </summary>
Type Type { get; }
/// <summary>
/// Main assembly that defines the module <see cref="Type"/>.
/// </summary>
Assembly Assembly { get; }
/// <summary>
/// All the assemblies of the module.
/// Includes the main <see cref="Assembly"/> and other assemblies defined
/// on the module <see cref="Type"/> using the <see cref="AdditionalAssemblyAttribute"/> attribute.
/// </summary>
List<Assembly> AllAssemblies { get; }
/// <summary>
/// The instance of the module class (singleton).
/// </summary>
IAbpModule Instance { get; }
/// <summary>
/// Is this module loaded as a plug-in?
/// </summary>
bool IsLoadedAsPlugIn { get; }
/// <summary>
/// Modules on which this module depends on.
/// A module can depend on another module using the <see cref="DependsOnAttribute"/> attribute.
/// </summary>
IReadOnlyList<IAbpModuleDescriptor> Dependencies { get; }
}

8
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/IAdditionalModuleAssemblyProvider.cs

@ -0,0 +1,8 @@
using System.Reflection;
namespace Volo.Abp.Modularity;
public interface IAdditionalModuleAssemblyProvider
{
Assembly[] GetAssemblies();
}

2
framework/src/Volo.Abp.Core/Volo/Abp/Reflection/AssemblyFinder.cs

@ -29,7 +29,7 @@ public class AssemblyFinder : IAssemblyFinder
foreach (var module in _moduleContainer.Modules)
{
assemblies.Add(module.Type.Assembly);
assemblies.AddRange(module.AllAssemblies);
}
return assemblies.Distinct().ToImmutableList();

2
framework/src/Volo.Abp.Http.Client.Web/Volo/Abp/Http/Client/Web/AbpHttpClientWebModule.cs

@ -31,7 +31,7 @@ public class AbpHttpClientWebModule : AbpModule
.ServiceProvider
.GetRequiredService<IModuleContainer>()
.Modules
.Select(m => m.Type.Assembly)
.SelectMany(m => m.AllAssemblies)
.Where(a => a.GetTypes().Any(AbpHttpClientProxyHelper.IsClientProxyService))
.Distinct())
{

5
framework/test/Volo.Abp.Core.Tests/Volo/Abp/Modularity/ModuleLoader_Tests.cs

@ -21,9 +21,14 @@ public class ModuleLoader_Tests
modules.Length.ShouldBe(2);
modules[0].Type.ShouldBe(typeof(IndependentEmptyModule));
modules[1].Type.ShouldBe(typeof(MyStartupModule));
modules[1].Assembly.ShouldBe(typeof(MyStartupModule).Assembly);
modules[1].AllAssemblies.Count.ShouldBe(2);
modules[1].AllAssemblies[0].ShouldBe(typeof(IAbpApplication).Assembly);
modules[1].AllAssemblies[1].ShouldBe(typeof(MyStartupModule).Assembly);
}
[DependsOn(typeof(IndependentEmptyModule))]
[AdditionalAssembly(typeof(IAbpApplication))]
public class MyStartupModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

2
framework/test/Volo.Abp.Core.Tests/Volo/Abp/Reflection/AssemblyFinder_Tests.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NSubstitute;
using Shouldly;
using Volo.Abp.Modularity;
@ -50,6 +51,7 @@ public class AssemblyFinder_Tests
{
var moduleDescriptor = Substitute.For<IAbpModuleDescriptor>();
moduleDescriptor.Type.Returns(moduleType);
moduleDescriptor.AllAssemblies.Returns(new List<Assembly> { moduleType.Assembly });
return moduleDescriptor;
}
}

Loading…
Cancel
Save