Ninject dynamically bind to implementation

7.4k Views Asked by At

There are several questions on Stack Overflow that are similar but not exactly what I'm looking for. I would like to do Ninject binding based on a runtime condition, that isn't pre-known on startup. The other questions on Stack Overflow for dynamic binding revolve around binding based on a config file or some such - I need to it to happen conditionally based on a database value while processing the data for a particular entity. E.g.,

public class Partner
{
    public int PartnerID { get; set; }
    public string ExportImplementationAssembly { get; set; }
}

public interface IExport
{
    void ExportData(DataTable data);
}

Elsewhere, I have 2 dlls that implement IExport

public PartnerAExport : IExport
{
    private readonly _db;
    public PartnerAExport(PAEntities db)
    {
        _db = db;
    }
    public void ExportData(DataTable data)
    {
        // export parter A's data...
    }
}

Then for partner B;

public PartnerBExport : IExport
{
    private readonly _db;
    public PartnerBExport(PAEntities db)
    {
        _db = db;
    }
    public void ExportData(DataTable data)
    {
        // export parter B's data...
    }
}

Current Ninject binding is;

public class NinjectWebBindingsModule : NinjectModule
{
    public override void Load()
    {
        Bind<PADBEntities>().ToSelf();
        Kernel.Bind(s => s.FromAssembliesMatching("PartnerAdapter.*.dll")
                          .SelectAllClasses()
                          .BindDefaultInterfaces()
                   );
    }
}

So how do I set up the bindings such that I can do;

foreach (Partner partner in _db.Partners)
{
    // pseudocode...
    IExport exportModule = ninject.Resolve<IExport>(partner.ExportImplementationAssembly);
    exportModule.ExportData(_db.GetPartnerData(partner.PartnerID));
}

Is this possible? It seems like it should be but I can't quite figure how to go about it. The existing binding configuration above works fine for static bindings but I need something I can resolve at runtime. Is the above possible or am I just going to have to bypass Ninject and load the plugins using old-school reflection? If so, how can I use that method to resolve any constructor arguments via Ninject as with the statically bound objects?

UPDATE: I've updated my code with BatteryBackupUnit's solution such that I now have the following;

Bind<PADBEntities>().ToSelf().InRequestScope();
Kernel.Bind(s => s.FromAssembliesMatching("PartnerAdapter.*.dll")
                    .SelectAllClasses()
                    .BindDefaultInterfaces()
                    .Configure(c => c.InRequestScope())
            );

Kernel.Bind(s => s.FromAssembliesMatching("PartnerAdapter.Modules.*.dll")
                    .SelectAllClasses()
                    .InheritedFrom<IExportService>()
                    .BindSelection((type, baseTypes) => new[] { typeof(IExportService) })
            );
Kernel.Bind<IExportServiceDictionary>().To<ExportServiceDictionary>().InSingletonScope();
ExportServiceDictionary dictionary = KernelInstance.Get<ExportServiceDictionary>();

Instantiating the export implementations within 2 test modules works and instantiates the PADBEntites context just fine. However, all other bindings in my services layer now no longer work for the rest of the system. Likewise, I cannot bind the export layer if I change PADBEntities variable/ctor argument to an ISomeEntityService component. It seems I'm missing one last step in configuring the bindings to get this work. Any thoughts?

Error: "Error activating ISomeEntityService. No matching bindings are available and the type is not self-bindable"

Update 2: Eventually got this working with a bit of trial and error using BatteryBackupUnit's solution though I'm not too happy with the hoops to jump thought. Any other more concise solution is welcome.

I changed the original convention binding of;

        Kernel.Bind(s => s.FromAssembliesMatching("PartnerAdapter.*.dll")
                          .SelectAllClasses()
                          .BindDefaultInterfaces()
                   );

to the much more verbose and explicit;

Bind<IActionService>().To<ActionService>().InRequestScope();
Bind<IAuditedActionService>().To<AuditedActionService>().InRequestScope();
Bind<ICallService>().To<CallService>().InRequestScope();
Bind<ICompanyService>().To<CompanyService>().InRequestScope();
//...and so on for 30+ lines

Not my favorite solution but it works with explicit and convention based binding but not with two conventions. Can anyone see where I'm going wrong with the binding?

Update 3: Disregard the issue with the bindings in Update 2. It appears that I've found a bug in Ninject relating to having multiple binding modules in a referenced library. A change in module A, even though never hit via breakpoint will break a project explicitly using a different module B. Go figure.

