Bearington
Bearington

Reputation: 112

Ninject Error with multiple bindings

I have an MVC4 app that uses reflection to load controllers at run time. These controllers as well as the main app use Ninject to inject things into the constructors.

Each dynamic controller maintains a list of all the bindings it needs and stores them as a Ninject module that the main app loads at run time.

I'm having issues at the moment where multiple dynamic controllers contain the same bindings. I want the dynamic controllers to be self contained so i don't want to remove the bindings from inside the controller projects and i don't really want to have to parse a txt or xml document to read all the bindings.

Is there a way to remove duplicate bindings or tell Ninject to use the first binding it comes across if there is more than one.

Loading all the referenced assmblies bindings

public static StandardKernel LoadNinjectKernel(IEnumerable<Assembly> assemblies)
{
    var kernel = new StandardKernel();

    foreach (var asm in assemblies)
    {
       asm
       .GetTypes()
       .Where(t =>
              t.GetInterfaces()
                   .Any(i =>
                       i.Name == typeof(INinjectBootstrapper).Name))
            .ToList()
            .ForEach(t =>
            {
                var ninjectModuleBootstrapper =
                    (INinjectBootstrapper)Activator.CreateInstance(t);


                kernel.Load(ninjectModuleBootstrapper.GetModules());
            });
    }

    return kernel;
}

Binding Class

public class NinjectBindings : Ninject.Modules.NinjectModule
{
    public override void Load()
    {
        Bind<IDMSService>().To<DMSService>();
        Bind<ICaseManagerRepo>().To<CaseManagerRepo>();
    }
}

Controller Factory

 protected override IController GetControllerInstance(System.Web.Routing.RequestContext requestContext, Type controllerType)
    {
        if (controllerType != null)
        {                
            return (IController)kernel.Get(controllerType);
        }
        else
        {               
            return base.GetControllerInstance(requestContext, controllerType);
        }                           
    }

Upvotes: 3

Views: 1362

Answers (2)

InteXX
InteXX

Reputation: 6367

It's over ten years later by now, but perhaps this can help someone with a similar problem.

I was able to avoid duplicate registrations using this extension method:

public IBindingToSyntax<T> TryBind<T>(this IKernel instance)
{
    IBindingToSyntax<T> builder;
    IBinding binding;
    Type type;

    type = typeof(T);
    binding = instance.GetBindings(typeof(T)).FirstOrDefault;

    if (binding == null)
    {
        binding = new Binding(type);
        instance.AddBinding(binding);
    }

    builder = new BindingBuilder<T>(binding, instance, type.Format);

    return builder;
}

Usage:

container.TryBind(Of IDotNetService).To(Of DotNetService)();

Caution: this doesn't work with named bindings, as we can't know at this point in the call chain that we're going to be using a named binding.

-- EDIT --

I added an overload that enables the use of named bindings:

public IBindingToSyntax<T> TryBind<T>(this IKernel instance)
{
    return instance.TryBind<T>(string.Empty);
}


public IBindingToSyntax<T> TryBind<T>(this IKernel instance, string name)
{
    Func<IBinding, bool> predicate;
    IBindingToSyntax<T> builder;
    IBinding binding;
    Type type;

    type = typeof(T);

    if (string.IsNullOrEmpty(name))
        predicate = Binding => true;
    else
        predicate = Binding => Binding.Metadata.Name == name;

    binding = instance.GetBindings(type).FirstOrDefault(predicate);

    if (binding == null)
    {
        binding = new Binding(type);
        instance.AddBinding(binding);
    }

    builder = new BindingBuilder<T>(binding, instance, type.Format);

    return builder;
}

Usage:

container.TryBind(Of IDotNetService)("MyServiceName").To(Of DotNetService)();

This comes in handy when registering a list of subtypes, e.g. when you don't know their explicit types at design time:

foreach (var type in BasePager.Pagers)
    Container.TryBind<BasePager>(type.FullName).To(type).InSingletonScope.Named(type.FullName);

It might not cover all scenarios, but it should cover most.

Upvotes: 0

BatteryBackupUnit
BatteryBackupUnit

Reputation: 13233

No, there is not.

You could try and see if using IBindingRoot.Rebind instead of Bind suits your need. However i would strongly advise against it, since it does not work with:

  • Multi-Binding
  • Conditional Binding when the condition changes
  • adding arguments, OnActivation/OnDeactivation,.. to the binding when they change
  • It is not thread safe

And even if you get it to work, when you have any conditions, arguments, OnActivation/OnDeactivation, you'll end up with a lot of code duplication and issues which are hard to pinpoint (whether stuff works will depend on module loading sequence). It's really not the way to go.

Instead, you can do what one always does: Eliminate duplication. Move bindings which you need at multiple places to their own module. Create some type (lets call it ControllerModules) which specifies which modules are needed for one controller. Instead of loading all modules, find all ControllerModules, read the modules from them, and load all of these modules.

In pseudo code this could look like:

IEnumerable<Type> modulesToLoad = Assemblies.InPath(...)
          .SelectAllClasses()
          .InheritingFrom<ControllerModules>
          .SelectMany(x => x.RequiredModules)
          .Distinct();
IKernel.Load(modulesToLoad);

Upvotes: 2

Related Questions