Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Fluent Binder API

Namespace: SimplEnteiner.Core.Binder.Interfaces (public contracts), SimplEnteiner.Core.Binder.Implementations (internal implementations) Sources: Core/Binder/Interfaces/, Core/Binder/Implementations/

This is the primary public API surface used to register services. Every step returns a narrower interface, guiding the caller through: targetimplementationlifetimeoptionsapply.

flowchart LR
    A["IBinder.Bind<T>()"] --> B["IBindingTo<T>"]
    B -->|To/ToSelf/ToMethod/ToInstance| C["IBindingLifetime<T>"]
    C -->|AsSingle/AsTransient/AsCached/AsScoped| D["IBindingOptions<T>"]
    D -->|WithArguments/WhenInjectedInto/WithId/OnActivation/OnRelease| D
    D -->|Apply| E[Registered in Registry]

    A2["IBinder.Decorate<T>()"] --> F["IBindingDecorate<T>"]
    F -->|With<TDecorator>order| G["IBindingDecorateLifetime<T>"]
    G -->|AsSingle/AsTransient/AsCached/AsScoped| H[Registered as decorator]

IBinder

public interface IBinder
{
    IBindingTo<T> Bind<T>();
    IBindingTo Bind(Type interfaceType);
    IBindingDecorate<TService> Decorate<TService>();
    IBindingDecorate Decorate(Type interfaceType);
    void BindConvention(Action<IConventionBuilder> configure);
}

Implemented by DIContainer and Scope. This is the interface consumers primarily interact with — the entry point of every binding.

IBindingTo<TInterface> / IBindingTo

public interface IBindingTo<TInterface>
{
    IBindingLifetime<TInterface> To<TImplementation>() where TImplementation : TInterface;
    IBindingLifetime<TInterface> ToSelf();
    IBindingLifetime<TInterface> ToMethod(Func<TInterface> factory);
    IBindingLifetime<TInterface> ToInstance(TInterface instance);
}
MethodBehavior
To<TImplementation>()Binds TInterface to a concrete TImplementation. Compile-time constrained: TImplementation : TInterface.
ToSelf()Binds TInterface to itself as implementation (only valid/useful when TInterface is itself instantiable, e.g. binding a concrete class to itself).
ToMethod(Func<TInterface>)Binds to a factory delegate invoked on every resolution requiring a new instance (subject to lifetime caching). Throws ArgumentNullException if factory is null.
ToInstance(TInterface instance)Binds to an already-constructed instance. Internally forces the registration's lifetime to Singleton regardless of a later .AsX() call (see Registry.CreateRegistration: if (instance != null) lifeTime = LifeTime.Singleton;). Throws ArgumentNullException if instance is null.
container.Bind<IGreeter>().To<Greeter>().AsSingle().Apply();
container.Bind<IClock>().ToMethod(() => new SystemClock(DateTimeOffset.UtcNow)).AsTransient().Apply();
container.Bind<IConfig>().ToInstance(new AppConfig { Env = "prod" }).AsSingle().Apply(); // instance forces Singleton anyway

The non-generic IBindingTo mirrors all four members using Type/object instead of generics — useful for reflection-driven or convention-based registration.

IBindingLifetime<TInterface> / IBindingLifetime

public interface IBindingLifetime<TInterface>
{
    IBindingOptions<TInterface> AsSingle();
    IBindingOptions<TInterface> AsTransient();
    IBindingOptions<TInterface> AsCached();
    IBindingOptions<TInterface> AsScoped();
}

Maps directly to the four LifeTime values. See Scopes, Lifetimes and Disposal for exact semantics of each.

IBindingOptions<TInterface> / IBindingOptions

