关于C#:ninject是什么?什么时候使用它?

What is Ninject and when do you use it?

我在一个项目中帮助了几个朋友,还有一个使用ninject的类。我对C很陌生,我不知道那门课在做什么,这就是为什么我需要理解Ninject。有人能解释Ninject是什么,以及何时使用它(如有可能,举例说明)?或者如果你可以指向一些链接,那也会很好。

我试过这个问题:Ninject教程/文档?但它对像我这样的初学者没有真正的帮助。


Ninject是.NET的依赖注入器,是模式依赖注入器(控制模式反转形式)的实际实现。

假设您有两个类:DbRepositoryController

1
2
3
4
5
6
7
8
9
class Controller {
   private DbRepository _repository;

   // ... some methods that uses _repository
}

class DbRepository {
   // ... some bussiness logic here ...
}

所以,现在有两个问题:

  • 您必须使_repository初始化才能使用它。你有这样的方法:

  • 在构造函数中手动。但是,如果dbrepository的构造函数改变了呢?您需要重写您的Controller类,因为代码的其他部分已更改。如果你只有一个Controller,这并不难,但是如果你有几个类依赖于你的Repository,你就有一个真正的问题。
  • 您可以使用服务定位器或工厂或SMTH。现在您依赖于服务定位器。您有一个全局服务定位器,所有代码都必须使用它。当您需要在代码的一部分中使用一个激活逻辑,而在代码的另一部分中使用其他逻辑时,您将如何更改服务定位器的行为?只有一种方法——通过构造函数传递服务定位器。但是随着越来越多的课程,你需要通过越来越多的课程。不管怎样,这是个好主意,但这是个坏主意。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class Controller {
       private DbRepository _repository;

       public Controller() {
         _repository = GlobalServiceLocator.Get<DbRepository>()
       }

       // ... some methods that uses _repository
    }
  • 您可以使用依赖注入。看看代码:

    1
    2
    3
    4
    5
    6
    7
    class Controller {
       private IRepository _repository;

       public Controller(IRepository repository) {
          _repository = repository;
       }
    }
  • 现在,当你需要控制器时,你可以写:ninjectDevKernel.Get();ninjectTestKernel.Get();。您可以在依赖性冲突解决程序之间任意快速切换。看到了吗?很简单,你不需要写很多东西。

  • 您不能对其进行单元测试。您的Controller依赖于DbRepository,如果您想测试一些使用存储库的方法,您的代码将进入数据库并请求它获取数据。很慢,很慢。如果您在DbRepository中的代码发生更改,您在Controller上的单元测试将失败。在这种情况下,只有集成测试必须说"问题"。在单元测试中需要什么——隔离类,在一个测试中只测试一个类(理想情况下——只有一个方法)。如果您的DbRepository代码失败,您会认为Controller代码失败—这很糟糕(即使您对DbRepositoryController进行了测试—它们都将失败,您可以从错误的地方开始)。确定错误的真正位置需要很多时间。你需要知道一些课程是好的,另一些课程是失败的。

  • 当你想在所有的类中用其他的东西替换DbRepository时,你必须做大量的工作。

  • 你不能轻易控制EDOCX1的寿命〔0〕。此类的对象在初始化Controller时创建,并在Controller删除时删除。在Controller类的不同实例之间没有共享beat,在其他类之间也没有共享beat。有了Ninject,你可以简单地写:

    1
    kernel.Bind<IRepository>().To<DbRepository>().InSingletonScope();
  • 以及依赖注入的特殊特性——敏捷开发!您描述了控制器使用带有接口IRepository的存储库。你不需要写DbRepository,你可以写简单的MemoryRepository类,开发Controller,其他人开发DbRepository。当DbRepository完成时,您只需在依赖关系解析器中重新绑定默认IRepository现在是DbRepository。你写了很多控制器?他们现在都将使用DbRepository。那太酷了。

    阅读更多:

  • 控制反转(wiki)
  • 依赖项注入(wiki)
  • 控制容器的反转和依赖注入模式(Martin Fowler)

  • ninject是控制容器的反转。

    它是做什么的?

    假设您有一个依赖于Driver类的Car类。

    1
    2
    3
    4
    5
    6
    7
    public class Car
    {
       public Car(IDriver driver)
       {
          ///
       }
    }

    为了使用Car类,您可以这样构建它:

    1
    2
    IDriver driver = new Driver();
    var car = new Car(driver);

    国际奥委会会签集中了关于如何建立课程的知识。它是一个中央存储库,知道一些事情。例如,它知道构建汽车需要使用的具体类是Driver,而不是任何其他IDriver

    例如,如果您正在开发MVC应用程序,您可以告诉Ninject如何构建控制器。通过注册哪些具体的类满足特定的接口,可以做到这一点。在运行时,Ninject将找出构建所需控制器所需的类,以及所有幕后操作。

    1
    2
    // Syntax for binding
    Bind<IDriver>().To<Driver>();

    这是有益的,因为它允许您构建更容易进行单元测试的系统。假设Driver封装了Car的所有数据库访问。在汽车的单元测试中,您可以这样做:

    1
    2
    IDriver driver = new TestDriver(); // a fake driver that does not go to the db
    var car = new Car(driver);

    有整个框架负责自动为您创建测试类,它们被称为模拟框架。

    更多信息:

    • Github/Ninject主页
    • 控制反转
    • 控制容器的反转和依赖注入模式
    • 模拟对象


    其他的答案很好,但我也想指出使用ninject文章实现依赖项注入的方法。这是我读过的最好的文章之一,它用一个非常优雅的例子解释了依赖注入和ninject。

    这是文章的一个片段:

    下面的接口将由我们的(smsseservice)和(mocksmsseservice)实现,基本上新的接口(ismsservice)将公开两个服务的相同行为,代码如下:

    1
    2
    3
    4
    public interface ISMSService
     {
     void SendSMS(string phoneNumber, string body);
     }

    (smsseservice)实现(ismsservice)接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class SMSService : ISMSService
     {
     public void SendSMS(string mobileNumber, string body)
     {
     SendSMSUsingGateway(mobileNumber, body);
     }




    private void SendSMSUsingGateway(string mobileNumber, string body)
     {
     /*implementation for sending SMS using gateway*/
    Console.WriteLine("Sending SMS using gateway to mobile:
        {0}. SMS body: {1}"
    , mobileNumber, body);
     }
     }

    (mocksmsseservice)完全不同的实现,使用相同的接口:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class MockSMSService :ISMSService
     {
     public void SendSMS(string phoneNumber, string body)
     {
     SaveSMSToFile(phoneNumber,body);
     }

    private void SaveSMSToFile(string mobileNumber, string body)
     {
     /*implementation for saving SMS to a file*/
    Console.WriteLine("Mocking SMS using file to mobile:
        {0}. SMS body: {1}"
    , mobileNumber, body);
     }
     }

    我们需要实现对(uihandler)类构造函数的更改以通过它传递依赖项,通过这样做,使用(uihandler)的代码可以确定要使用(ismsservice)的具体实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class UIHandler
     {
     private readonly ISMSService _SMSService;

    public UIHandler(ISMSService SMSService)
     {
     _SMSService = SMSService;
     }
     public void SendConfirmationMsg(string mobileNumber) {

     _SMSService.SendSMS(mobileNumber,"Your order has been shipped successfully!");
     }
     }

    现在,我们必须创建一个独立的类(ninjectbindings),它继承自(ninjectmodule)。这个类将负责在运行时解析依赖项,然后我们将重写用于在其中配置绑定的加载事件。Ninject的好处在于,我们不需要更改(ismsservice)、(smsseservice)和(mocksmsseservice)中的代码。

    1
    2
    3
    4
    5
    6
    7
    public class NinjectBindings : Ninject.Modules.NinjectModule
     {
     public override void Load()
     {
     Bind<ISMSService>().To<MockSMSService>();
     }
     }

    现在,在ui表单代码中,我们将使用ninject的绑定,它将确定要使用的实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Program
     {
     static void Main(string[] args)
     {
     IKernel _Kernal = new StandardKernel();
     _Kernal.Load(Assembly.GetExecutingAssembly());
     ISMSService _SMSService = _Kernal.Get<ISMSService>();

    UIHandler _UIHandler = new UIHandler(_SMSService);
     _UIHandler.SendConfirmationMsg("96279544480");

    Console.ReadLine();
     }
     }

    现在,代码使用ninject kernal来解析所有的依赖链,如果我们想在发布模式(在生产环境中)而不是模拟模式下使用真实服务(smsseservice),我们需要更改ninject绑定类(ninjectbindings),只使用正确的实现,或者使用下面的if-debug指令:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class NinjectBindings : Ninject.Modules.NinjectModule
     {
     public override void Load()
     {
    #if DEBUG
     Bind<ISMSService>().To<MockSMSService>();
    #else
     Bind<ISMSService>().To<SMSService>();
    #endif

    }
     }

    现在我们的绑定类(ninjectbindings)位于所有执行代码之上,我们可以轻松地在一个位置控制配置。

    还有,看什么是控制反转?文中提到了一些非常简单的例子来理解国际奥委会。