模块与插件式装配

Config.AddModule<T>() 如何用一次反射扫描把整个程序集接入路由、DI 与元模型。

最后更新

Known 的可扩展性建立在"程序集即插件"这个约定上:一个模块就是一个类库, Config.AddModule<T>() 只需一个泛型锚点,就能把整个程序集的路由、服务与元数据接进运行时。

反射扫描的三条线

调用 AddModule<T>() 时,Known 通过 typeof(T).Assembly 拿到目标程序集,然后按三条线扫描:

  1. Razor 组件 — 所有 @page 指令生成的组件类型加入 Router.AdditionalAssemblies, 跨程序集的路由与本项目内的路由共用一张表。
  2. 服务契约 — 类接口带 [WebApi] 特性时注册到服务端路由;类实现带 [Service] 特性时 注册到 DI 容器并自动配对到对应接口。
  3. 元模型工件 — 领域内的 Schema/Synonym/Entity 定义按 SysSchemaItem 多态工件表登记, 运行时以 SysMenu 薄导航树组织可见性。

三条线共享同一份程序集清单,因此模块的"接入点"只有一处、心智负担恒定。

为什么不用 [Module] 特性

早期版本曾在 assemblyinfo 上标 [assembly: KnownModule(...)]。定稿改回 AddModule<T>() 显式调用,原因有三:

  • 可排序:程序启动顺序显式、可预测,特性驱动会有加载顺序歧义。
  • 可裁剪:同一份模块程序集,可以按需只 AddModule<Frontend>() 或只 AddModule<Backend>()。
  • 可测试:单元测试里创建 ServiceCollection 后手动 AddModule<T>() 即可,不依赖特性扫描副作用。

模块程序集的最小骨架

MyCompany.Known.Modules.Order/
├─ MyCompany.Known.Modules.Order.csproj   Razor 类库
├─ _Imports.razor
├─ OrderModule.cs                         // 用作 AddModule<T> 锚点的空标记类
├─ Pages/
│   └─ OrderList.razor                    // @page "/order/list"
├─ Services/
│   └─ OrderService.cs                    // IOrderService 接口([WebApi]) 与 OrderService 实现([Service]) 同文件
└─ wwwroot/
    └─ order.css                          // 模块私有样式,_content 路径自动可访问

宿主端接入:

Config.AddModule<OrderModule>();

边界与限制

  • 不要跨模块引用 Razor 组件;模块间应通过 [WebApi] 契约或元模型工件交流。
  • 不要把业务实体的 EF DbContext 直接放在模块里;数据层属宿主进程的职责,见数据层。
  • 模块程序集必须是 Razor 类库(Microsoft.NET.Sdk.Razor);纯类库无法输出 _content/ 静态资源路径。
  • 一个 T 只能对应一个程序集,AddModule<T>() 重复调用会去重。

与其他概念的关系

微信公众号二维码 扫码关注公众号
QQ 交流群二维码 扫码加入 QQ 群