问题
I have been helping a few friends on a project and there is a class that uses Ninject. I am fairly new to C# and I have no idea what that class is doing, which is why I need to understand Ninject. Can anyone explain what Ninject is and when does one use it(with example if possible)? Or if you can point to some links that would be great too.
I tried this question: Ninject tutorials/documentations? but it didn't really help a beginner like me.
回答1:
Ninject is dependency injector for .NET, practical realisation of pattern Dependency Injection (form of Inversion of Control pattern).
Suppose you have two classes DbRepository
and Controller
:
class Controller {
private DbRepository _repository;
// ... some methods that uses _repository
}
class DbRepository {
// ... some bussiness logic here ...
}
So, now you have two problems:
You must inialize
_repository
to use it. You have such ways:- In constructor manually. But what if constructor of DbRepository changes? You need to rewrite your
Controller
class because other part of code was changed. It's not hard if you have only oneController
, but if you have a couple of classes that have dependency on yourRepository
you have a real problem. You can use service locator or factory or smth. Now you have dependency on your service locator. You have a global service locator and all code must use it. How you will change behaviour of service locator when you need to use in one part of code one activation logic and something other in other part of code? There is only one way - passing service locator through the constructors. But with more and more classes you will need to pass it more and more. Anyway, that is nice idea, but it is bad idea.
class Controller { private DbRepository _repository; public Controller() { _repository = GlobalServiceLocator.Get<DbRepository>() } // ... some methods that uses _repository }
You can use dependency injection. Look at the code:
class Controller { private IRepository _repository; public Controller(IRepository repository) { _repository = repository; } }
Now when you need your controller you write:
ninjectDevKernel.Get<Controller>();
orninjectTestKernel.Get<Controller>();
. You can switch beetween dependency resolvers as fast as you want. See? It's simple, you don't need to write a lot.- In constructor manually. But what if constructor of DbRepository changes? You need to rewrite your
You can't make unit tests on it. Your
Controller
have dependency onDbRepository
and if you want test some method that uses repository, your code will go to database and ask it for data. That's slow, very slow. If your code inDbRepository
changes, your unit test onController
will fall. Only integration test must say 'problems' in this case. What you need in unit tests - to isolate your classes and test only one class in one test (in ideal - only one method). If yourDbRepository
code fails, you will think thatController
code failed - and that's bad (even if you have tests forDbRepository
andController
- they both will fail and you can start from wrong place). It takes a lot of time to determine where is error really. You need to know that some class is ok, and some other class was failed.When you want replace
DbRepository
with something other in all your classes, you have to do a lot of work.You can't easy control lifetime of
DbRepository
. Object of this class creates on inializing ofController
and deletes whenController
deletes. There are no sharing beetween different instances ofController
class and there are no sharing beetween other classes. With Ninject you can simply write:kernel.Bind<IRepository>().To<DbRepository>().InSingletonScope();
And special feature of dependency injection - agile development! You describe that your controller uses repository with interface IRepository
. You don't need to write DbRepository
, you can write simple MemoryRepository
class and develop Controller
while other guys develops DbRepository
. When DbRepository
will be finished, you just rebinds in your dependency resolver that default IRepository
is DbRepository
now. You have writed a lot of controllers? All of them will use now DbRepository
. That's cool.
Read more:
- Inversion of control (wiki)
- Dependency injection (wiki)
- Inversion of Control Containers and the Dependency Injection pattern (Martin Fowler)
回答2:
Ninject is an Inversion of Control container.
What does it do?
Suppose you have a Car
class that depends on a Driver
class.
public class Car
{
public Car(IDriver driver)
{
///
}
}
In order to use the Car
class you build it like so:
IDriver driver = new Driver();
var car = new Car(driver);
A IoC containter centralizes the knowledge about how to build classes. It is a central repository that knows a few things. For example, it knows that the concrete class that you need to use to build a car is a Driver
and not any other IDriver
.
For example, if you are developing a MVC application, you can tell Ninject how to build your controllers. You do so by registering which concrete classes satisfy specific interfaces. At run time Ninject will figure out which classes are needed to build the required controller, and all behind the scenes.
// Syntax for binding
Bind<IDriver>().To<Driver>();
This is beneficial because it lets you build systems that are more easily unit testable. Suppose that Driver
encapsulates all database access for Car
. In a unit test for Car you can do this:
IDriver driver = new TestDriver(); // a fake driver that does not go to the db
var car = new Car(driver);
There are entire frameworks that take care of automatically creating testing classes for you and they are called mocking frameworks.
For more information:
- GitHub/Ninject Home
- Inversion of Control
- Inversion of Control Containers and the Dependency Injection pattern
- Mock Object
回答3:
Other answers are great but I would also like to point out this Implementing Dependency Injection using Ninject article.
This is one of the best articles I ever read which explains Dependency Injection and Ninject with a very elegant example.
Here's the snippet from the article:
Below Interface will be implemented by our (SMSService) and (MockSMSService), basically the new Interface (ISMSService) will expose the same behaviors of both services as the code below:
public interface ISMSService
{
void SendSMS(string phoneNumber, string body);
}
(SMSService) implementation to implement the (ISMSService) interface:
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);
}
}
(MockSMSService) with totally different implementation using the same interface:
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);
}
}
we need to implement a change to our (UIHandler) class constructor to pass the dependency through it, by doing this, the code which uses the (UIHandler) can determine which concrete implementation of (ISMSService) to use:
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!");
}
}
Now, we have to create a separate class (NinjectBindings) which inherits from (NinjectModule). This class will be responsible to resolve dependencies at run time, then we’ll override the load event which is used to configure the binding in it. The nice thing about Ninject is that we do not need to change our code in (ISMSService), (SMSService), and (MockSMSService).
public class NinjectBindings : Ninject.Modules.NinjectModule
{
public override void Load()
{
Bind<ISMSService>().To<MockSMSService>();
}
}
Now in UI form code, we’ll use the binding for Ninject which will determine which implementation to use:
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();
}
}
Now the code is using the Ninject Kernal to resolve all chain of dependencies, if we want to use the real service (SMSService) in Release mode (on production environment) instead of the mock one, we need to change on the Ninject binding class (NinjectBindings) only to use the right implementation or by using the #if DEBUG directive as below:
public class NinjectBindings : Ninject.Modules.NinjectModule
{
public override void Load()
{
#if DEBUG
Bind<ISMSService>().To<MockSMSService>();
#else
Bind<ISMSService>().To<SMSService>();
#endif
}
}
Now our binding class (NinjectBindings) is living on the top of all our execution code and we can control the configuration easily in once place.
Also, see What is Inversion of Control? some very simple examples are mentioned to understand IoC.
来源:https://stackoverflow.com/questions/17375234/what-is-ninject-and-when-do-you-use-it