Может ли Ninject разрешить абстрактные зависимости после инициализации объекта?

#asp.net-mvc-3 #ninject

#asp.net-mvc-3 #ninject

Вопрос:

Кто-нибудь знает, возможно ли использовать Ninject для разрешения любых неразрешенных абстрактных зависимостей вне процесса создания экземпляра? Я только что изучал внедрение конструктора в сравнении с внедрением свойства / метода / поля, но мне кажется, что Ninject все еще ожидает стать создателем типа, используя метод IKernel.Get<>() .

По сути, мы используем MVC3 для сборки нашего продукта, и мы столкнулись с ситуацией, когда мы хотим, чтобы ModelBinder по умолчанию сопоставлял значения формы экземпляру объекта, а затем мог вызывать метод в представленной ViewModel, который зависит от абстрактного интерфейса, например

 public class InviteFriend {
    [Required]
    public string EmailAddress { get; set; }

    public void Execute() {
        var user = IUserRepository.GetUser(this.EmailAddress);

        if (user == null) {
               IUserRepository.SaveInvite(this.EmailAddress);
        }

        MailMessage toSend = new MailMessage(); // Obviously some logic to prepare the body, subject and other mail properties
        SmtpClient.Send(toSend);
    }
}
  

где действие контроллера получит InviteFriend в качестве аргумента метода. Мы хотим, чтобы Ninject мог разрешить эту зависимость IUserRepository, но я не совсем понимаю, как это сделать, поскольку сам объект создается с помощью MVC ModelBinder, а не Ninject IKernel.Get<>().

Может быть, решением является модельный модуль на основе Ninject, или это кажется действительно плохой идеей?

ОТРЕДАКТИРУЙТЕ, ЧТОБЫ ДОБАВИТЬ: После приведенных ниже комментариев я понимаю, что мой наспех скомпонованный пример кода на самом деле не отражает того, с чем мы сталкиваемся. Я обновил пример кода, чтобы отразить эту логику для InviteFriend.Выполнить () сложнее, чем просто вызвать метод в одном репозитории. Потенциально, это логика, представляющая дискретную задачу, которая могла бы координировать взаимодействия между несколькими различными объектами домена и несколькими репозиториями. Репозитории определены абстрактно и в идеале должны быть разрешены Ninject.

Комментарии:

1. Я чувствую, что иметь подобные методы в модели — плохая идея — любые манипуляции с данными должны выполняться контроллером. Модель должна представлять данные и только данные.

2. Я согласен с Лукасом в том, что контроллер должен иметь IUserRepository, а класс InviteFriend должен делать только то, что он должен делать: представлять входные данные пользователя.

3. Не приведет ли это к появлению контроллеров fat? Я думаю, что использование этой логики в ViewModel или, может быть, лучше называемых объектах в командном стиле, сохраняет бизнес-логику в объектах домена, а затем ее легче использовать повторно, вместо того, чтобы эффективно помещать бизнес-логику в методы действия контроллера, которые оставляют область для повторного использования кода в стиле copy-paste. Таким образом, приложение Silverlight / WP7 могло бы повторно использовать тот же объект в командном стиле и не нуждалось бы в дублировании кода для той же логики…

4. Почему бы вам не добавить надлежащий бизнес-уровень и не внедрить его в контроллеры?

Ответ №1:

Я думаю, что вы ищете примерно следующий сценарий:

 public class InviteFriend {
    [Required]
    public string EmailAddress { get; set; }

    // More information
}

public interface ICommand {
    void Execute();
}

public class InviteFriendCommand : ICommand
{
    public InviteFriend(InviteFriend info, IUserRepository userRepo, IMailSender mailSender) {
        this.inviteFriend = info;
        this.userRepo = userRepo;
        this.mailSender = mailSender;
    }

    public void Execute() {
        var user = this.userRepo.GetUser(this.inviteFriend.EmailAddress);

        if (user == null) {
               this.userRepo.SaveInvite(this.inviteFriend.EmailAddress);
        }

        MailMessage toSend = new MailMessage(); // Obviously some logic to prepare the body, subject and other mail properties
        this.mailSender.Send(toSend);
    }
}

