Alby's blog

世上没有巧合,只有巧合的假象。

0%

OrchardCore 如何实现模块化( Modular )和 Multi-Tenancy

一、概述

通常我们会在 Startup 类通过 void ConfigureServices(IServiceCollection services) 配置应用的服务。常见的形如 AddXXX 的方法,实际上调用的都是 IServiceCollection 或直接说是 ServiceCollectionAddSingletonAddTransientAddScoped等方法。调用ApplicationBuilderRequestDelegate Build() 方法会调用 IServiceCollection 的扩展方法 BuildServiceProvider 会创建并返回一个 ServiceProvider 对象。
还会在 Startup 类通过 void Configure(IApplicationBuilder app, IHostingEnvironment env) 配置请求管道,在该方法内进行的主要操作是添加中间件。常见的形如 UseMiddlewareUseXXX 的方法,实际上调用的都是 IApplicationBuilder 或直接说是 ApplicationBuilderIApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) 方法,Use 方法并不是马上将中间件配置入请求管道,而是将“实例化中间件的方式”保存到 ApplicationBuilder 内部一个列表的操作。调用ApplicationBuilderRequestDelegate Build() 方法会实例化中间件并把各个中间件串联起来。

OrchardCore 通过将服务和中间件放在不同的程序集以支持模块化。各个模块提供类似于 ConfigureServicesConfigure 的方法供运行时调用。

OrchardCore 还支持 Multi-TenancyTenant 有如下特性:

  1. 多个 Tenant 运行在同一个应用程序域中,每个 Tenant 几乎可以看做是独立的网站;
  2. 根据 HostPortPath 的各种组合匹配不同的 TenantModularTenantContainerMiddleware);
  3. 延迟激活,第一次请求 Tenant 才会激活(ModularTenantContainerMiddleware);
  4. 每个 Tenant 有不同的 DI 容器(ModularTenantContainerMiddleware);
  5. 每个 Tenant 有不同的请求管道,可以共享中间件,还可以使用特定中间件(ModularTenantRouterMiddleware)。

二、模块定义

模块是依赖于 OrchardCore.Modules.Targets 程序集的程序集,可以有各自的配置选项服务中间件等,还可以有各自的路由视图控制器FilterModelBinder 等,看起来像是一个 MVC Area
模块包含 0 或多个 FeatureFeature 是功能的逻辑组合,可单独开启或禁用。
Feature 之间可有依赖关系,并且支持跨模块的依赖。

备注:

  1. OrchardCore 中,一个程序集只包含一个模块。
  2. 模块可以看做是特殊的 Feature
  3. 用于定义 ThemeOrchardCore.Theme.Targets 程序集也依赖于 OrchardCore.Modules.Targets 程序集。

1、Mainifest.cs 文件

模块有个以程序集特性的形式嵌入程序集中的 Mainifest ,用于描述模块的基本信息、拥有的 Feature 和依赖的其他 Feature 等。一般写在 Mainifest.cs 文件中,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using OrchardCore.Modules.Manifest;
[assembly: Module(
Name = "XML-RPC",
Author = "The Orchard Team",
Website = "http://orchardproject.net",
Version = "2.0.0"
)]
[assembly: Feature(
Id = "OrchardCore.XmlRpc",
Name = "XML-RPC",
Description = "The XML-RPC module enables creation of contents from client applications such as Open Live Writer.",
Category = "Infrastructure"
)]
[assembly: Feature(
Id = "OrchardCore.RemotePublishing",
Name = "Remote Publishing",
Description = "The remote publishing feature enables creation of contents from client applications such as Open Live Writer.",
Dependencies = new [] { "OrchardCore.XmlRpc" },
Category = "Infrastructure"
)]

在运行时可将 Mainifest 读取至 MainifestInfo 对象中。
ModuleAttribure 用于描述模块基本信息,只能用于程序集并且只能使用一次,在运行时可读取至 ModuleInfo 对象中。
一个模块可以包含 0 或多个 FeatureFeatureAttribure 用于描述模块提供的 Feature,只能用于程序集并且可以使用多次,在运行时可读取至 FeatureInfo 对象中。

