Baubit.DI
2025.51.3
dotnet add package Baubit.DI --version 2025.51.3
NuGet\Install-Package Baubit.DI -Version 2025.51.3
<PackageReference Include="Baubit.DI" Version="2025.51.3" />
<PackageVersion Include="Baubit.DI" Version="2025.51.3" />
<PackageReference Include="Baubit.DI" />
paket add Baubit.DI --version 2025.51.3
#r "nuget: Baubit.DI, 2025.51.3"
#:package Baubit.DI@2025.51.3
#addin nuget:?package=Baubit.DI&version=2025.51.3
#tool nuget:?package=Baubit.DI&version=2025.51.3
Baubit.DI
Autofac Extension: Baubit.DI.Autofac
Modularity framework for .NET with configuration-driven module composition.
Table of Contents
- Installation
- Overview
- Security
- Quick Start
- Application Creation Patterns
- Module Configuration in appsettings.json
- Recursive Module Loading
- Custom Service Provider Factories
- Consumer Module Registration
- API Reference
- Configuration Keys
- Extensions
- Inspiration
- License
Installation
dotnet add package Baubit.DI
Overview
Baubit.DI provides a modular approach to dependency injection where service registrations are encapsulated in modules. Modules can be:
- Composed hierarchically through nested modules
- Loaded dynamically from configuration
- Defined programmatically using
IComponent - Combined using both approaches (hybrid loading)
- Integrated with
IHostApplicationBuildervia extension methods
Security
Baubit.DI uses compile-time module discovery to eliminate remote code execution (RCE) vulnerabilities from configuration-driven module loading. Modules must be annotated with [BaubitModule] and discovered at compile time. Configuration can only select from these pre-registered modules using simple string keys instead of assembly-qualified type names.
Key security features:
- No reflection-based type loading from configuration
- Compile-time validation of module definitions
- Configuration uses simple string keys, not type names
- Consumer assemblies can register their own modules using
[GeneratedModuleRegistry]
To migrate existing code, add [BaubitModule("key")] to your modules and update configuration to use the key instead of the assembly-qualified type name.
Quick Start
1. Define a Configuration
public class MyModuleConfiguration : Configuration
{
public string ConnectionString { get; set; }
public int Timeout { get; set; } = 30;
}
2. Create a Module
[BaubitModule("mymodule")] // Required for configuration-based loading
public class MyModule : Module<MyModuleConfiguration>
{
// Constructor for loading from IConfiguration (appsettings.json)
public MyModule(IConfiguration configuration)
: base(configuration) { }
// Constructor for programmatic creation
public MyModule(MyModuleConfiguration configuration, List<IModule> nestedModules = null)
: base(configuration, nestedModules) { }
public override void Load(IServiceCollection services)
{
services.AddSingleton<IMyService>(new MyService(Configuration.ConnectionString));
}
}
Note: The [BaubitModule("key")] attribute registers your module for compile-time validated, secure configuration loading. The key is used in appsettings.json instead of assembly-qualified type names.
Application Creation Patterns
Baubit.DI supports three patterns for creating applications. Each pattern has its use cases.
Important: You must call the
Register()method on each module registry (typically 1 per library) before module loading. See Consumer Module Registration.
Pattern 1: Modules from appsettings.json
Load ALL modules from configuration. Module types, their configurations, and nested modules are defined in JSON.
// Register consumer modules first (if any)
MyModuleRegistry.Register();
// appsettings.json defines all modules
await Host.CreateApplicationBuilder()
.UseConfiguredServiceProviderFactory()
.Build()
.RunAsync();
appsettings.json:
{
"modules": [
{
"key": "mymodule",
"configuration": {
"connectionString": "Server=localhost;Database=mydb"
}
}
]
}
Use when:
- Module configuration should be externally configurable
- You want to change behavior without recompiling
- All module settings can be expressed in configuration
Pattern 2: Modules from Code (IComponent)
Load ALL modules programmatically using IComponent. No configuration file needed.
// Register consumer modules first (if any)
MyModuleRegistry.Register();
// Define a component that builds modules in code
public class MyComponent : Component
{
protected override Result<ComponentBuilder> Build(ComponentBuilder builder)
{
return builder.WithModule<MyModule, MyModuleConfiguration>(cfg =>
{
cfg.ConnectionString = "Server=localhost;Database=mydb";
});
}
}
// Load modules from component only (no appsettings.json)
await Host.CreateEmptyApplicationBuilder(new HostApplicationBuilderSettings())
.UseConfiguredServiceProviderFactory(componentsFactory: () => [new MyComponent()])
.Build()
.RunAsync();
Use when:
- Module configuration is determined at compile time
- You need full control over module instantiation
- Configuration values come from code, not files
Pattern 3: Hybrid Loading (appsettings.json + IComponent)
Combine BOTH configuration-based and code-based module loading. This is the most flexible approach.
// Register consumer modules first (if any)
MyModuleRegistry.Register();
// Load modules from BOTH appsettings.json AND code
await Host.CreateApplicationBuilder()
.UseConfiguredServiceProviderFactory(componentsFactory: () => [new MyComponent()])
.Build()
.RunAsync();
Loading order:
- Components from
componentsFactoryare loaded first - Modules from appsettings.json
modulessection are loaded second
Use when:
- Some modules need external configuration (database connections, API keys)
- Some modules need compile-time configuration or code-based logic
- You want to extend configuration-based modules with additional code-based ones
The full set of sample code can be found here
Module Configuration in appsettings.json
Module configurations can be defined in three ways:
Direct Configuration
Configuration values are enclosed in a configuration section:
{
"modules": [
{
"key": "mymodule",
"configuration": {
"connectionString": "Server=localhost;Database=mydb",
"timeout": 60,
"modules": [
{
"key": "nested-module",
"configuration": { }
}
]
}
}
]
}
Indirect Configuration
Configuration is loaded from external sources via configurationSource:
{
"modules": [
{
"key": "mymodule",
"configurationSource": {
"jsonUriStrings": ["file://path/to/config.json"]
}
}
]
}
config.json:
{
"connectionString": "Server=localhost;Database=mydb",
"timeout": 60,
"modules": [
{
"key": "nested-module",
"configuration": {
"somePropKey": "some_prop_value"
}
}
]
}
Hybrid Configuration
Combine direct values with external sources:
{
"modules": [
{
"key": "mymodule",
"configuration": {
"connectionString": "Server=localhost;Database=mydb"
},
"configurationSource": {
"jsonUriStrings": ["file://path/to/additional.json"]
}
}
]
}
additional.json:
{
"timeout": 60,
"modules": [
{
"key": "nested-module",
"configuration": { }
}
]
}
Recursive Module Loading
The module system supports recursive loading - modules can contain nested modules, which can contain their own nested modules, and so on. This enables extremely modular application architectures.
Example: Multi-level nesting
{
"modules": [
{
"key": "root-module",
"configuration": {
"modules": [
{
"key": "feature-module",
"configuration": {
"modules": [
{
"key": "subfeature-module",
"configuration": { }
}
]
}
}
]
}
}
]
}
ServiceProviderFactory flattens the module graph and loads each module in a depth first manner.
Custom Service Provider Factories
Baubit.DI uses ServiceProviderFactory by default, which works with the standard .NET IServiceCollection. You can provide custom factory implementations for integration with third-party DI containers.
See Baubit.DI.Autofac for reference.
Via Generic Type Parameter
await Host.CreateApplicationBuilder()
.UseConfiguredServiceProviderFactory<HostApplicationBuilder, CustomServiceProviderFactory>()
.Build()
.RunAsync();
Custom factory requirements:
- Must implement
IServiceProviderFactoryorIServiceProviderFactory<TContainerBuilder> - Must have a constructor accepting
(IConfiguration configuration, IComponent[] components) - Must derive from
ServiceProviderFactory<TContainerBuilder>for container integration
Consumer Module Registration
Consumer projects (test projects, libraries, plugins) can register their own modules using the [GeneratedModuleRegistry] attribute.
Important: Registry registration must be called before any module loading operations.
Step 1: Install Baubit.DI
dotnet add package Baubit.DI
The generator is automatically included in the package.
Step 2: Create a Registry Class
using Baubit.DI;
namespace MyProject
{
[GeneratedModuleRegistry]
internal static partial class MyModuleRegistry
{
// Register() method will be generated automatically
}
}
The generator discovers all modules in your assembly marked with [BaubitModule] and generates a Register() method in your namespace.
Step 3: Define Your Modules
[BaubitModule("my-custom-module")]
public class MyCustomModule : Module<MyConfig>
{
public MyCustomModule(IConfiguration config) : base(config) { }
public override void Load(IServiceCollection services)
{
services.AddSingleton<IMyService, MyService>();
}
}
Step 4: Register at Startup
Critical: Call Register() before any module loading:
// MUST be called before any module loading
MyModuleRegistry.Register();
// Now your modules are available in configuration
await Host.CreateApplicationBuilder()
.UseConfiguredServiceProviderFactory()
.Build()
.RunAsync();
Why this is required:
- The main
Baubit.DI.ModuleRegistryonly knows about modules in the Baubit.DI assembly - Consumer assemblies must explicitly register their modules with
ModuleRegistry.RegisterExternal() - The generated
Register()method does this automatically - Registration must happen before
UseConfiguredServiceProviderFactory()initializes the registry
Configuration:
{
"modules": [
{
"key": "my-custom-module",
"configuration": { }
}
]
}
API Reference
<details> <summary><strong>BaubitModuleAttribute</strong></summary>
Marks a module class for compile-time discovery and registration.
| Property | Description |
|---|---|
Key |
Unique string key used in configuration to identify this module |
Usage:
[BaubitModule("mymodule")]
public class MyModule : Module<MyConfig> { }
Requirements:
- Key must be unique across all modules in the compilation
- Module must implement
IModule - Module must have a constructor accepting
IConfiguration
</details>
<details> <summary><strong>GeneratedModuleRegistryAttribute</strong></summary>
Marks a partial class to receive generated module registry methods for consumer assemblies.
Usage:
[GeneratedModuleRegistry]
internal static partial class MyModuleRegistry
{
// Register() method generated automatically
}
// At application startup:
MyModuleRegistry.Register();
Purpose:
- Allows test projects to register test-specific modules
- Enables consumer libraries to provide their own modules
- Supports plugin architectures with distributed modules
</details>
<details> <summary><strong>ModuleRegistry</strong></summary>
Registry for secure module instantiation based on compile-time discovered modules.
| Method | Description |
|---|---|
TryCreate(string key, IConfiguration, out IModule) |
Attempts to create a module from the specified key |
RegisterExternal(Action<Dictionary<...>>) |
Registers modules from consumer assemblies |
Thread Safety: All public members are thread-safe.
Security: Only modules discovered at compile time with [BaubitModule] can be created.
</details>
<details> <summary><strong>IModule</strong></summary>
Interface for dependency injection modules.
| Member | Description |
|---|---|
Configuration |
Module configuration |
NestedModules |
Child modules |
Load(IServiceCollection) |
Register services |
</details>
<details> <summary><strong>Module / Module<TConfiguration></strong></summary>
Abstract base classes for modules.
| Constructor | Description |
|---|---|
Module(TConfiguration, List<IModule>) |
Create with config and nested modules |
Module(IConfiguration) |
Create from IConfiguration section |
| Virtual Method | Description |
|---|---|
OnInitialized() |
Called after construction |
GetKnownDependencies() |
Return hardcoded module dependencies |
Load(IServiceCollection) |
Register services (call base.Load for nested modules) |
</details>
<details> <summary><strong>IComponent / Component</strong></summary>
Interface and base class for grouping related modules.
| Method | Description |
|---|---|
Build(ComponentBuilder) |
Override to configure which modules the component contains |
GetEnumerator() |
Returns enumerator for modules (builds on first call) |
Dispose() |
Release resources |
</details>
<details> <summary><strong>ComponentBuilder</strong></summary>
Builder for creating components with collections of modules.
| Method | Description |
|---|---|
CreateNew() |
Create a new builder instance |
WithModule<TModule, TConfiguration>(ConfigurationBuilder<T>) |
Add a module using configuration builder |
WithModule<TModule, TConfiguration>(Action<ConfigurationBuilder<T>>) |
Add a module with configuration handler |
WithModule<TModule, TConfiguration>(Action<T>) |
Add a module with configuration override |
WithModulesFrom(params IComponent[]) |
Add modules from existing components |
Build() |
Build the component |
</details>
<details> <summary><strong>ModuleBuilder</strong></summary>
Builds modules from configuration.
| Method | Description |
|---|---|
CreateNew(IConfiguration) |
Create builder from config section |
CreateMany(IConfiguration) |
Create builders for nested modules |
Build() |
Build the module instance |
</details>
<details> <summary><strong>ModuleBuilder<TModule, TConfiguration></strong></summary>
Strongly-typed module builder.
| Method | Description |
|---|---|
CreateNew(ConfigurationBuilder<TConfiguration>) |
Create typed builder |
WithNestedModules(params IModule[]) |
Add nested modules |
WithNestedModulesFrom(IConfiguration) |
Load nested modules from config |
WithOverrideHandlers(params Action<TConfiguration>[]) |
Add configuration override handlers |
Build() |
Build typed module |
</details>
<details> <summary><strong>HostBuilderExtensions</strong></summary>
Extension methods for IHostApplicationBuilder.
| Method | Description |
|---|---|
UseConfiguredServiceProviderFactory(IConfiguration, Func<IComponent[]>, Action<T,IResultBase>) |
Configure host with module-based DI using factory type from configuration or default |
UseConfiguredServiceProviderFactory<THostApplicationBuilder, TServiceProviderFactory>(IConfiguration, Func<IComponent[]>, Action<T,IResultBase>) |
Configure host with module-based DI using specified factory type |
</details>
<details> <summary><strong>IServiceProviderFactory / IServiceProviderFactory<TContainerBuilder></strong></summary>
Interface for service provider factories that can be configured via host application builders.
| Member | Description |
|---|---|
UseConfiguredServiceProviderFactory<THostApplicationBuilder>(IHostApplicationBuilder) |
Configure host builder with this factory |
InternalFactory |
The internal factory wrapped by this instance (generic version only) |
Modules |
Collection of loaded modules (generic version only) |
Load(TContainerBuilder) |
Load modules into container (generic version only) |
</details>
<details> <summary><strong>ServiceProviderFactory<TContainerBuilder></strong></summary>
Abstract base class for service provider factories that integrate module-based dependency injection with custom container builders.
| Constructor | Description |
|---|---|
ServiceProviderFactory(IServiceProviderFactory<TContainerBuilder>, IConfiguration, IComponent[]) |
Create with internal factory, configuration, and components |
| Property | Description |
|---|---|
InternalFactory |
The internal service provider factory being wrapped |
Modules |
Flattened collection of all loaded modules |
| Method | Description |
|---|---|
Load(TContainerBuilder) |
Abstract method to load modules into the container builder |
UseConfiguredServiceProviderFactory<THostApplicationBuilder>(IHostApplicationBuilder) |
Configure host builder with this factory |
</details>
<details> <summary><strong>ServiceProviderFactory</strong></summary>
Default service provider factory that uses IServiceCollection for dependency injection.
| Constructor | Description |
|---|---|
ServiceProviderFactory(IConfiguration, IComponent[]) |
Create with configuration and components |
ServiceProviderFactory(DefaultServiceProviderFactory, IConfiguration, IComponent[]) |
Create with specific default factory, configuration, and components |
| Method | Description |
|---|---|
Load(IServiceCollection) |
Load all modules into the service collection |
</details>
<details> <summary><strong>ModuleExtensions</strong></summary>
Extension methods for module operations.
| Method | Description |
|---|---|
TryFlatten<TModule>(TModule) |
Flatten a module and its nested modules into a flat list |
TryFlatten<TModule>(TModule, List<IModule>) |
Flatten a module into an existing list |
Serialize(JsonSerializerOptions) |
Serialize a module to JSON string (uses module key from [BaubitModule]) |
SerializeAsJsonObject(JsonSerializerOptions) |
Serialize modules collection to JSON object (uses module keys) |
Note: Serialization methods use the module key from [BaubitModule] attribute instead of assembly-qualified type names for security.
</details>
Configuration Keys
| Key | Description |
|---|---|
key |
Module key (from [BaubitModule] attribute) |
configuration |
Object containing direct configuration values |
configurationSource |
Object specifying external configuration sources |
modules |
Array of nested module definitions (inside configuration) |
moduleSources |
Array of external configuration sources for modules |
Extensions
Look at Baubit.DI.Extensions
Inspiration
See INSPIRATION.md for details on libraries and ideas that influenced this project.
License
MIT
| 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 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. |
| .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 was computed. |
| .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
- Baubit.Configuration (>= 2025.51.2)
- Microsoft.Extensions.DependencyInjection (>= 9.0.5)
- Microsoft.Extensions.Hosting (>= 9.0.5)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Baubit.DI:
| Package | Downloads |
|---|---|
|
Baubit.DI.Extensions
Extensions for Baubit.DI |
|
|
Baubit.DI.Autofac
Autofac support for Baubit.DI. |
GitHub repositories
This package is not used by any popular GitHub repositories.