Ninject: Bind Constructor Argument to Property of Other Object
Asked Answered
A

2

14

I have an IConfig object that contains settings used throughout my application. At the moment, I inject the entire object into the constructor of each object that needs it, as follows:

public interface IConfig 
{
    string Username { get; }
    string Password { get; }
    //... other settings
}

public class Foo : IFoo
{
    private readonly string username;
    private readonly string password;

    public Foo(IConfig config)
    {
        this.username = config.Username;
        this.password = config.Password;
    }
}

The downside is that IConfig contains a large number of settings because it's deserialised from an overall config file, so injecting the entire object is unnecessary. What I'd like to do is change the constructor to Foo(string username, string password) so that it only receives the settings it needs. This also makes creating Foo objects for testing much easier (not having to set up IConfig just to create Foo). I'd like to bind the constructor arguments directly in my NinjectModule, something like the following:

public class MyModule : NinjectModule
{
    public override void Load()
    {
        Bind<IConfig>().To<JsonConfig>()
            .InSingletonScope();

        Bind<IFoo>().To<Foo>()
            .WithConstructorArgument("username", IConfig.Username)
            .WithConstructorArgument("password", IConfig.Password);
    }
}

Obviously this code doesn't work, but how would I go about doing what I wanted?

My original idea was to use the NinjectModule.Kernel to get the IKernel then get an instance of my IConfig object and inject the properties as required, but the object returned by NinjectModule.Kernel has no Get<T>() method.

Atlanta answered 18/5, 2012 at 8:30 Comment(0)
F
16

You are on the right track:

The Kernel.Get<T>() method is an extension method defined on the ResolutionExtensions in the Ninject namepsace so with adding the using Ninject; it is available in your module as well.

But instead of the Module.Kernel you should use the IContext provided in the second overload of WithConstructorArgument to get the Kernel:

Bind<IFoo>().To<Foo>()
    .WithConstructorArgument("username", 
                             context => context.Kernel.Get<IConfig>().Username)
    .WithConstructorArgument("password", 
                             context => context.Kernel.Get<IConfig>().Password);
Fridge answered 19/5, 2012 at 7:5 Comment(3)
I can't seem to get this to work. The IConfig object created in the lambda contains all default values (empty strings for those settings). If I just do a normal Kernel.Get<IConfig> now that I've added using Ninject this does work properly. Should I invest the effort to get the context version working, is there any advantage?Atlanta
It should work with context.Kernel, I will have a look into it. How the values in IConfig gets pupulated? Are you using multiple Kernels?Fridge
Just created a simple test project and this does work. Must be something specific to my particular solution. Thanks!Atlanta
O
1

This could be a good candiate for the Interface segregation principle.

In this case, define another interface such as an ICredentialConfig containing just the Username and Password properties, then make IConfig implement this interface.

public Interface ICredentialConfig
{
   string Username { get; }
   string Password { get; }
}

public Interface IConfig : ICredentialConfig
{
   //... other settings
}

Now make Foo dependant on ICredentialConfig instead of IConfig. You can then:

  1. Inject your JsonConfig using Ninject, instead of having hardcoded parameter names.
  2. Implement/Mock an ICredentialConfig for instantiating Foo in tests, instead of having to implement the full IConfig interface.
Odellodella answered 12/3, 2015 at 5:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.