5

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.

Nigel
  • 2,961
  • 1
  • 14
  • 32
DiskJunky
  • 4,750
  • 3
  • 37
  • 66
  • IMO, the answers below are all implementing some form of factory, which is a proper answer. Inject the factory, let the factory return the proper IExport. Despite some of the (insightful) comments in the answers below, having this in a factory helps insulate you from ninject specific features. – Trevor Ash Sep 07 '15 at 14:27
  • @Atoms, ninject is somewhat required here as the export implementations require other services in their ctors that are already bound via the ninject kernel. Makes sense to re-use them rather than implementing a specific factory – DiskJunky Sep 07 '15 at 14:53
  • 1
    Using the two-part convention, which binding was missing? Also note, that with the two-part convention, you would have bound every `IExport` twice. For the the first convention you should have excluded all `IExport`s. I'd suggest you create a new question in regards on how to form the question. Maybe another SE platform like codereview or programmers would be better. **Suggestion**: Why not have a convention for all types ending in `Service`, and specifically bind all the rest? Furthermore, if there's more specific bindings they can be put into `NinjectModule`s in the specific assemblies. – BatteryBackupUnit Sep 07 '15 at 16:32
  • @BatteryBackupUnit, I would if I knew how :) My ninject is at n00b level. I did see the duplicate bindings but filtered them out when populating the dictionary. How do I exclude the IExport in the original binding? As for creating a rule for services, I also have ...Reader and ...Writer but I guess those could be configured too. The trick is 'how'. Ninject documentation isn't the best and it's tricky to work out what you need to solve a given problem. Hence this question in the first place – DiskJunky Sep 07 '15 at 16:37
  • @yeah i understand. however, the questions regarding the contextual binding for `IExport`and how to design conventional bindings should be separated. "how to design" the conventional bindings is not a good fit for SO. Concrete question => how to exclude a type is a good fit for SO. Answer: you can use the `Where`filter-method to exclude all types which implement `IExport`. For some more info on conventions also see [here](http://stackoverflow.com/a/19288554/684096) – BatteryBackupUnit Sep 07 '15 at 20:47
  • @BatteryBackupUnit, fair point, I'll extract and re-post on conventions. I've tagged your reply as the answer as it did get me to a working solution. Thanks for all the help! – DiskJunky Sep 08 '15 at 08:45

2 Answers2

3

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));
}
BatteryBackupUnit
  • 12,934
  • 1
  • 42
  • 68
  • good implementation with the virtue of being easier to understand than Steve's. However, the binding fails for me trying to instantiate the dictionary when I started adding other interfaces/the PADBEntities object to the export implementation ctors. Any ideas? – DiskJunky Sep 07 '15 at 14:55
  • This (most likely) means not all types required by some `IExport` are bound. Please notice that in my answer i changed the convention binding for the types of the `PartnerAdapter.*.dll`s. You may want to change this back to what you originally had or make a more appropriate convention / binding. I did the change because i don't recommend this kind of very broad convention. Rather, I'd have the dll's implement some `NinjectModule`s and load these with `kernel.Load`. In case my suspicion is wrong, please post the entire exception (type, message, stacktrace, potential inner exceptions..) – BatteryBackupUnit Sep 07 '15 at 15:11
  • I figured something as much but the binding selection that you have in your example breaks the existing binding that I had in the OP - it no longer can see the IService references in the ctors that I had already set up. Error: `Error activating ISomeEntityService. No matching bindings are available and the type is not self-bindable`. This binding works as specified in the original post but not when adding your binding for IExport – DiskJunky Sep 07 '15 at 15:19
  • I've updated my question with an ugly but working binding solution. Can you suggest any improvement? – DiskJunky Sep 07 '15 at 16:19
2

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)));
Steven
  • 166,672
  • 24
  • 332
  • 435
  • Effective but problematic - there are potentially a large number of partners using an unknown final number of export implementations. Your solution would require a new constructor argument and an update of an `if` or `switch` every time we wanted to add a new implementation. Still, I hadn't considered this approach – DiskJunky Sep 07 '15 at 11:56
  • @DiskJunky: My solution does not require a new ctor argument; this is just a simple example. Adapt the solution to your particular case. If you have many assemblies, you might want to inject a dictionary with a mapping instead. – Steven Sep 07 '15 at 12:09
  • For some reason, we as developers (including myself), are tempted in trying to solve all are problems using the tools we use, while some problems are not for the tools to solve. If we take a step back and and look at the problem as if we don't use a tool at all, it often results in a cleaner and more practical solution. – Steven Sep 07 '15 at 12:12
  • @Steven: How would that dictionary look like? How would it be maintained? How would it be verified? Is it still worth it if there are, let's say a 100 entries? – BatteryBackupUnit Sep 07 '15 at 12:33
  • @BatteryBackupUnit: How would you reflect over a set of assemblies in .NET and fill corresponding types in a dictionary? The exact answer of course depends on the exact requirements of the OP, but a solution would be trivial. With Ninject, you can even (ab)use the Kernel as dictionary; see my update. – Steven Sep 07 '15 at 12:38
  • The idea of constructing the whole object graph at once conflicts with the idea of `InRequestScope`. If there needs to be `InRequestScope` it's always going to be tough to do. Building the entire object graph on every request (with all possible `IExport`s does not really seem such a good idea - performance wise. The optimization would then have to try to replace `InRequestScope` by constructing `DbContext` when needed and passing it around (instead of injecting it `InRequestScope`) – BatteryBackupUnit Sep 07 '15 at 13:06
  • @Steven, very interesting approach! I'll have to give this a go to see if it works - I'm a little unsure whether that late call to kernel.Bind() in the lambda will resolve correctly but it's well worth an attempt at implementing – DiskJunky Sep 07 '15 at 13:07
  • @BatteryBackupUnit, in this case, we can ignore InRequestScope() - thankfully it's not needed for the export. I'd simply copy/pasted from the existing binding implementation. I'll remove from post as an unnecessary complication – DiskJunky Sep 07 '15 at 13:09
  • @BatteryBackupUnit: Although my preference is to have all components that are part of the object graph registered as singleton, that's not what I tried to discuss here and that's not what I was talking about. What I meant here was that the container should know upfront which *registration/binding* should be injected into a consumer. That particular binding can still have a scoped lifestyle which results in multiple instances. Even with multiple instances, the exact shape of the object graph is known at compose time. – Steven Sep 07 '15 at 13:20
  • Ok i get the point. But isn't the `ExportProxy` quite superfluous? One can just use `Func exportProvider` directly. Of course, if it would be used at several places i'd also extract the logic into the `ExportProxy`. If the `IExport` has multiple methods / arguments (which are not required for deciding which actual export to use), though, then i would rather have an `ExportProvider` instead of a proxy (which basically then is the same as the answer i provided). – BatteryBackupUnit Sep 07 '15 at 13:29
  • 1
    @BatteryBackupUnit: For me it's all about hiding implementation details for the consumer. Having a `Func` or `ExportDictionary` injected into consumers makes them aware of the existence of multiple implementations and a consumer gets an extra concept/abstraction to work with. But in the end, it depends on context, because with my solution the OP might be required to change the `IExport` interface. Although the changed abstraction might be better, but if changing is not an possible, you'll again end up with an extra factory/provider like abstraction like your `IExportDictionary`. – Steven Sep 07 '15 at 13:35