BrixWare.Extensions.DynamicDependencyInjection.Loader
1.0.0
dotnet add package BrixWare.Extensions.DynamicDependencyInjection.Loader --version 1.0.0
NuGet\Install-Package BrixWare.Extensions.DynamicDependencyInjection.Loader -Version 1.0.0
<PackageReference Include="BrixWare.Extensions.DynamicDependencyInjection.Loader" Version="1.0.0" />
<PackageVersion Include="BrixWare.Extensions.DynamicDependencyInjection.Loader" Version="1.0.0" />
<PackageReference Include="BrixWare.Extensions.DynamicDependencyInjection.Loader" />
paket add BrixWare.Extensions.DynamicDependencyInjection.Loader --version 1.0.0
#r "nuget: BrixWare.Extensions.DynamicDependencyInjection.Loader, 1.0.0"
#:package BrixWare.Extensions.DynamicDependencyInjection.Loader@1.0.0
#addin nuget:?package=BrixWare.Extensions.DynamicDependencyInjection.Loader&version=1.0.0
#tool nuget:?package=BrixWare.Extensions.DynamicDependencyInjection.Loader&version=1.0.0
BrixWare Dynamic Dependency Injection (DDI)
A secondary DI container that runs alongside Microsoft's IServiceProvider at runtime. Enables dynamic registration and removal of services after BuildServiceProvider().
DDI is an extension of Microsoft's DI, not a replacement. It builds exclusively on Microsoft.Extensions.DependencyInjection.
Why DDI?
DDI was originally developed in 2024 to solve a concrete problem: integrating multiple AI providers and a growing set of plugins into a large .NET application — at runtime, without restarting the host. The providers and plugins changed frequently, each with its own dependencies, and the decision about which ones to load was driven by licensing, configuration, and even AI itself.
The technology has been stable and in production use since then. As someone who relies heavily on open-source software every day, publishing DDI is my way of giving something back to the community that has given me so much.
Switching to an alternative DI container was not an option. A large base of existing software is built on Microsoft's DI, and retrofitting it to a different container would have been impractical. DDI was therefore designed to extend Microsoft's DI — not replace it — so that existing code, registrations, and patterns remain completely untouched.
Three scenarios drove the design:
- Licensing constraints — certain modules may only be loaded if a valid license is present. The decision to load them happens at runtime, not at build time.
- Flexible runtime environments — assemblies need to be loaded, swapped, or unloaded without restarting the application. Think hot-reload for business logic, feature toggling at the assembly level, or multi-tenant setups where each tenant activates a different set of services.
- AI-driven component composition — modern applications increasingly assemble their behavior at runtime based on AI decisions: selecting the right model provider, loading a specialist agent, or wiring up tool implementations chosen dynamically. DDI makes it straightforward to register, replace, or remove these components on the fly — without a restart and without touching the rest of the application.
- Closing the gap with Python — Python developers take it for granted: load a module, instantiate a class, wire it into a running system — all at runtime, all dynamic. In .NET, the compiled and type-safe nature of the platform has traditionally made this kind of flexibility difficult. DDI brings that same dynamic capability to .NET — loading, composing, and executing objects within a managed lifecycle — without sacrificing the type safety, DI integration, and lifetime management that .NET developers expect.
Microsoft's built-in DI intentionally locks the container after BuildServiceProvider(). DDI solves exactly this gap: a secondary container that lives alongside the standard one and can be modified freely at runtime — including full support for dependency chains that mix DDI-registered and standard DI-registered services.
How DDI Compares
| Feature | DDI | DryIoc | Stashbox | Lamar | Autofac | Simple Injector |
|---|---|---|---|---|---|---|
| Register after Build | Yes | Yes | Yes (ReMap) | Yes (additive) | No | No |
| Remove / Unregister | Yes | Yes (with caveats) | No | No | No | No |
| Cascade invalidation | Yes (automatic) | No | No | No | n/a | n/a |
| Dispose on Remove | Yes (immediate) | No (scope-end) | n/a | n/a | n/a | n/a |
| Works alongside MS DI | Yes (secondary container) | No (replaces) | No (replaces) | No (replaces) | No (replaces) | No (replaces) |
| Cross-container resolution | Yes (DDI ↔ MS DI) | No | No | No | No | No |
| Diagnostics API | Yes | No | No | Partial | No | No |
DryIoc supports
Unregister()but has an expression-caching problem: deregistered services can remain inlined in cached resolution expressions. Services must be registered withSetup.With(asResolutionCall: true)to avoid stale references. DryIoc also replaces MS DI entirely rather than extending it.
Features
- Singleton, Transient, and Scoped lifetimes — full parity with Microsoft DI
- Keyed services — register and resolve services by key
- Factory-based registration — delegate factories for all lifetimes
- Cross-container resolution — DDI objects can depend on services from the parent DI container and vice versa
- DynamicActivator — custom constructor selection with automatic parameter resolution (DDI → DI fallback)
- Cascade invalidation — removing a service automatically invalidates all dependent singletons
- IDisposable handling — singletons and scoped instances are disposed when removed or when the scope/collection is disposed
- Diagnostics API —
DependencyInfoprovides dependency graphs, resolution status, and lifetime metadata - TryAdd idempotent registration —
TryAddSingleton,TryAddTransient,TryAddScopedfor safe registration - Scoped lifetime —
CreateScope()creates isolated scopes with per-scope instance caching - Multi-target — supports .NET Standard 2.0, .NET Standard 2.1, and .NET 10
Getting Started
Installation
# Core library
dotnet add package BrixWare.Extensions.DynamicDependencyInjection
# Optional: Assembly loader for plugin architectures
dotnet add package BrixWare.Extensions.DynamicDependencyInjection.Loader
1. Register the DDI container in your DI setup
var services = new ServiceCollection();
// Register your standard DI services
services.AddSingleton<ILogger, Logger>();
// Add the DDI container
services.AddDynamicServiceProvider();
var serviceProvider = services.BuildServiceProvider();
2. Register services dynamically at runtime
var ddi = serviceProvider.GetRequiredService<IDynamicServiceCollection>();
// Singleton
ddi.AddSingleton<ICache, InMemoryCache>();
// Transient
ddi.AddTransient<IRepository, DataRepository>();
// Scoped
ddi.AddScoped<IScopedCounter, ScopedCounter>();
// Factory-based
ddi.AddSingleton<IAuditService>(provider => new AuditService("created-by-factory"));
// Keyed
ddi.AddKeyedSingleton<IMessageSender, EmailSender>("email");
ddi.AddKeyedSingleton<IMessageSender, SmsSender>("sms");
3. Resolve services
var ddiProvider = serviceProvider.GetRequiredService<IDynamicServiceProvider>();
// Direct resolution
var cache = ddiProvider.GetRequiredService<ICache>();
// Keyed resolution
var emailSender = ddiProvider.GetKeyedService<IMessageSender>("email");
// Convenience extensions on IServiceProvider
var repo = serviceProvider.GetDynamicRequiredService<IRepository>();
4. Scoped lifetime
using var scope = ddiProvider.CreateScope();
var scopedProvider = scope.ServiceProvider;
var counter1 = scopedProvider.GetRequiredService<IScopedCounter>(); // new instance
var counter2 = scopedProvider.GetRequiredService<IScopedCounter>(); // same instance within scope
// Different scopes get different instances
using var scope2 = ddiProvider.CreateScope();
var counter3 = scope2.ServiceProvider.GetRequiredService<IScopedCounter>(); // different instance
5. Remove services dynamically
ddi.RemoveSingleton<ICache>(); // Removes and disposes cached instance
ddi.RemoveAll<IMessageSender>(); // Removes all lifetimes + keyed registrations
ddi.RemoveScoped<IScopedCounter>(); // Removes scoped registration
6. Dynamic assembly loading (Loader)
var ddi = serviceProvider.GetRequiredService<IDynamicServiceCollection>();
// Register a service from an external assembly at runtime
ddi.AddSingleton(
instanceAssemblyPath: @"C:\plugins",
instanceAssemblyName: "MyPlugin",
instanceTypeName: "MyPlugin.Services.ReportGenerator",
interfaceAssemblyPath: @"C:\plugins",
interfaceAssemblyName: "MyPlugin.Abstractions",
interfaceTypeName: "MyPlugin.Abstractions.IReportGenerator");
7. Diagnostics
var info = ddi.GetDependencyInfo<IRepository>();
// info.Dependencies → [ILogger, ICache]
// info.Dependents → [IOrderService]
// info.Lifetime → Singleton
// info.IsResolved → true/false
// info.IsFactory → true/false
var allInfos = ddi.GetAllDependencyInfos();
Architecture
GetService<T>() / GetKeyedService<T>()
→ ResolveService() / ResolveKeyedService()
→ ResolveDescriptor()
├─ ResolveSingleton() (caches result in ImplementationInstance)
├─ ResolveTransient() (creates new instance each time)
└─ ResolveScoped() (caches per scope via DynamicServiceScope)
├─ InvokeFactory() (delegate-based)
└─ CreateFromDescriptor() → DynamicActivator.CreateInstance()
└─ SelectBestConstructor() + InvokeConstructor()
(recursive parameter resolution with DDI → DI fallback)
Cross-Container Resolution
DDI objects can have constructor parameters from the parent DI container. The DynamicActivator automatically resolves parameters by checking:
- DDI container (
IsRegistered) - Parent DI container (
IServiceProviderIsServiceorGetService)
This enables deep dependency chains across both containers:
PaymentService(DDI) → OrderService(DDI) → DataRepository(DDI) → ILogger(DI) + ICache(DDI)
→ NotificationService(DDI) → ILogger(DI)
→ IConfiguration(DI)
Project Structure
| Project | Description | Packable |
|---|---|---|
BrixWare.Extensions.DynamicDependencyInjection |
Core DDI library | Yes (NuGet) |
BrixWare.Extensions.DynamicDependencyInjection.Loader |
Assembly/type loader for dynamic registration from strings | Yes (NuGet) |
BrixWare.Extensions.DynamicDependencyInjection.UnitTests |
NUnit test suite for core library | — |
BrixWare.Extensions.DynamicDependencyInjection.Loader.UnitTests |
NUnit test suite for loader | — |
BrixWare.TestApp |
ASP.NET Core demo app with SQLite | — |
BrixWare.TestLib |
Shared test service definitions | — |
BrixWare.DDI.AgentDemo |
AI Agent demo — generates .NET services at runtime via Claude + Roslyn | — |
Target Frameworks
| Package | .NET Standard 2.0 | .NET Standard 2.1 | .NET 10 |
|---|---|---|---|
| Core library | ✓ | ✓ | ✓ |
| Loader | ✓ | ✓ | ✓ |
.NET Standard 2.0 ensures compatibility with .NET Framework 4.6.1+ and .NET Core 2.0+.
Core Types
| Type | Purpose |
|---|---|
DynamicServiceCollection |
Service registry — implements IList<DynamicServiceDescriptor>, IDynamicServiceCollection, IDynamicServiceProvider, IDisposable |
DynamicServiceDescriptor |
Descriptor with ServiceKey, ServiceType, ImplementationType, ImplementationInstance, ParameterTypes, Lifetime |
DynamicServiceCollectionExtensions |
Registration API — AddSingleton, AddTransient, AddScoped, AddKeyed*, TryAdd*, Remove* |
DynamicServiceProviderExtensions |
Resolution API — GetService, GetKeyedService, GetRequired*, CreateScope |
DynamicActivator |
Constructor selection + parameter resolution (DDI → DI fallback) |
DynamicServiceScope |
Per-scope instance cache with IDisposable cleanup |
ScopedDynamicServiceProvider |
Scoped wrapper implementing IDynamicServiceProvider |
DependencyInfo |
Diagnostics — dependency graph, resolution status, lifetime metadata |
DynamicServiceExtensions |
AddDynamicServiceProvider() integration extension |
ServiceProviderExtensions |
GetDynamicService<T>() / GetDynamicRequiredService<T>() convenience extensions |
DynamicServiceRegistration |
(Loader) Registers services from assembly path + type name strings |
Demo Applications
TestApp — ASP.NET Core Web API
A fully working ASP.NET Core demo with a pre-seeded SQLite database demonstrating the full DDI dependency chain.
cd src/BrixWare.TestApp
dotnet run
Then open https://localhost:{PORT}/scalar/v1 in your browser.
The demo database contains 7 pre-registered services:
| # | Service | Implementation | Lifetime | Dependencies |
|---|---|---|---|---|
| 1 | IBookRepository |
BookRepository |
Singleton | none (DDI leaf) |
| 2 | INotificationService |
EmailNotificationService |
Singleton | ILogger (DI) |
| 3 | IInventoryService |
InventoryService |
Singleton | IBookRepository (DDI) + ILogger (DI) |
| 4 | IBookSearchService |
BookSearchService |
Transient | IBookRepository (DDI) + ILogger (DI) |
| 5 | IReportService |
ReportService |
Singleton | IBookRepository (DDI) + IConfiguration (DI) |
| 6 | IOrderService |
OrderService |
Transient | IInventoryService (DDI) + INotificationService (DDI) + ILogger (DI) |
| 7 | ILibraryFacade |
LibraryFacade |
Singleton | IOrderService + IBookSearchService + IReportService (all DDI) + ILogger (DI) |
Resetting the demo: Delete
src/BrixWare.TestApp/demo.dband restart the app — it will be recreated and re-seeded automatically.
Key API Endpoints
| Endpoint | Description |
|---|---|
GET /api/library/status |
Shows which DDI services are currently resolvable |
GET /api/library/books |
Lists all books (via DDI leaf) |
POST /api/library/checkout/{id} |
Checks out a book (resolves 3-level DDI chain) |
POST /api/library/order |
Places an order (resolves 4-level DDI chain) |
GET /api/library/facade/report |
Full facade — resolves all 7 services |
GET /api/dynamicservices |
Lists all registered DDI services from the database |
PUT /api/dynamicservices/{id}/toggle |
Enable/disable a service at runtime |
POST /api/dynamicservices/reload |
Reloads the DDI container from the database |
AgentDemo — AI-Powered Runtime Code Generation
A CLI application that demonstrates the most advanced DDI use case: AI-generated .NET services compiled and registered at runtime.
cd src/BrixWare.DDI.AgentDemo
dotnet run
The demo uses Claude (Anthropic API) to generate C# code from natural language prompts, compiles it in-memory with Roslyn, and registers the resulting type in DDI — all without restarting the application.
Flow:
User Prompt
→ Claude API (C# code generation)
→ Roslyn CSharpCompilation (in-memory assembly)
→ DDI.AddKeyedSingleton (runtime registration)
→ GetService<IGeneratedService>() (resolve & execute)
Example prompts:
Simple — greeting with date/time:
Create a service that returns the current date and time formatted as a friendly
German greeting, like "Guten Morgen! Es ist Samstag, der 5. April 2026, 14:30 Uhr"
Algorithmic — Fibonacci with memoization:
Create a service that calculates the first 20 Fibonacci numbers and returns them
as a formatted table with index and value, e.g. "F(1) = 1, F(2) = 1, F(3) = 2..."
Visual — ASCII bar chart:
Create a service that generates ASCII art of a bar chart showing fictional monthly
revenue data for January through June 2026 with random values between 10000 and
50000. Include a legend and total sum at the bottom.
Note: The AgentDemo requires an Anthropic API key. This is a proof-of-concept to demonstrate DDI's dynamic capabilities. See the Security Disclaimer for important notes about runtime code execution.
Dependencies
Core Library
| Package | .NET Standard 2.0/2.1 | .NET 10 |
|---|---|---|
Microsoft.Extensions.DependencyInjection |
8.0.1 | 10.0.5 |
Microsoft.Extensions.DependencyInjection.Abstractions |
8.0.2 | 10.0.5 |
Microsoft.Bcl.AsyncInterfaces |
8.0.0 | — |
Loader
| Package | .NET Standard 2.0/2.1 | .NET 10 |
|---|---|---|
Microsoft.Extensions.DependencyInjection.Abstractions |
8.0.2 | 10.0.5 |
Microsoft.Extensions.Options |
8.0.2 | 10.0.5 |
Security Disclaimer
DDI enables loading and registering arbitrary .NET assemblies and types at runtime. This is a powerful capability that introduces security responsibilities the caller must address.
By using this library you accept the following:
- Use at your own risk. DDI does not validate, authenticate, sign-check, or sandbox any assembly or type that is registered with it. It is the sole responsibility of the consuming application to ensure that only trusted, authorized assemblies are loaded.
- Implement your own security controls. Before passing an assembly path, type name, or factory delegate to DDI, the calling code must apply appropriate safeguards such as:
- Verifying assembly signatures or strong names
- Restricting allowed paths to known, controlled directories
- Validating type names against an allowlist
- Enforcing license and authorization checks prior to registration
- No warranty. This software is provided "as is", without warranty of any kind, express or implied. The authors and copyright holders are not liable for any claim, damages, or other liability arising from the use of this software or from loading assemblies through it.
The AgentDemo compiles and executes AI-generated code at runtime. This is inherently risky and intended as a proof-of-concept only. Never run AI-generated code in production without proper sandboxing and review.
The MIT license under which this software is distributed already excludes all warranties and liability to the maximum extent permitted by applicable law. This section makes the runtime-loading risk explicit so that integrators are aware of it before deployment.
Contributing
Contributions are welcome! Please read the Contributing Guide for details on how to submit pull requests, report issues, and contribute to the project.
License
MIT — Copyright © 2025 BrixWare — Janusch Koza
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. 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 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. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- BrixWare.Extensions.DynamicDependencyInjection (>= 1.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options (>= 8.0.2)
-
.NETStandard 2.1
- BrixWare.Extensions.DynamicDependencyInjection (>= 1.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
- Microsoft.Extensions.Options (>= 8.0.2)
-
net10.0
- BrixWare.Extensions.DynamicDependencyInjection (>= 1.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Options (>= 10.0.5)
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 | 41 | 4/6/2026 |