ModuleAttribute 继承自 FeatureAttribute ,都位于 OrchardCore.Abstractions 程序集、OrchardCore.Modules.Manifest 命名空间中。

从类或对象来看,1 个 MainifestInfo 对象包含 1 个 ModuleInfo 对象,1 个 ModuleInfo 对象包含 0 或多个 FeatureInfo 对象。

2、ManifestInfo 和 FeatureInfo 类

ExtensionManager 类用于获取 Mainifest ,并将相关数据反序列化入 ManifestInfoFeatureInfo 对象中。

3、ModuelNameAttribute 类

如果一个项目引用了一些模块,MSBuild 在生成项目时会针对每个模块添加一个程序集级的 ModuleNameAttrbute ,用于保存引用的模块名称。
AssemblyAttributeModuleNamesProvider 类的 IEnumerable<string> GetModuleNames() 方法能够收集到 ModuleNameAttrbute

AssemblyAttributeModuleNamesProvider 位于 OrchardCore.Abstractions 程序集、OrchardCore.Modules 命名空间中。

4、ModuleMarkerAttribute 类

MSBuild 在生成模块的项目时会自动添加程序集级的 ModuleMarkerAttributeModuleMarkerAttribute 继承 ModuleAttribute),位于 OrchardCore.Abstractions 程序集、 OrchardCore.Modules.Manifest 命名空间中。

5、ModuleAssetAttribute类

MSBuild 在生成模块的项目时会自动添加程序集级的 ModuleAssetAttributeModuleAssetAttribute 继承 ModuleAttribute),位于 OrchardCore.Abstractions 程序集、 OrchardCore.Modules.Manifest 命名空间中。

6、Module 类

在创建 Module 对象时,传入模块程序集的名称,构造函数会通过 Assembly.Load 加载模块程序集,并且收集模块的 ModuleAttributeModuleAssetAttributeModuleMarkerAttribute 放入自身属性中。

这里说的是 Module 类不是 ModuleAttribute 类。

ModularApplicationContext 位于 OrchardCore.Abstractions 程序集、OrchardCore.Modules 命名空间中。

7、IStartup 接口

每个模块可能需要注册一些服务至 DI 容器中,也可能需要注册一些中间件。OrchardCore 定义了一个 OrchardCore.Modules.IStartup, OrchardCore.Modules.Abstractions 接口,以及实现了该接口的 OrchardCore.Module s.StartupBase, sOrchardCore.Modules.Abstractions 抽象类。OrchardCore 模块通常有一个 Startup.cs 文件,实现了继承自 SetupBase 抽象类的名为 Startup 的具体类。

OrchardCoreStartup 类不是指通常 ASP.NET Core 中的那个类,IStartup 接口也不是通常 ASP.NET Core 中的那个接口,尽管它们的确很相似。

通常,对于 ASP.NET Core 应用的 Startup 类我们不直接实现 IStartup 接口,而采用更灵活的基于方法名约定的方式。另外,通过 IHostingStartup(承载启动)实现,在启动时从外部程序集向应用添加增强功能。但是使用 IHostingStartup 无法控制各个模块注册服务和添加中间件的顺序,也不支持延迟加载。
OrchardCore.Modules.IStartup,OrchardCore.Modules.Abstractions 相较于 Microsoft.AspNetCore.Hosting.IStartup,Microsoft.AspNetCore.Hosting.Abstractions 多了个 Order 属性,并且前者的 Configure 方法签名为void Configure(IApplicationBuilder app, IRouteBuilder routes, IServiceProvider serviceProvider),后者为 void Configure(IApplicationBuilder app)。因为模块通常位于不同的程序集, Order 属性的作用是控制向 DI 容器注册服务、添加中间件、添加配置和添加路由的顺序。

void Configure(IApplicationBuilder app, IRouteBuilder routes, IServiceProvider serviceProvider)routesserviceProvier 是为了支持模块化和 Multi-Tenancy

三、模块引擎

事实上没有一种明确的组件叫模块引擎。OrchardCore 提供了一些由于支持模块的基础设施,并提供将分散于各个模块的服务收集起来注册至 DI 容器,以及中间件添加至请求管道的机制。