2

There are 2 best solutions below

4
On BEST ANSWER

It's important to note that while the actual "condition match" is a runtime condition, you actually know the possible set of matches in advance (at least on startup when building the container) - which is evidenced by the use of the conventions. This is what the conditional / contextual bindings are about (described in the Ninject WIKI and covered in several questions). So you actually don't need to do the binding at an arbitrary runtime-time, rather you just have to do the resolution/selection at an arbitrary time (resolution can actually be done in advance => fail early).

Here's a possible solution, which features:

  • creation of all bindings on startup
  • fail early: verification of bindings on startup (through instanciation of all bound IExports)
  • selection of IExport at an arbitrary runtime

.

internal interface IExportDictionary
{
    IExport Get(string key);
}

internal class ExportDictionary : IExportDictionary
{
    private readonly Dictionary<string, IExport> dictionary;

    public ExportDictionary(IEnumerable<IExport> exports)
    {
        dictionary = new Dictionary<string, IExport>();
        foreach (IExport export in exports)
        {
            dictionary.Add(export.GetType().Assembly.FullName, export);
        }
    }

    public IExport Get(string key)
    {
        return dictionary[key];
    }
}

Composition root:

// this is just going to bind the IExports.
// If other types need to be bound, go ahead and adapt this or add other bindings.
kernel.Bind(s => s.FromAssembliesMatching("PartnerAdapter.*.dll")
        .SelectAllClasses()
        .InheritedFrom<IExport>()
        .BindSelection((type, baseTypes) => new[] { typeof(IExport) }));

kernel.Bind<IExportDictionary>().To<ExportDictionary>().InSingletonScope();

// create the dictionary immediately after the kernel is initialized.
// do this in the "composition root".
// why? creation of the dictionary will lead to creation of all `IExport`
// that means if one cannot be created because a binding is missing (or such)
// it will fail here (=> fail early).
var exportDictionary = kernel.Get<IExportDictionary>(); 

Now IExportDictionary can be injected into any component and just used like "required":

foreach (Partner partner in _db.Partners)
{
    // pseudocode...
    IExport exportModule = exportDictionary.Get(partner.ExportImplementationAssembly);
    exportModule.ExportData(_db.GetPartnerData(partner.PartnerID));
}
11
On

I would like to do Ninject binding based on a runtime condition, that isn't pre-known on startup.

Prevent making runtime decisions during building of the object graphs. This complicates your configuration and makes your configuration hard to verify. Ideally, your object graphs should be fixed and should not change shape at runtime.

Instead, make the runtime decision at... runtime, by moving this into a proxy class for IExport. How such proxy exactly looks like, depends on your exact situation, but here's an example:

public sealed class ExportProxy : IExport
{
    private readonly IExport export1;
    private readonly IExport export2;
    public ExportProxy(IExport export1, IExport export2) {
        this.export1 = export1;
        this.export2 = export2;
    }

    void IExport.ExportData(Partner partner) {
        IExport exportModule = GetExportModule(partner.ExportImplementationAssembly);
        exportModule.ExportData(partner);
    }

    private IExport GetExportModule(ImplementationAssembly assembly) {
        if (assembly.Name = "A") return this.export1;
        if (assembly.Name = "B") return this.export2;
        throw new InvalidOperationException(assembly.Name);
    }
}

Or perhaps you're dealing with a set of dynamically determined assemblies. In that case you can supply the proxy with a export provider delegate. For instance:

public sealed class ExportProxy : IExport
{
    private readonly Func<ImplementationAssembly, IExport> exportProvider;
    public ExportProxy(Func<ImplementationAssembly, IExport> exportProvider) {
        this.exportProvider = exportProvider;
    }

    void IExport.ExportData(Partner partner) {
        IExport exportModule = this.exportProvider(partner.ExportImplementationAssembly);
        exportModule.ExportData(partner);
    }
}

By supplying the proxy with a Func<,> you can still make the decision at the place where you register your ExportProxy (the composition root) where you can query the system for assemblies. This way you can register the IExport implementations up front in the container, which improves verifiability of the configuration. If you registered all IExport implementations using a key, you can do the following simple registration for the ExportProxy

kernel.Bind<IExport>().ToInstance(new ExportProxy(
    assembly => kernel.Get<IExport>(assembly.Name)));