OpenRobot.Framework.Modularity 1.0.0

dotnet add package OpenRobot.Framework.Modularity --version 1.0.0
                    
NuGet\Install-Package OpenRobot.Framework.Modularity -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="OpenRobot.Framework.Modularity" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="OpenRobot.Framework.Modularity" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="OpenRobot.Framework.Modularity" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add OpenRobot.Framework.Modularity --version 1.0.0
                    
#r "nuget: OpenRobot.Framework.Modularity, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package OpenRobot.Framework.Modularity@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=OpenRobot.Framework.Modularity&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=OpenRobot.Framework.Modularity&version=1.0.0
                    
Install as a Cake Tool

OpenRobot.Framework.Modularity

模块化启动框架(net8.0):模块生命周期 + [DependsOn] 拓扑排序 + 程序集扫描 + 系统路径配置 + 后台定时任务。 日志基于 Microsoft.Extensions.Logging,依赖注入用标准 IServiceCollection。

  • 模块化:一个模块 = 一个继承 StartupModule 的类;四阶段 PreInitialize → Initialize → PostInitialize → Shutdown, 外加异步就绪等待 WaitReadyAsync。依赖关系用 [DependsOn] 声明,框架做确定性拓扑排序。
  • 构造注入:模块由容器经 ActivatorUtilities 创建,要日志注入 ILogger<TModule>,要服务注入接口—— 没有静态服务定位器。
  • 依赖只读:模块由容器创建、容器只构筑一次;不提供运行期改容器的能力。
  • 无副作用:不写环境变量、不落盘、不起后台线程(后台任务要显式注册才会跑)。

安装

<PackageReference Include="OpenRobot.Framework.Modularity" Version="1.0.0" />

包由本仓库产出:dotnet pack common/OpenRobot.Framework.Modularity/OpenRobot.Framework.Modularity.csproj -c Release。

依赖:

  • Microsoft.Extensions.DependencyInjection 8.0.1(完整包:BuildServiceProvider() 不在 Abstractions 里)
  • Microsoft.Extensions.Logging.Abstractions 8.0.2

库不引具体日志实现。控制台输出由宿主引 Microsoft.Extensions.Logging.Console 并调 AddSimpleConsole() 决定。


快速开始

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OpenRobot.Framework.Modularity;
using OpenRobot.Framework.Modularity.BackgroundTasks;
using OpenRobot.Framework.Modularity.Paths;

var builder = ModuleHostBuilder.Create(args);

builder.Services.AddLogging(b => b
    .AddSimpleConsole(o => { o.SingleLine = true; o.TimestampFormat = "HH:mm:ss.fff "; })
    .SetMinimumLevel(LogLevel.Information));
builder.Services.AddSingleton<ISystemPathConfiguration>(new SystemPathConfiguration());
builder.Services.AddSingleton<IBackgroundTaskManager, BackgroundTaskManager>();
builder.Services.AddSingleton<MyService>();

builder.Options.AssemblyPrefixes.Add("MyApp");   // 主动加载 MyApp*.dll 里的模块
builder.AddModule<BackgroundTasksModule>();      // 想要后台任务统一启停就加这一行

await using var host = builder.Build();          // 容器在这里构筑,且只构筑这一次
await host.RunAsync();                           // 初始化 → 就绪等待 → 阻塞至 Ctrl+C → 逆序关闭

写一个模块

public sealed class MyModule : StartupModule
{
    private readonly ILogger<MyModule> _logger;
    private readonly MyService _service;

    // 构造注入:容器负责解析;模块本身不必注册进 services
    public MyModule(ILogger<MyModule> logger, MyService service)
    {
        _logger = logger;
        _service = service;
    }

    public override void Initialize()
    {
        // 只注册、不连接——此刻其它模块的处理器可能还没就位
        _service.MessageReceived += OnMessage;
        _logger.LogInformation("MyModule 已装配");
    }

    public override void PostInitialize()
    {
        _service.Connect();   // 全部模块 Initialize 完成后才发起连接
    }

    public override async Task WaitReadyAsync(CancellationToken ct)
    {
        await _service.WaitConnectedAsync(ct);   // 就绪前不打印「服务已启动」
    }

    public override void Shutdown() => _service.MessageReceived -= OnMessage;
}

声明依赖

[DependsOn(typeof(DeviceRegisterModule))]      // 本模块排在 DeviceRegisterModule 之后
public sealed class CloudMqttModule : StartupModule { /* ... */ }

API 速查

ModuleHostBuilder

成员 说明
Create(args) 创建构建器;args 存进 Options.Arguments,库不解析
Services IServiceCollection,用标准扩展方法注册
Options ModuleHostOptions(扫描前缀 / 就绪超时 / Ctrl+C)
AddModule<TModule>() 显式注册模块类型(不在扫描范围或需要固定顺序时用)
Build() 构筑容器 + 收集模块(显式 ∪ 扫描)→ 拓扑排序 → 实例化,返回 ModuleHost

ModuleHost

成员 说明
Services / Modules 容器 / 按拓扑序的模块实例
Initialize() PreInitialize → Initialize → PostInitialize,幂等
WaitReadyAsync(ct) 按序调各模块就绪等待;单个超时/异常不阻断其余
RunAsync(ct) 完整流程:初始化 → 就绪 → 阻塞至 Ctrl+C / ct → 关闭
Shutdown() 逆序关闭,逐模块隔离异常,幂等
DisposeAsync() Shutdown + 释放容器

ModuleHostOptions

