Search code examples
c#ninjectninject-extensionsninject-conventions

Ninject dynamically bind to implementation


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.


Solution

  • 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));
    }