NGS.ModuleHost
1.0.1
dotnet add package NGS.ModuleHost --version 1.0.1
NuGet\Install-Package NGS.ModuleHost -Version 1.0.1
<PackageReference Include="NGS.ModuleHost" Version="1.0.1" />
<PackageVersion Include="NGS.ModuleHost" Version="1.0.1" />
<PackageReference Include="NGS.ModuleHost" />
paket add NGS.ModuleHost --version 1.0.1
#r "nuget: NGS.ModuleHost, 1.0.1"
#:package NGS.ModuleHost@1.0.1
#addin nuget:?package=NGS.ModuleHost&version=1.0.1
#tool nuget:?package=NGS.ModuleHost&version=1.0.1
Read in other languages: Русский
NGS.ModuleHost
A pluggable module host for .NET with provable unloading. Load, unload and hot-reload code at runtime without restarting the process — with a guarantee that a module's assembly actually leaves memory after unload.
What it is
A module is a plain .NET assembly in its own folder. The host loads it into a dedicated collectible
AssemblyLoadContext, gives it an isolated DI container, and starts it. When a module's ALC is
unloaded, it is provably collected by the GC: if any live reference to the module's code remains
in the host, UnloadAsync does not pretend everything is fine — it throws ModuleLeakException.
That verified guarantee is what sets this apart from plugin loaders that "sort of" unload.
- Target framework: net10.0
- Single dependency:
Microsoft.Extensions.DependencyInjection - Host and modules communicate through DI
Who needs it
Any application with a plugin architecture. Hot-reload is an option, not an obligation:
- Hot-reload — load, unload and reload modules at runtime without a restart. For long-running processes where restarting is expensive: bots, game servers, assistants, SaaS with plugins.
- Load-only — load modules at startup and never unload them; updating means restarting the process. Modularity and extensibility without the unload discipline. Great for ASP.NET and any app that can simply restart.
If you never call UnloadAsync/ReloadAsync, you don't pay for unloading (no GC probes, no
StopAsync discipline).
Important: ALC isolation is an unload boundary, not a sandbox. Untrusted code is not isolated this way inside a single process.
Installation
dotnet add package NGS.ModuleHost
Quick start
The host lives in your DI container:
using Microsoft.Extensions.DependencyInjection;
using NGS.ModuleHost;
var services = new ServiceCollection()
.AddModuleEvents() // optional subsystems: event bus, cache, configs, tasks, shared services
.AddModuleCache()
.AddModuleTasks()
.AddModuleHost()
.BuildServiceProvider();
var host = services.GetRequiredService<ModuleHost>();
await host.LoadAllAsync("modules"); // load everything from ./modules
await host.ReloadAsync("acme.hello"); // swap the .dll without restarting the process
await host.UnloadAsync("acme.hello"); // unload — with a GC-verified collection check
And a module is a single class:
using Microsoft.Extensions.DependencyInjection;
using NGS.ModuleHost;
using NGS.ModuleHost.Events;
public sealed class HelloModule : IModule
{
public string Id => "acme.hello";
public Task StartAsync(IServiceProvider services, CancellationToken ct)
{
var bus = services.GetRequiredService<IEventBus>();
bus.Subscribe<Ping>((e, _) => { /* ... */ return Task.CompletedTask; });
return Task.CompletedTask;
}
}
The one unload rule
Built-in subsystems (events, cache, [Loop] tasks, shared services) release everything module-owned
themselves on unload — you don't unsubscribe manually. Manual cleanup is needed only for "raw"
roots the module created itself: its own Timer, Task, or a subscription to a C# event on an
external object. Release those in StopAsync — otherwise the ALC won't unload and UnloadAsync
will honestly say so.
More detail — in Docs.md.
Demo
A WPF app: on the left, a list of modules with load / unload / reload buttons; on the right, a panel where modules add their UI on the fly and remove it on unload.
dotnet run --project sample/NGS.ModuleHost.Sample
The demo widgets show both approaches to module UI: Clock builds a WPF control itself (direct WPF), while Counter describes its UI as data and the host renders it (declarative). Both unload cleanly.
On WPF and unloading. The library itself is framework-agnostic. WPF has one caveat: text input controls (
TextBox/RichTextBox) initialize the system Text Services (TSF) subsystem, which holds the native part of the last text module until the next text input — soUnloadAsyncof such a module may report a leak (the managed part is unloaded, and there is no accumulation). This is a WPF limitation, not the library's; details in Docs.md.
Features
- Isolation and unloading — a dedicated ALC and child-DI per module, provable collection after unload
- Reload — swap a .dll without restarting the process
- Manifest — id, version, dependencies; dependency-ordered loading
- Subsystems (all optional): event bus, shared cache, JSON configs, inter-module services,
background
[Loop]tasks - Extension point — your own subsystems via
IModuleHostFeature
Documentation
The full guide is in Docs.md: API, module layout and build, contract assembly, manifest, every subsystem with examples, leak diagnostics.
License
Apache-2.0 © 2026 ZikQ
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. 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. |
-
net10.0
- Microsoft.Extensions.DependencyInjection (>= 10.0.9)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.