【问题标题】:Can Ninject resolve abstract dependencies after the object is initialised?Ninject 可以在对象初始化后解析抽象依赖吗?
【发布时间】:2011-04-27 14:47:14
【问题描述】:

有谁知道是否可以使用 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 的 ModelBinder,或者这似乎是一个非常糟糕的主意?

编辑添加:在下面的 cmets 之后,我意识到我匆忙模拟的代码示例并不能真正反映我们所面临的情况。我更新了代码示例以反映 InviteFriend.Execute() 的逻辑比仅在一个存储库上调用方法更复杂。潜在地,这是表示离散任务的逻辑,该任务可以协调多个不同领域对象和多个存储库之间的交互。存储库是抽象定义的,理想情况下将由 Ninject 解决。

【问题讨论】:

  • 我觉得在模型中使用这样的方法是个坏主意——任何数据操作都应该由控制器完成。模型应该只代表数据和数据。
  • 我同意 Lukas 的观点,即控制器应该有 IUserRepository 并且 InviteFriend 类应该只做它应该做的事情:代表用户输入数据。
  • 这不会导致胖控制器吗?我的想法是,在 ViewModel 中使用这个逻辑,或者更好地称为命令样式对象,将业务逻辑保留在域对象中,然后更容易重用,而不是有效地将业务逻辑放在控制器操作方法中为复制粘贴样式的代码重用留出了余地。这样,Silverlight/WP7 应用程序可以重复使用相同的命令样式对象,而无需为相同的逻辑复制代码...
  • 为什么不添加适当的业务层并将其注入控制器?

标签: asp.net-mvc-3 ninject


【解决方案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();
    }
}

如果您需要稍微调整一下,请原谅我。我用我的大脑编译器一起破解了它;)

【讨论】:

  • +1 但想指出注入 resolutinroot 是个坏消息 - 让容器泄漏得太远。考虑绑定 Func&lt;InviteFriend,ICommand&gt; 或使用 @Remo Gloor 在另一个答案中给出的 Func 模块(即将在 2.3 -4 我相信)
  • 嗨鲁本,你是对的。我总是尝试使用 Func 方法。但有时这会变得复杂。这就是为什么我们在我的团队中决定工厂可以使用 IResolutionRoot。
  • 感谢您的评论 - 这绝对是实现我概述的 InviteFriend 命令的一种更可靠的方法,因此我将其作为一个很好的、经过深思熟虑的答案表示赞同。但是,我没有接受它作为 the 的答案,因为我的问题是关于 Ninject 的功能,而不是关于解决方案的最佳架构。感谢您抽出宝贵时间回答:)
  • Marbarch:我明白你的意思——我确实太武断地表达了我的观点。 (话虽如此,你让 Container 诡计渗透到你的应用程序中越多,就越容易过度使用容器中的东西,这些东西最好作为普通代码浮出水面,但我相信你很清楚这一点。)
【解决方案2】:

感谢您的所有 cmets,但我随后找到了我正在寻找的信息。

答案是可以使用 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。

我意识到这在架构上不如 cmets 中提出的一些建议那么纯粹,但本着 YAGNI 的精神,现在这已经足够好了,我们以后可以随时根据 Daniel 回答中的一些好的建议进行重构.然而,这是一个关于 Ninject 功能的问题,而不是架构审查问题,这就是我认为自己问题的答案:)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-01-04
    相关资源
    最近更新 更多