Baubit.DI 2025.51.3

dotnet add package Baubit.DI --version 2025.51.3
                    
NuGet\Install-Package Baubit.DI -Version 2025.51.3
                    
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="Baubit.DI" Version="2025.51.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Baubit.DI" Version="2025.51.3" />
                    
Directory.Packages.props
<PackageReference Include="Baubit.DI" />
                    
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 Baubit.DI --version 2025.51.3
                    
#r "nuget: Baubit.DI, 2025.51.3"
                    
#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 Baubit.DI@2025.51.3
                    
#: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=Baubit.DI&version=2025.51.3
                    
Install as a Cake Addin
#tool nuget:?package=Baubit.DI&version=2025.51.3
                    
Install as a Cake Tool

Baubit.DI

CircleCI codecov<br/> NuGet NuGet <br/> .NET Standard 2.0 License: MIT<br/> Known Vulnerabilities

Autofac Extension: Baubit.DI.Autofac

Modularity framework for .NET with configuration-driven module composition.

Table of Contents

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 IHostApplicationBuilder via 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:

  1. Components from componentsFactory are loaded first
  2. Modules from appsettings.json modules section 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 IServiceProviderFactory or IServiceProviderFactory<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.ModuleRegistry only 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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.

Version Downloads Last Updated
2025.51.3 1,502 12/19/2025
2025.49.7 276 12/7/2025
2025.49.6 241 12/6/2025
2025.49.2 1,141 12/2/2025
2025.49.1 675 12/2/2025