Ninject: auto binding, contextual binding, and concurrency

Auto type binding

Ninject is a popular dependency injection container used to achieve Inversion of control (IOC) by decoupling C# types. Types are registered to the Ninject kernel and at run time, instances of a type are “injected” based on the registered dependency.

In large projects, type registration can be separated into different Ninject modules. A Ninject module is a special type that C# class that the Ninject library recognizes and is able to load dependencies that are registered in the module instance.

Below is an example of code that automatically scans assembly files (.dll files) for Ninject module instances and registering the module dependencies with the Ninject kernel:

public class Program
{
    static void Main()
    {
        var container = new NinjectDIContainer();
        container.RegisterServices("AssemblyName such as AssemblyA");
        container.RegisterServices("AssemblyName such as AssemblyB");
    }
}

public partial class NinjectDIContainer
{
    private readonly IKernel _Kernel;
    public NinjectDIContainer(IKernel kernel) { this._Kernel = kernel; }
    public NinjectDIContainer() : this(new StandardKernel()) { }
    public void RegisterServices(string assemblyName)
    {
        try
        {
            var assemblyToLoad = Assembly.Load(assemblyName);
            _Kernel.Load(assemblyToLoad);
        }
        catch (Exception ex)
        {
        }
    }
}

public class SomeClassInAssemblyA : NinjectModule
{
    public SomeClass() : base() { }
    public override void Load()
    { // kernel binding code
    }
}

public class SomeClassInAssemblyB : NinjectModule
{
    public SomeClass() : base() { }
    public override void Load()
    { // kernel binding code
    }
}

Below is another example of loading dependencies:

public partial class NinjectDIContainer
{
    public void RegisterServices(string[] filters)
    {
        try
        {
            var x = this.GetAllDllByDirectory();
            var y = this.GetFilteredNinjectModules(x.ToArray(), filters);
            foreach (var module in this.LoadDllIntoAssembly(y.ToArray()))
            {
                this._Kernel.Load(module);
            }
        }
        catch (Exception exception) { }
    }

    protected IEnumerable<string> GetAllDllByDirectory()
    {
        return Directory.GetFiles(DllRootPath, "*.dll", SearchOption.AllDirectories);
    }

    protected IEnumerable<string> GetFilteredNinjectModules(string[] dllToLoad, string[] filters)
    {
        return dllToLoad.Where(dll =>
        {
            for (int x = 0; x < filters.Length; x++)
            {
                if (dll.Contains(filters[x])) { return true; }
            }

            return false;
        });
    }

    protected IEnumerable<Assembly> LoadDllIntoAssembly(string[] asmFilePaths)
    {
        foreach (var asmPath in asmFilePaths)
        {
            yield return Assembly.LoadFrom(asmPath);
        };
    }
}

Contextual binding

Below shows how to configure type binding dependent on context.

public class SomeClass : NinjectModule
{
    public SomeClass() : base() { }
    public override void Load()
    {
        this.Kernel.Bind().To & gt; BarcodeFactory & lt; ().InSingletonScope();
        this.Kernel.Bind().To & gt; UPCAEncoder & lt; ().WhenInjectedExactlyInto & gt; BarcodeFactory & lt; ().Named("UPCA");
        this.Kernel.Bind().To & gt; EAN13Encoder & lt; ().WhenInjectedExactlyInto & gt; BarcodeFactory & lt; ().Named("EAN13");
    }
}
public class BarcodeFactory : IBarcodeFactory
{
    public BarcodeFactory(IList barcodeEncoders)
    {
    }
}

source – Other examples

Type resolution and concurrency

When using Ninject or any other IOC containers, every thread should get its own object graph. This means all instances injected by the IOC container should be new instances and not be shared across different processes. The IOC container should be queried again per thread. (source)

public class Service
{
    private IItemProcessor processor;
    public SomeService(IItemProcessor processor) {
        this.processor = processor;
    }

    public void DoStuff() {
        this.processor.Process(new { "1", "2" });
    }
}
public class ItemProcessor : IItemProcessor
{
    private IKernel container;
    public ItemProcessor(IKernel container) {
        this.container = container;
    }

    public void Process(string[] items) {
        Parallel.Foreach(items, item => {
            // new instance with unique object graph
            var other = this.container.Get<IOtherType>(); 
            other.DoWork(item);
        });    
    }
}

Service locator anti-pattern

Service locator pattern is a known design pattern used to encapsulate class. It attempts to replace referencing of types in a class with the service locator instance. This causes all other types/classes needing a dependency to the service locator type/class.

One main downside of the Service Locator implementations is that it hides binding issues at compile time. Making it likely to run into run-time errors instead of compile-time errors.

For more details about why Service Locator is an anti-pattern see the below article (source).

Leave a Reply

Your email address will not be published. Required fields are marked *