#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, а не вопрос архитектурного обзора, и это то, что я считаю ответом на мой собственный вопрос 🙂