项 默认 说明
AssemblyPrefixes 空 扫描前主动加载的 dll 名前缀(不区分大小写)。空 = 只扫已加载程序集
ReadyTimeout 15s 就绪等待整体超时;超时后后台初始化继续
HandleConsoleCancelKeyPress true RunAsync 是否接管 Ctrl+C
Arguments 启动参数 原样保存,库不解析

StartupModule

成员 说明
PreInitialize / Initialize / PostInitialize / Shutdown 四个虚方法,默认空实现
WaitReadyAsync(ct) 异步就绪等待,默认立即就绪
Services 宿主容器。优先用构造注入,本属性只是延迟解析的逃生口

BackgroundTasks

类型 说明
IBackgroundTask Name + Start() + IAsyncDisposable
BackgroundTaskBase 周期任务基类:启动即执行一次,之后按 Interval 循环;异常不中断循环
IBackgroundTaskManager All / Register(不启动)/ StartAll(幂等)/ StopAll(逆序 + 清空)
BackgroundTasksModule 内置模块:PostInitialize 统一启动、Shutdown 统一停止
public sealed class HeartbeatTask : BackgroundTaskBase
{
    public HeartbeatTask(ILoggerFactory factory, MyService service) : base(factory) => _service = service;
    public override string Name => "心跳";
    public override TimeSpan Interval => TimeSpan.FromSeconds(30);
    protected override Task ExecuteAsync(CancellationToken ct) => _service.PingAsync(ct);
}

// 模块 Initialize 里注册,BackgroundTasksModule 在 PostInitialize 统一启动
_manager.Register(new HeartbeatTask(loggerFactory, service));

Paths

成员 值
AppRootPath 构造参数;默认 AppContext.BaseDirectory
AppDataPath / AppLogPath / PluginPath {root}/Data、{root}/Logs、{root}/Plugins

与源工程对照

迁移自 SmartEdge 的 OpenRobot.EdgeControl.* / SmartStudio.SelfHost.Startup:

源工程 本库 变化
StartupModule StartupModule 去掉 Configuration 属性(改构造注入)
SmartStartup ModuleHost + ModuleHostBuilder 静态类 → 实例类;扫描前缀从硬编码改为可配
ISmartLogger / ConsoleLogger 删除 换 Microsoft.Extensions.Logging
SmartConfiguration 删除 换标准 DI
StartupModuleConfig 删除 「日志 + 系统路径」打包体,两者现由 DI 直接提供
HostStartupModule BackgroundTasksModule 同样职责,收进库内
SystemPathConfiguration Paths.SystemPathConfiguration 增加可注入的根目录(便于测试)

三处行为差异(有意为之)

  1. 循环依赖抛异常:源实现检测到环后把剩余模块按发现顺序追加(顺序随机且无人知晓)。 本库在 Build() 抛 InvalidOperationException,消息含涉及模块名。
  2. Shutdown 逐模块隔离:源实现任一模块抛异常即中断逆序链,后续模块永不释放。本库逐个 try/catch 记日志继续。
  3. Initialize 失败回滚:源实现 Initialize() 抛出后已初始化模块不回收。本库捕获后逆序 Shutdown 已进入 Initialize 的模块再重抛,不留半初始化状态。

确定性拓扑排序

源工程按 AppDomain.GetAssemblies() 的返回顺序建图——同层模块的先后由运行时决定,多机部署可能出现 「本机正常、另一台启动顺序不同」。本库先把节点按 (程序集名, 类型全名) 定序再排序,相同输入永远给出相同顺序。


已知限制

  1. 容器必须异步释放:ModuleHost 内含只实现 IAsyncDisposable 的服务时,同步 Dispose() 会抛 InvalidOperationException。始终写 await using var host = builder.Build();。
  2. 容器构筑后冻结:不提供运行期增删服务的能力(源工程 SmartConfiguration 的反复重建容器会导致 旧 provider 泄漏、已解析单例丢失)。运行期可变状态请自己持有字段或用模块间事件。
  3. [DependsOn] 指向未参与构建的类型会被忽略:否则引一个可选模块就得连带引它的整个依赖程序集。
  4. 框架自带模块不参与自动扫描:BackgroundTasksModule 位于框架程序集,扫描时被跳过—— 自动纳进来会让「没注册 IBackgroundTaskManager」的宿主在 Build() 里直接抛(编译通过、启动崩溃)。 要用后台任务统一启停,请显式 AddModule<BackgroundTasksModule>() 并注册 IBackgroundTaskManager。 扫描的语义是「发现应用自己的模块」,与源工程一致(HostStartupModule 本就在应用程序集里)。
  5. PreInitialize 阶段不参与回滚:若某模块在 PreInitialize 抛出,已执行 PreInitialize 的模块不会被 Shutdown(它们的 Initialize 尚未开始,通常也没有资源在手)。需要资源保护的初始化放 Initialize。

版本历史

1.0.0(首次发布)

  • 模块生命周期 PreInitialize / Initialize / PostInitialize / WaitReadyAsync / Shutdown
  • [DependsOn] 确定性拓扑排序,循环依赖抛异常
  • ModuleHostBuilder / ModuleHost:容器一次性构筑、模块经 ActivatorUtilities 构造注入
  • 程序集扫描(可配前缀,主动加载未加载的 dll;ReflectionTypeLoadException 降级取可用类型)
  • ISystemPathConfiguration / SystemPathConfiguration
  • BackgroundTaskBase / IBackgroundTaskManager / BackgroundTasksModule
  • 日志改用 Microsoft.Extensions.Logging,不再有自定义日志抽象
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 98 9/18/2026