1、AddOrchardCore

AddOrchardCore 不准确地说就是将服务注册至 DI 容器中以及将中间件添加至请求管道的,并返回一个 OrchardCoreBuilder 对象。
OrchardCoreBuilder 严格来说不是生成器模式,它类似于 Startup 类有 ConfigureServicesConfigure方法。但是当调用这两类方法时,并不是直接将服务注册到 DI 容器中或注册中间件,而是将注册的方式通过委托保存在集合中(通过 StartupActions )。这样做的目的是为了将来给每个 Tenant 注册这些服务和中间件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/// <summary>
/// Adds OrchardCore services to the host service collection.
/// </summary>
public static OrchardCoreBuilder AddOrchardCore(this IServiceCollection services)
{
// If an instance of OrchardCoreBuilder exists reuse it,
// so we can call AddOrchardCore several times.
var builder = services
.LastOrDefault(d => d.ServiceType == typeof(OrchardCoreBuilder))?
.ImplementationInstance as OrchardCoreBuilder;
if (builder == null)
{
builder = new OrchardCoreBuilder(services);
services.AddSingleton(builder);

AddDefaultServices(services);
AddShellServices(services);
AddExtensionServices(builder);

AddStaticFiles(builder);

AddAntiForgery(builder);
AddAuthentication(builder);
AddDataProtection(builder);

// Register the list of services to be resolved later on
services.AddSingleton(services);
}
return builder;
}

① AddDefaultServices

添加默认服务,比如 LoggingLocalizationWeb Encoders (指 HtmlUrlJavascript 的编码器)。
重要的是添加 Routing 服务。IServiceCollection 的扩展方法 AddMvc/AddMvcCore 会添加 Routing 服务。就算不是 MVC 应用也可以是使用路由,并且 OrchardCore 的路由可配置在不同的模块,所以在这里注册是因为后续会使用 Routing 相关服务。

② AddShellServices

添加用于支持 Tenant 的相关服务。Shell 涉及众多的类,这里暂时不分析。

③ AddExtensionServices

添加用于支持模块化的相关服务。主要是 AssemblyAttributeModuleNamesProvider : IModuleNamesProviderModularApplicationContext : IApplicationContext
AssemblyAttributeModuleNamesProvider 提供了一种从程序集的 Attribute 获取模块名称的方式。
ModularApplicationContext 提供了一个 OrchardCore.Modules.Application 对象,可在某些情况下指代应用。使用 ModularApplicationContext 的属性 Application 时,会触发 Application 对象的构造过程。

④ AddStaticFiles

添加静态文件服务中间件,主要是增加 ModuleEmbeddedStaticFileProvider 的支持。

⑤ AddAntiForgery

主要是提供对 Multi-Tenancy 的支持。为不同的 TenantAntiforgery Cookie 设置的名称和路径。

⑥ AddAuthentication

主要是提供对 Multi-Tenancy 的支持。

⑦ AddDataProtection

主要是提供对 Multi-Tenancy 的支持。

2、AddMvc

AddMvc 主要作用是添加和 Mvc 相关的中间件。请注意这是 OrchardBuilder 而不是 IServiceCollection 的扩展方法。
类似的方法 AddNancy 用于提供对 Nancy 的支持。

3、UseOrchardCore

UseOrchardCore 是一个 IApplicationBuilder 的扩展方法,主要作用是添加中间件 ModularTenantContainerMiddlewareModularTenantRouterMiddleware

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace Microsoft.AspNetCore.Builder
{
public static class ApplicationBuilderExtensions
{
/// <summary>
/// Enables multi-tenant requests support for the current path.
/// </summary>
public static IApplicationBuilder UseOrchardCore(this IApplicationBuilder app, Action<IApplicationBuilder> configure = null)
{
var env = app.ApplicationServices.GetRequiredService<IHostingEnvironment>();
var appContext = app.ApplicationServices.GetRequiredService<IApplicationContext>();
env.ContentRootFileProvider = new CompositeFileProvider(
new ModuleEmbeddedFileProvider(appContext),
env.ContentRootFileProvider);
app.UseMiddleware<PoweredByMiddleware>();
// Ensure the shell tenants are loaded when a request comes in
// and replaces the current service provider for the tenant's one.
app.UseMiddleware<ModularTenantContainerMiddleware>();
configure?.Invoke(app);
app.UseMiddleware<ModularTenantRouterMiddleware>();
return app;
}
}
}

