Ninject: аргумент конструктора Bind для свойства другого объекта

У меня есть объект IConfig, содержащий параметры, используемые во всем приложении. В настоящий момент я вставляю весь объект в конструктор каждого объекта, который ему нужен, следующим образом:

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

Недостатком является то, что IConfig содержит большое количество настроек, поскольку он десериализован из общего файла конфигурации, поэтому инъекция всего объекта не требуется. То, что я хотел бы сделать, это изменить конструктор на Foo(string username, string password), чтобы он получал только нужные ему настройки. Это также упрощает создание объектов Foo для тестирования (не нужно настраивать IConfig только для создания Foo). Я хотел бы привязать аргументы конструктора непосредственно в моем NinjectModule, что-то вроде следующего:

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

Очевидно, что этот код не работает, но как я буду делать то, что хотел?

Моя первоначальная идея состояла в том, чтобы использовать NinjectModule.Kernel, чтобы получить IKernel, затем получить экземпляр моего объекта IConfig и вставить свойства по мере необходимости, но объект, возвращаемый NinjectModule.Kernel, не имеет метода Get<T>().

Ответ 1

Вы на правильном пути:

Метод Kernel.Get<T>() - это метод расширения, определенный в ResolutionExtensions в теге Ninject, поэтому с добавлением using Ninject; он также доступен в вашем модуле.

Но вместо Module.Kernel вы должны использовать IContext, предоставленный во второй перегрузке WithConstructorArgument, чтобы получить Kernel:

Bind<IFoo>().To<Foo>()
    .WithConstructorArgument("username", 
                             context => context.Kernel.Get<IConfig>().Username)
    .WithConstructorArgument("password", 
                             context => context.Kernel.Get<IConfig>().Password);

Ответ 2

Это может быть хорошим приветствием для Принципа разделения селектирования.

В этом случае определите другой интерфейс, такой как ICredentialConfig, содержащий только свойства Username и Password, затем создайте IConfig реализацию этого интерфейса.

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

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

Теперь сделайте Foo зависимым от ICredentialConfig вместо IConfig. Затем вы можете:

  • Внесите свой JsonConfig, используя Ninject, вместо жестких имен параметров.
  • Реализация /Mock ICredentialConfig для создания экземпляра Foo в тестах вместо необходимости реализовать полный интерфейс IConfig.