public interface IBindingOptions<TInterface>
{
    IBindingOptions<TInterface> WithArguments(params object[] args);
    IBindingOptions<TInterface> WhenInjectedInto<T>();
    IBindingOptions<TInterface> WithId(object id);
    IBindingOptions<TInterface> OnActivation(Action<TInterface> onActivation);
    IBindingOptions<TInterface> OnRelease(Action<TInterface> onRelease);
    void Apply();
}
MethodBehavior
WithArguments(params object[] args)Supplies extra constructor arguments that are matched by assignable type against the implementation's constructor parameters (see Resolver.ResolveConstructorWithArguments); parameters not satisfied by an argument are resolved normally from the container.
WhenInjectedInto<T>()Registers this binding as conditional: it is only used when the interface is being injected as a dependency of T (matched via ResolutionContext.RequestType during constructor/member parameter resolution). Mutually exclusive in practice with WithId — internally both route into the same ConditionalBindings dictionary keyed by (InterfaceType, Id ?? ConditionType).
WithId(object id)Registers this binding as conditional, keyed by an explicit id object instead of a consumer type. Resolved via Resolve<T>(id)/Resolve(Type, id) or automatically when a constructor parameter/field/property is marked with [Id(id)] (see Attributes and Delegates).
OnActivation(Action<TInterface>)Callback invoked immediately after the instance is constructed and member-injected (before being returned to the caller). Runs on every activation (i.e. every time a new instance is created, not on cache/singleton/scoped hits).
OnRelease(Action<TInterface>)Callback invoked when the instance is disposed by the owning CleanupService/RepositoryService (invoked before Dispose()/DisposeAsync() is called on the instance itself, if it implements IDisposable/IAsyncDisposable).
Apply()Finalizes the staged builder (BindingBuilder.ExecuteAllStages()) and registers it into the target's Registry. Required unless Build() is called later (which flushes any un-applied pending bindings automatically).
container.Bind<IRepository>()
    .To<SqlRepository>()
    .AsScoped()
    .WithArguments("Server=.;Database=App;")
    .OnActivation(r => Console.WriteLine($"{r.GetType().Name} activated"))
    .OnRelease(r => Console.WriteLine($"{r.GetType().Name} released"))
    .Apply();

IBindingDecorate<TInterface> / IBindingDecorate

public interface IBindingDecorate<TInterface>
{
    IBindingDecorateLifetime<TInterface> With<TImplementation>(int? order = null) where TImplementation : TInterface;
}

Starts a decorator registration for TInterface. order controls the wrapping order when multiple decorators target the same interface (lower order wraps first / closer to the base implementation — decorators are applied in ascending Order, see Registry.AddDecorator's binary-insert-by-order logic). If order is omitted, it defaults to 0 for the first decorator and previous.Order + 1 for subsequent ones.

For the non-generic IBindingDecorate.With(Type implementation, int? order = null), BindingDecorate.Validate enforces that implementation is actually compatible with the target interface (handles exact, closed-generic, and open-generic-definition interface shapes), throwing InvalidOperationException otherwise.

IBindingDecorateLifetime<TInterface> / IBindingDecorateLifetime

public interface IBindingDecorateLifetime<TInterface>
{
    void AsSingle();
    void AsTransient();
    void AsCached();
    void AsScoped();
}

Unlike the main binding pipeline, each of these methods implicitly applies the registration (Apply() is called internally right after SetLifetime) — there is no separate .Apply() step for decorators. See Decorators for full semantics including generic decorator resolution.

container.Decorate<IGreeter>().With<LoggingGreeterDecorator>(order: 0).AsTransient();
container.Decorate<IGreeter>().With<CachingGreeterDecorator>(order: 1).AsSingle();

BindingBuilder (internal engine, public class)

Namespace: SimplEnteiner.Core.Binder Source: Core/Binder/BindingBuilder.cs

While consumers never construct a BindingBuilder directly (it is created internally by Bind<T>()/Decorate<T>()), it is a public class because it flows through internal-visible integration points and is useful to understand for advanced scenarios (custom IScopeFactory/IRegistryFactory implementations, diagnostics). Key public members:

public Type InterfaceType { get; }
public Type ImplementationType { get; }
public Func<object> FactoryMethod { get; }
public object Instance { get; }
public LifeTime LifeTime { get; }
public List<object> Arguments { get; }
public Type ConditionType { get; }
public object Id { get; }
public int? Order { get; }
public Action<object> OnActivation { get; }
public Action<object> OnRelease { get; }
public bool IsComplete { get; }     // true once the FinalStage has been reached
public bool IsRegistered { get; }   // true once registered in a Registry (prevents double-registration)

Its Set* methods (SetImplementation, SetFactoryMethod, SetInstance, SetLifetime, SetCondition, SetId, SetOnActivation, SetOnRelease, AddDecorator) are the low-level operations that the fluent Binder.Implementations classes call; each transition is validated against the internal BuilderStateMachine (see Design Patterns → Fluent Builder + Finite State Machine) and throws InvalidOperationException if called out of order (e.g., setting the implementation twice).

Continue to Registration and Resolution.