1.3 ABP 总体介绍 - 模块系统

1.3.1 ABP 模块系统简介

ASP.NET Boilerplate提供了构建模块的基础结构,并将它们组合在一起以创建应用程序。模块可以依赖于另一个模块。通常,组件被视为模块。如果创建具有多个程序集的应用程序,建议您为每个程序集创建一个模块定义。

模块系统当前专注于服务端而不是客户端。

名词解释 : Assembly

程序集:Assembly 是一个用来包含程序的名称,版本号,自我描述,文件关联关系和文件位置等信息的一个集合。最简单的理解就是:一个你自己写的类库生成的 dll 就可以看做是一个程序集,这个程序集可以包括很多类,类又包括很多方法等。

1.3.2 定义模块

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

public class MyBlogApplicationModule : AbpModule //定义
{
    public override void Initialize() //初始化
    {
        IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
        //这行代码的写法基本上是不变的。它的作用是把当前程序集的特定类或接口注册到依赖注入容器中。
    }
}

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

1.3.3 生命周期方法

ASP.NET Boilerplate在应用程序启动和关闭时调用某些特定的模块方法。您可以覆盖这些方法以执行某些特定任务。

ABP 框架通过依赖关系的顺序来调用这些方法,假如:模块 A 依赖于模块 B,那么模块 B 要在模块 A 之前初始化,模块启动的方法顺序如下:

  1. PreInitialize-B
  • PreInitialize-A
  • Initialize-B
  • Initialize-A
  • PostInitialize-B
  • PostInitialize-A

下面是具体方法的说明:

PreInitialize 预初始化

当应用程序启动时,首先调用此方法。它是在初始化之前配置框架和其他模块的首选方法。

依赖注入注册之前,你可以在这个方法中指定你需要注入的自定义启动类。例如:加入你创建了某个符合约定的注册类,你应该使用 IocManager.AddConventionalRegisterer 方法在这里注册它。

Initialize 初始化

初始化:在这个方法中一般是来进行依赖注入的注册,一般我们通过 IocManager.RegisterAssemblyByConvention 这个方法来实现。如果你想实现自定义的依赖注入,那么请参考依赖注入的相关文档。

PostInitialize 初始化后

初始化后:最后一个方法,这个方法用来解析依赖关系。

Shutdown 关闭

关闭:当应用关闭以后,这个方法被调用。

1.3.4 模块依赖

Abp 框架会自动解析模块之间的依赖关系,但是我们还是建议使用DependsOn属性显式声明依赖项,如下所示:。

[DependsOn(typeof(MyBlogCoreModule))]//通过注解来定义依赖关系
public class MyBlogApplicationModule : AbpModule
{
    public override void Initialize()
    {
        IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
    }
}

例如上面的代码,我们就声明了 MyBlogApplicationModule 和 MyBlogCoreModule 的依赖关系,MyBlogApplicationModule 这个应用模块依赖于 MyBlogCoreModule 核心模块,并且,MyBlogCoreModule 核心模块会在 MyBlogApplicationModule 模块之前进行初始化。

ABP 可以从 startup module 递归的解析依赖关系,并按需初始化它们。最后初始化的模块是启动模块(startup module)。

1.3.5 插件模块

当模块从启动模块以及其依赖关系进行调查发现的时候,ABP 也能够动态的加载其它指定模块。AbpBootstrapper 类定义了 PlugInSources 属性,我们能用该属性添加需要动态加载的模块。插件源可以是任何实现了 IPlugInSource 接口的类。FolderPlugInSource 类实现了该接口,它可以被用来加载指定文件夹下的程序集。

ASP.NET Core

ABP 的 ASP.NET Core 模块也可以动态加载模块,你只需要在 Startup 类中使用已定义的扩展方法 AddAbp,如下所示:

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

我们可以使用扩展方法 AddFolder 更方便的实现上述功能:

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

了解更多关于 Startup 类的信息,请查看 ASP.NET 文档

ASP.NET MVC,Web API

对于经典的 ASP.NET MVC 应用,我们可以在 global.asax 重写 Application_Start 方法来添加插件文件夹,如下所示:

public class MvcApplication : AbpWebApplication<MyStartupModule>
{
    protected override void Application_Start(object sender, EventArgs e)
    {
        AbpBootstrapper.PlugInSources.AddFolder(@"C:\MyPlugIns");
        //...
        base.Application_Start(sender, e);
    }
}

插件中的控制器

如果你的模块包括了 MVC 或者 Web API 控制器,ASP.NET 不能发现这些控制器。为了克服这个问题,你可以在 global.asax 中添加代码来实现,如下所示:

using System.Web;
using Abp.PlugIns;
using Abp.Web;
using MyDemoApp.Web;

[assembly: PreApplicationStartMethod(typeof(PreStarter), "Start")]

namespace MyDemoApp.Web
{
    public class MvcApplication : AbpWebApplication<MyStartupModule>
    {
    }

    public static class PreStarter
    {
        public static void Start()
        {
            //...
            MvcApplication.AbpBootstrapper.PlugInSources.AddFolder(@"C:\MyPlugIns\");
            MvcApplication.AbpBootstrapper.PlugInSources.AddToBuildManager();
        }
    }
}

Additional Assemblies

对于 IAssemblyFinder 和 ITypeFinder 的默认实现(这两个接口的实现被 ABP 用来在应用程序中发现指定的类)仅仅只用来查找模块程序集以及在这些程序集中所使用的类型。我们可以在我们的模块中重写 GetAdditionalAssemblies 方法来包含附加程序集。

1.3.6 自定义的模块方法

我们自己定义的模块中可能有方法被其他依赖于当前模块的模块调用,下面的例子,假设模块 2 依赖于模块 1,并且想在预初始化的时候调用模块 1 的方法。这样,就把模块 1 注入到了模块 2,因此,模块 2 就能调用模块 1 的方法了。

译者注: ABP 的模块系统与 Orchard 的模块有类似之处,但还是有比较大的差别。Orchard 的框架修改了 ASP.NET 程序集的默认加载方式(模块的 DLL 没有放在 Bin 文件夹下,是放在 WEB 项目根文件夹下面的 Modules 文件夹下),实现了功能模块的热插拔,而 ABP 的模块程序集还是放在 Bin 文件夹下的,没有实现热插拔。

public class MyModule1 : AbpModule
{
    public override void Initialize() //初始化模块
    {
        IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());//这里,进行依赖注入的注册。
    }

    public void MyModuleMethod1()
    {
        //这里写自定义的方法。
    }
}

[DependsOn(typeof(MyModule1))]
public class MyModule2 : AbpModule
{
	private readonly MyModule1 _myModule1;

	public MyModule2(MyModule1 myModule1)
	{
	    _myModule1 = myModule1;
	}

    public override void PreInitialize()
    {
        _myModule1.MyModuleMethod1(); //调用MyModuleMethod1的方法。
    }

	public override void Initialize()
	{
	        IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
	 }
}

在这里,我们通过构造函数注入 MyModule1 到 MyModule2,所以 MyModule2 能够调用 MyModule1 的自定义方法。当且仅当 MyModule2 依赖于 MyModule1 才是可能的。

1.3.7 模块配置

虽然自定义模块可以被用来配置模块,但是,作者建议使用启动配置来定义和配置模块。

1.3.8 模块生命周期

所有的模块类都被自动的注册为单例模式。