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 (1)

Showing the top 1 NuGet packages that depend on OpenRobot.Framework.Modularity:

Package Downloads
OpenRobot.Litegraph.Core

Litegraph 流程引擎核心:画布解析与校验、图模型、46 个内置节点处理器、同步/异步通道、触发流程与主流程 tick、执行记录诊断。

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 117 9/18/2026