public interface ICommandFactory {
    ICommand CreateInviteFriendCommand(InviteFriend info);
}

public class CommandFactory {

    public CommandFactory(IResolutionRoot resolutionRoot) {
        this.resolutionRoot = resolutionRoot;
    }

    ICommand CreateInviteFriendCommand(InviteFriend info) {
        this.resolutionRoot.Get<InviteFriendCommand>(new ConstructorArgument("info", info));
    }
}

public class YourController {

    // Somewhere

    var command = this.commandFactory.CreateInviteFriendCommand(info);
    command.Execute();

}

public class YourModule : NinjectModule {

    override Load() {
        Bind<IUserRepository>().To<UserRepo>().InRequestScope();
        Bind<ICommandFactory>().To<CommandFactory>().InRequestScope();
        Bind<InviteFriendCommand>().ToSelf().InRequestScope();
    }
}
  

Простите меня, когда вам нужно немного подправить это. Я взломал его вместе со своим компилятором out of brain 😉

Комментарии:

1. 1 но хотел бы отметить, что внедрение resolutinroot — это плохая новость, позволяющая контейнерам проникать слишком далеко. Рассмотрите возможность привязки Func<InviteFriend,ICommand> или используйте функциональный модуль, который @Remo Gloor дал в другом ответе (я полагаю, что он появится в 2.3-4)

2. Привет, Рубен, ты прав. Я всегда стараюсь использовать подход Func<> . Но иногда это становится слишком сложным. Вот почему мы в моей команде решили, что фабрики могут использовать IResolutionRoot.

3. Спасибо за ваш комментарий — это определенно более НАДЕЖНЫЙ способ реализации описанной мной команды InviteFriend, поэтому я поддержал его как хороший, хорошо продуманный ответ. Однако я не принял это как ответ, поскольку мой вопрос касался возможностей Ninject, а не наилучшей архитектуры для решения. Спасибо, что нашли время ответить, хотя 🙂

4. Marbarch: Я понимаю вашу точку зрения — я определенно сформулировал свою точку зрения слишком догматично. (Сказав это, чем дальше вы позволяете контейнерному обману просачиваться в ваше приложение, тем больше соблазн чрезмерно использовать содержимое контейнера, которое лучше отображать как обычный код, но я уверен, что вы хорошо осведомлены об этом.)

Ответ №2:

Спасибо за все ваши комментарии, но впоследствии я нашел информацию, которую искал.

Ответ заключается в том, что с помощью Ninject можно вводить зависимости после создания экземпляра. Решение заключается в следующем:

 public class InviteFriend {
    [Inject]
    public IUserRepository UserRepo { get; set; }

    [Required]
    public string EmailAddress { get; set; }

    public void Execute() {
        var user = UserRepo.GetUser(this.EmailAddress);

        if (user == null) {
               UserRepo.SaveInvite(this.EmailAddress);
        }

        MailMessage toSend = new MailMessage(); // Obviously some logic to prepare the body, subject and other mail properties
        SmtpClient.Send(toSend);
    }
}
  

С помощью клиентского кода, затем с использованием ядра Ninject следующим образом:

 IKernel container = new StandardKernel(new ModuleWithMyBindings());
container.Inject(instanceOfInviteFriend);
  

Сам код немного сложнее, чем этот, т. Е. я не создаю экземпляр нового IKernel каждый раз, когда мне это нужно.

Я понимаю, что это архитектурно менее чисто, чем некоторые предложения, выдвинутые в комментариях, но в духе YAGNI на данный момент этого достаточно, и мы всегда можем позже провести рефакторинг с некоторыми хорошими предложениями в ответе Даниэля. Однако это был вопрос о возможностях Ninject, а не вопрос архитектурного обзора, и это то, что я считаю ответом на мой собственный вопрос 🙂