Module System

引言

ABP 提供了构建模块的基础结构,并将模块组合在一起以创建程序。一个模块可以依赖于另一个模块。通常一个程序集(DLL)被视为一个模块。如果创建具有多个程序集的程序,建议你为每个程序集定义一个模块。
模块系统目前专注于服务器端而不是客户端。

模块定义

模块定义类是一个派生自 AbpModule 的类。假设我们正在开发一个可以在不同程序中使用的 Blog 模块。最简单的模块定义如下:

  1. public class MyBlogApplicationModule : AbpModule
  2. {
  3. public override void Initialize()
  4. {
  5. IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
  6. }
  7. }

如果需要,模块定义类可以通过依赖注入注册其类(也可以按常规方式完成,如上所示)。它还可以配置应用程序和其他模块,为应用程序添加新功能,等等……

生命周期方法

ABP 在应用程序启动和关闭时调用特定的模块方法。你可以通过重写这些方法以执行特定任务。
ABP 根据依赖顺序调用这些方法。即如果模块 A 依赖于模块 B,则先初始化模块 B 再初始化模块 A。
启动方法的确切顺序是:PreInitialize-B、PreInitialize-A、Initialize-B、Initialize-A、PostInitialize-B 和 PostInitialize-A。所有的依赖关系都是如此。程序关闭(shutdown)时调用方法的顺序也类似,只不过是反过来。

PreInitialize

程序启动时,首先调用此方法。它是在初始化之前配置框架和其他模块的首选方法。
你还可以在此处编写一些特定代码,以便在依赖注入注册之前运行。例如,如果你创建传统的注册类,则应使用IocManager.AddConventionalRegisterer 方法在此处注册它。

Initialize

这是应该进行依赖注入注册的地方。通常使用 IocManager.RegisterAssemblyByConvention 方法。如果要定义自定义依赖项注册,请参阅依赖注入文档

PostInitialize

此方法在启动过程的最后调用。在这里解决依赖是安全的。

Shutdown

程序关闭时调用此方法。

模块依赖

一个模块可以依赖于另一个模块。你需要使用 DependsOn 属性显式声明依赖,如下所示:

  1. [DependsOn(typeof(MyBlogCoreModule))]
  2. public class MyBlogApplicationModule : AbpModule
  3. {
  4. public override void Initialize()
  5. {
  6. IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
  7. }
  8. }

此处,我们向 ABP 声明 MyBlogApplicationModule 依赖于 MyBlogCoreModule,即 MyBlogCoreModule 应该在 MyBlogApplicationModule 之前初始化。
ABP 可以从 startup 模块开始递归地解析依赖关系并相应地初始化它们。startup 模块初始化为最后一个模块。

可插拔模块

虽然从 startup 模块开始解析依赖关系并初始化模块,但 ABP 也支持动态加载模块。 AbpBootstrapper 类定义了 PlugInSources 属性,该属性可用于向动态加载的可插拔模块添加源。可插拔模块的源可以是任意实现了 IPlugInSource 接口的类。 PlugInFolderSource 类实现从文件夹中的程序集获取可插拔模块。

ASP.NET Core

ABP ASP.NET Core 可以在 Startup 类中通过 AddAbp 扩展方法的 options 添加可插拔模块源:

  1. services.AddAbp<MyStartupModule>(options =>
  2. {
  3. options.PlugInSources.Add(new FolderPlugInSource(@"C:\MyPlugIns"));
  4. });

使用 AddFolder 扩展方法的语法更简单:

  1. services.AddAbp<MyStartupModule>(options =>
  2. {
  3. options.PlugInSources.AddFolder(@"C:\MyPlugIns");
  4. });

ASP.NET MVC, Web API

在 ASP.NET MVC 程序中,我们通过重写 global.asax 中的 Application_Start 来添加可插拔模块:

  1. public class MvcApplication : AbpWebApplication<MyStartupModule>
  2. {
  3. protected override void Application_Start(object sender, EventArgs e)
  4. {
  5. AbpBootstrapper.PlugInSources.AddFolder(@"C:\MyPlugIns");
  6. //...
  7. base.Application_Start(sender, e);
  8. }
  9. }

可插拔模块中的控制器

如果你的模块包含 MVC 或 Web API 的控制器,ASP.NET 将无法解析你的控制器。为了解决这个问题,你可以参考下面的代码修改 global.asax:

  1. using System.Web;
  2. using Abp.PlugIns;
  3. using Abp.Web;
  4. using MyDemoApp.Web;
  5. [assembly: PreApplicationStartMethod(typeof(PreStarter), "Start")]
  6. namespace MyDemoApp.Web
  7. {
  8. public class MvcApplication : AbpWebApplication<MyStartupModule>
  9. {
  10. }
  11. public static class PreStarter
  12. {
  13. public static void Start()
  14. {
  15. //...
  16. MvcApplication.AbpBootstrapper.PlugInSources.AddFolder(@"C:\MyPlugIns\");
  17. MvcApplication.AbpBootstrapper.PlugInSources.AddToBuildManager();
  18. }
  19. }
  20. }

额外的程序集

IAssemblyFinder 和 ITypeFinder 的默认实现(ABP 用于解析程序中的特定类)仅在它们的程序集中查找模块程序集和类型。我们可以覆盖模块中的 GetAdditionalAssemblies 方法以包含其他程序集。

自定义模块方法

你的模块还可以自定义方法,其他依赖于它的模块也可以使用这些自定义方法。假设 MyModule2 依赖于 MyModule1 并想要在 PreInitialize 中调用 MyModule1 的方法。

  1. public class MyModule1 : AbpModule
  2. {
  3. public override void Initialize()
  4. {
  5. IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
  6. }
  7. public void MyModuleMethod1()
  8. {
  9. //this is a custom method of this module
  10. }
  11. }
  12. [DependsOn(typeof(MyModule1))]
  13. public class MyModule2 : AbpModule
  14. {
  15. private readonly MyModule1 _myModule1;
  16. public MyModule2(MyModule1 myModule1)
  17. {
  18. _myModule1 = myModule1;
  19. }
  20. public override void PreInitialize()
  21. {
  22. _myModule1.MyModuleMethod1(); //Call MyModule1's method
  23. }
  24. public override void Initialize()
  25. {
  26. IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
  27. }
  28. }

此处我们通过构造函数将 MyModule1 注入到 MyModule2,因此 MyModule2 可以调用 MyModule1 的自定义方法。当且仅当 Module2 依赖于 Module1 时才能这样做。

模块配置

虽然可以通过自定义模块方法来配置模块,但我们依然建议你使用 startup 配置系统来定义和配置模块。

模块生命周期

模块类被自动注册为单例模式。