四、Multi-Tenancy

ModularTenantContainerMiddleware 中间件中,根据 HostPortPath 的各种组合匹配不同的 TenantTenant 的激活延迟性的,在第一次请求 Tenant 才会激活。每个 Tenant 可以有不同的 DI 容器。

ModularTenantRouterMiddleware 中间件中,为当前 Tenant 配置单独的请求管道。

五、服务和中间件注册点

总结一下目前为止遇见的服务和中间件注册点。

1、服务注册点

包含名为 ConfigureServices 或类似的方法的接口和类:

类/接口 程序集 命名空间 备注
IStartup Microsoft.AspNetCore.Hosting.Abstractions Microsoft.AspNetCore.Hosting 接口。 实现类定义于应用。 配合 IWebHostBuilder.UseStartup 方法。
Startup 自定义 自定义 定义于应用。不继承任何接口或类,实现 Configure 和 ConfigureServices 等方法。 配合 IWebHostBuilder.UseStartup 方法。
IWebHostBuilder Microsoft.AspNetCore.Hosting.Abstractions Microsoft.AspNetCore.Hosting 接口。
WebHostBuilder : IWebHostBuilder Microsoft.AspNetCore.Hosting Microsoft.AspNetCore.Hosting ConfigureServices 不会进行实际的服务注册操作,当调用 Build 方法时才注册。
IStartup OrchardCore.Modules.Abstractions OrchardCore.Modules 接口。
StartupBase: IStartup OrchardCore.Modules.Abstractions OrchardCore.Modules 抽象类。
Startup: SetupBase 自定义 自定义 定义于 OrchareCore 模块。
OrchardCoreBuilder OrchardCore .Modules.Abstractions Microsoft.Extensions.DependencyInjection 注册 Tenant 服务和中间件。
StartupActions OrchardCore.Modules.Abstractions Microsoft.Extensions.DependencyInjection 包含 ConfigureServicesActions 属性,而非方法。

2、中间件注册点

包含名为 Configure 或类似方法的接口和类:

类/接口 程序集 命名空间 备注
IStartup Microsoft.AspNetCore.Hosting.Abstractions Microsoft.AspNetCore.Hosting 接口。实现类定义于应用。 配合 IWebHostBuilder.UseStartup 方法。
IHostingStartup Microsoft.AspNetCore.Hosting.Abstractions Microsoft.AspNetCore.Hosting 接口。
Startup 自定义 自定义 定义于应用。不继承任何接口或类,实现 Configure 和 ConfigureServices 等方法。 配合 IWebHostBuilder.UseStartup 方法。
IWebHostBuilder Microsoft.AspNetCore.Hosting.Abstractions Microsoft.AspNetCore.Hosting 接口。提供该接口的 Configure 扩展方法。
WebHostBuilderExtensions Microsoft.AspNetCore.Hosting Microsoft.AspNetCore.Hosting Configure 是 IWebHostBuilder 的扩展方法。
IStartup OrchardCore.Modules.Abstractions OrchardCore.Modules 接口。
StartupBase: IStartup OrchardCore.Modules.Abstractions OrchardCore.Modules 抽象类。
Startup: SetupBase 自定义 自定义 定义于 OrchareCore 模块。
OrchardCoreBuilder OrchardCore .Modules.Abstractions Microsoft.Extensions.DependencyInjection 注册 Tenant 服务和中间件。
StartupActions OrchardCore.Modules.Abstractions Microsoft.Extensions.DependencyInjection 包含 ConfigureActions 属性,而非方法。
IStartupFilter Microsoft.AspNetCore.Hosting.Abstractions Microsoft.AspNetCore.Hosting 接口。实现类定义于应用或 OrchareCore 模块。需注册为服务。

参考资料