【问题标题】:Best practice for Ninject to get different implementations based on top level projectNinject 基于顶级项目获得不同实现的最佳实践
【发布时间】:2023-12-24 02:54:01
【问题描述】:

我刚开始学习依赖注入,我被困在这里。

我的项目在实体框架 DbContext 上有一个 WCF 数据服务。

public class MyDataService : DataService<MyDbContext>
{

    protected override MyDbContext CreateDataSource()
    {
        // I want to use dependency injection for this
        return new MyDbContext();
    }
}

班级是
a) IIS 托管,所以我没有任何控制权 b) 用于集成测试,使用var host = new DataServiceHost(type, new Uri[] { });创建

两者都为 MyDbContext 使用不同的构造函数

所以基本上是用这个注入上下文

    protected override MyDbContext CreateDataSource()
    {
        INinjectModule module = ???; // - 
        IKernel kernel = new StandardKernel(module);
        return kernel.Get<MyDbContext>();
    }

所以问题是,在这种情况下,最佳做法是什么? 我应该:

a) 在主项目和服务都使用的类库中创建一个模块 b) 在包含 Ninject 模块的 DataService 项目中创建一个公共静态变量。 c) 在包含 Ninject 内核的 D​​ataService 项目中创建一个公共静态变量 d) 别的东西。

我更喜欢类似的东西

    protected override MyDbContext CreateDataSource()
    {
        DefaultKernel.Get<MyDbContext>();
    }

【问题讨论】:

    标签: c# dependency-injection inversion-of-control ninject


    【解决方案1】:

    首先,你应该有一个Composition Root。也就是说,创建 Kernel 的单个位置(不是在每个函数中)。

    其次,您在这里不需要NinjectModule。您是在要求 Ninject 创建一个具体对象的实例(在几乎所有情况下...都达不到目的)。

    您应该创建一个单独的NinjectModule,将其传递给Kernel.. 的构造函数,如下所示:

    interface IContext {
    }
    
    class MyDbContext : DbContext, IContext {
    }
    
    class YourModule : NinjectModule {
        protected override void Bind() {
            Bind<IContext>().To<MyDbContext>();
        }
    }
    
    // In your composition root somewhere
    var kernel = new StandardKernel(new NinjectModule[] { new YourModule() });
    
    // in your createdatasource method
    kernel.Get<IContext>();
    

    这将使您入门。通常,您的组合根是驱动在整个应用程序中注入对象的原因,从而消除了传递Kernel 的需要(您必须在当前设置中这样做)。

    在开始使用 DI/IoC 时很难理解的是,创建整个依赖关系图是容器的工作。因此,如果您设置以下绑定:

    IContract1 -> ConcreteObject1
    IContract2 -> ConcreteObject2
    IContract3 -> ConcreteObject3
    

    ..并具有以下设置:

    class ConcreteObject1 : IContract1 {
        public ConcreteObject1(IContract2 contract3) {
        }
    }
    
    class ConcreteObject2 : IContract2 {
        public ConcreteObject2(IContract3 contract3) {
        }
    }
    

    如果您要求容器提供IContract1 的具体实现(即ConcreteObject1),那么它将创建它....但是:ConcreteObject1 需要在构造函数中具体实现IContract2 .所以容器说“等等,我知道如何创建这个”.. 并传入ConcreteObject2 的实例。再一次,它说“等等,ConcreteObject2 想要一个 IContract3 的具体实现。再次,它去获取一个。

    希望对您有所帮助。

    【讨论】:

    • 谢谢,我不明白一件事:Composition Root 应该在主项目中还是在所有其他项目引用的类库中?
    • 通常它们是“自上而下”的。对于 Web 应用程序,将其放在 Web 应用程序 Global.asax.cs 中。对于 WinForms,Program.cs。
    • 我已经更新了我的答案,以帮助更好地理解内核如何只需要位于您的组合根目录中。
    • 感谢您的努力。自上而下的方法是我想要实现的。但是如何将 kenel 传递给依赖图呢?另一种情况:我使用 NLog 进行日志记录。在我的主应用程序中,我配置了 NLog。每个其他项目都使用静态LogManager.GetLogger(...) 方法来获取记录器。我必须自己实现类似的东西吗?或者是否有一些预定义的东西(只是一个例子:Ninject.KernelManager.GetDefaultKernel)如果不是这样,我必须创建自己的类库并在其他项目中使用它。
    • 你不会传递Kernel。内核的工作实际上只请求*对象。当它创建一个*对象时,它会创建自始至终所需的任何依赖项。所以基本上,你一直在传递一个依赖和一个依赖的依赖。内核知道如何为你创建这些对象(不向下传递)。如果不编写有关该主题的完整教程,很难解释:/