Skip to content

Registration

Jim Burlison edited this page Dec 5, 2025 · 1 revision

Registration

This guide covers all the ways to register services with SSDI.

Basic Registration

Registering Concrete Types

The simplest registration maps a type to itself:

container.Configure(c =>
{
    c.Export<MyService>();
});

var service = container.Locate<MyService>();

Registering with Non-Generic API

Use the non-generic overload for runtime type registration:

Type serviceType = typeof(MyService);

container.Configure(c =>
{
    c.Export(serviceType);
});

object service = container.Locate(serviceType);

Interface Registration

Single Implementation

Map an implementation to its interface:

container.Configure(c =>
{
    c.Export<SqlRepository>().As<IRepository>();
});

var repo = container.Locate<IRepository>(); // Returns SqlRepository

Multiple Interfaces

A single type can be registered as multiple interfaces:

container.Configure(c =>
{
    c.Export<UniversalService>()
        .As<IReader>()
        .As<IWriter>()
        .As<IProcessor>();
});

var reader = container.Locate<IReader>();
var writer = container.Locate<IWriter>();
// Both return the same type, but instances depend on lifetime

Multiple Implementations

Register multiple implementations of the same interface:

container.Configure(c =>
{
    c.Export<AuthHandler>().As<IPacketHandler>();
    c.Export<GameHandler>().As<IPacketHandler>();
    c.Export<ChatHandler>().As<IPacketHandler>();
});

// Resolve single (returns first registered)
var handler = container.Locate<IPacketHandler>(); // Returns AuthHandler

// Resolve all implementations
var allHandlers = container.Locate<IEnumerable<IPacketHandler>>();
// Returns: [AuthHandler, GameHandler, ChatHandler]

Instance Registration

Register pre-built instances as singletons:

var config = new Configuration
{
    ServerName = "GameServer",
    Port = 8080
};

container.Configure(c =>
{
    c.ExportInstance(config).As<IConfiguration>();
});

var resolvedConfig = container.Locate<IConfiguration>();
// Returns the exact instance you registered

Use Cases for Instance Registration

  • Configuration objects loaded from files
  • External dependencies created outside the container
  • Mock objects for testing
  • Shared resources initialized before container setup
// Real-world example
var logger = new FileLogger("game.log");
var settings = JsonSerializer.Deserialize<GameSettings>(File.ReadAllText("settings.json"));

container.Configure(c =>
{
    c.ExportInstance(logger).As<ILogger>();
    c.ExportInstance(settings).As<GameSettings>();
});

Dynamic Registration

Runtime Type Registration

Register types discovered at runtime:

// Register all types implementing an interface from an assembly
var assembly = Assembly.GetExecutingAssembly();

container.Configure(c =>
{
    foreach (var type in assembly.GetTypes()
        .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract))
    {
        c.Export(type).As<IPlugin>();
    }
});

var plugins = container.Locate<IEnumerable<IPlugin>>();

Plugin System Example

public void LoadPlugins(string pluginDirectory)
{
    foreach (var dll in Directory.GetFiles(pluginDirectory, "*.dll"))
    {
        var assembly = Assembly.LoadFrom(dll);

        container.Configure(c =>
        {
            foreach (var type in assembly.GetTypes()
                .Where(t => typeof(IGamePlugin).IsAssignableFrom(t)))
            {
                c.Export(type).As<IGamePlugin>();
            }
        });
    }
}

Multiple Configure Calls

You can call Configure multiple times. This is useful for:

  • Modular registration
  • Plugin loading
  • Conditional registration
// Core services
container.Configure(c =>
{
    c.Export<GameEngine>().Lifestyle.Singleton();
    c.Export<InputManager>().Lifestyle.Singleton();
});

// Feature-specific services
container.Configure(c =>
{
    c.Export<InventorySystem>();
    c.Export<CraftingSystem>();
});

// Optional services based on configuration
if (enableMultiplayer)
{
    container.Configure(c =>
    {
        c.Export<NetworkManager>().Lifestyle.Singleton();
        c.Export<PlayerSync>();
    });
}

Registration with Lifetimes

Combine registration with Lifetimes:

container.Configure(c =>
{
    // Transient (default) - new instance each time
    c.Export<Projectile>();

    // Singleton - single shared instance
    c.Export<GameEngine>().Lifestyle.Singleton();

    // Scoped - one per scope
    c.Export<PlayerInventory>().Lifestyle.Scoped();

    // Interface with lifetime
    c.Export<SqlRepository>()
        .As<IRepository>()
        .Lifestyle.Singleton();
});

Registration with Parameters

Provide constructor parameters at registration time:

container.Configure(c =>
{
    // By type
    c.Export<GameServer>()
        .WithCtorParam<int>(8080)
        .WithCtorParam<string>("MyServer");

    // By name
    c.Export<DatabaseConnection>()
        .WithCtorParam("connectionString", "Server=localhost;Database=game");

    // By position
    c.Export<NetworkClient>()
        .WithCtorParam(0, "192.168.1.1")
        .WithCtorParam(1, 9999);

    // Multiple positional
    c.Export<ComplexService>()
        .WithCtorPositionalParams("param1", 42, true, 3.14);
});

See Parameter Injection for more details.

Checking Registration

IsRegistered

Check if a type is registered before resolving:

if (container.IsRegistered<ILogger>())
{
    var logger = container.Locate<ILogger>();
    logger.Log("Service found!");
}

// Non-generic version
bool isRegistered = container.IsRegistered(typeof(ILogger));

Conditional Registration

container.Configure(c =>
{
    c.Export<DefaultLogger>().As<ILogger>();
});

// Later, conditionally add specialized logger
if (!container.IsRegistered<IFileLogger>())
{
    container.Configure(c =>
    {
        c.Export<FileLogger>().As<IFileLogger>();
    });
}

Registration Best Practices

1. Register Interfaces, Not Concrete Types

// ✅ Good - decoupled
c.Export<SqlRepository>().As<IRepository>();

// ⚠️ Okay for internal services
c.Export<GameEngine>();

// ❌ Avoid - tight coupling
var repo = container.Locate<SqlRepository>();

2. Use Appropriate Lifetimes

// Singleton for shared state
c.Export<GameState>().Lifestyle.Singleton();

// Transient for stateless services
c.Export<DamageCalculator>();

// Scoped for per-request/per-player
c.Export<PlayerSession>().Lifestyle.Scoped();

3. Group Related Registrations

// Core module
void RegisterCoreServices(DependencyInjectionContainer container)
{
    container.Configure(c =>
    {
        c.Export<GameEngine>().Lifestyle.Singleton();
        c.Export<InputManager>().Lifestyle.Singleton();
        c.Export<AudioManager>().Lifestyle.Singleton();
    });
}

// Combat module
void RegisterCombatServices(DependencyInjectionContainer container)
{
    container.Configure(c =>
    {
        c.Export<CombatSystem>();
        c.Export<DamageCalculator>();
        c.Export<SkillManager>().As<ISkillManager>();
    });
}

4. Register Dependencies Before Dependents

container.Configure(c =>
{
    // Dependencies first
    c.Export<Logger>().As<ILogger>();
    c.Export<Database>().As<IDatabase>();

    // Then services that depend on them
    c.Export<UserRepository>().As<IUserRepository>();
    c.Export<UserService>().As<IUserService>();
});

Next Steps

Clone this wiki locally