Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ninject: Choosing the wrong constructor

I have an ASP.NET MVC 3 application with Ninject v2.2.1.4. Everything was working great and then suddenly we started seeing Ninject attempting to create our DbContext using a constructor with a parameter over the parameterless constructor. Here are the bindings:

kernel.Bind<MyContext>().ToSelf().InRequestScope();

kernel.Bind<IUnitOfWork>().ToMethod(ctx => ctx.Kernel.Get<MyContext>());
kernel.Bind<DbContext>().ToMethod(ctx => ctx.Kernel.Get<MyContext>());

The MyContext is a DbContext object that implements the IUnitOfWork interface as well. I have set it up this way so the same context is injected into multiple repositories that are used in a single request. The MyContext constructors look like this:

public MyContext() { }
public MyContext(string connectionString) { }
public MyContext (long accountID) { }
public MyContext (Connection connection) { }

There are different constructors for different applications as they all use the same MyContext class. Looking at the bindings you would think when a MyContext class was requested that the parameterless constructor would be called but for whatever reason, it is not. The one with the long accountID parameter is called even though no accountID is being specified. This obviously throwns and exception statement that "No matching bindings are available, and the type is not self-bindable" It actually throws the exception when trying to generate a IUnitOfWork.

If I comment out the last three constructors everything works fine and the parameterless constructor is used. If I comment out any two of the parameterized constructors it tries to use the other and not the parameterless one.

The suggestions provided by Ninject are:

Suggestions:
  1) Ensure that you have defined a binding for long.
  2) If the binding was defined in a module, ensure that the module has been loaded into the kernel.
  3) Ensure you have not accidentally created more than one kernel.
  4) If you are using constructor arguments, ensure that the parameter name matches the constructors parameter name.
  5) If you are using automatic module loading, ensure the search path and filters are correct.

We don't have anything for 1 as we don't want to. I'm not sure what 2 and 5 mean. I do not believe we have done 3 and we are not doing 4.

Any thoughts as to why it wouldn't use the parameterless constructor in this scenario.

like image 708
Nick Olsen Avatar asked Oct 01 '12 23:10

Nick Olsen


2 Answers

@Xander's answer is right in general but Ninject has some very specific solutions in V3. Ninject scores constructors by a specific algorithm which is to find the one with the most parameters it knows how to resolve as documented in this wiki article [which claims to be for V2.4, which was actually badged 3.0]. See the code. I think this is also on the wiki. If it's not, someone should put it there.

RE the change in behavior you've seen, the chances are either Implicit Self Binding is changing the goalposts (new registrations are being added during resolution) or you've added a Binding that has made one of the other constructors more attractive.

The [Inject] attribute trumps all other criteria which is what you're after (although you don't actually want to have container specific attributes in your code).

The WithConstructorArgument technique suggested is actually effected by using ToConstructor - doing a WCA will not influence the selection (and I reckon you won't get complaints about the redundant specifications.

The real bottom line is that you should never end up in as big a mess as this as alluded to in @Mark Seemann's comment on this related question.


Sadly, the above is all a lie. If you move off v2.2, this answer will become correct. If you can't or won't, you need to look at the equivalent source and tests to find out the rules from before that (from memory (and some google code that appeared in search results in my research), it was based on the constructor count, but not sure how equal scores are disambiguated.

Pretty sure that in 2.2, adding an [Inject] is the quick way out.

like image 99
Ruben Bartelink Avatar answered Sep 22 '22 17:09

Ruben Bartelink


By default Ninject, along with other similar IoC frameworks, chooses the constructor with the most parameters. Specify which constructor to use during the initialization by the WithConstructorArgument extension method.

kernel.Bind<DbContext>()
      .WithConstructorArgument("connectionString",
             ConfigurationManager.ConnectionStrings["connection"]
                  .ConnectionString)
      .ToMethod(ctx => ctx.Kernel.Get<MyContext>());

To force Ninject to use the default constructor place the [Inject] attribute on the constructor:

[Inject]
public MyContext() { }
like image 23
Alex Avatar answered Sep 22 '22 17:09

Alex