Ninject – Getting Started, Resources, Basic Binding
This post will cover the information needed to get started with Ninject. By the end of this post, you will be able to use Ninject in your application as a dependency injector. Here is a description from the Ninject web site.
Ninject is a lightning-fast, ultra-lightweight dependency injector for .NET applications. It helps you split your application into a collection of loosely-coupled highly-cohesive pieces, and then glue them back together in a flexible manner. By using Ninject to support your software’s architecture, your code will become easier to write, reuse, test, and modify.
You will often hear frameworks like Ninject called Inversion of Control (IOC) containers. IOC containers are responsible for the creation and life-cycle management of dependencies that the objects in your application require. If you want more information on Dependency Injection (DI) and IOC containers check out my previous post.
Getting Started With Ninject
To get started with Ninject, you will need to download the code. Here is a link to the Ninject download page:
From this page select the platform that you are targeting. In our examples we will be targeting the .NET Framework 3.5.
You will be prompted to save an archive of the Ninject DLLs you will need. Unzip the contents of this file into a folder that can be accessed later in your project. Here is a screenshot of the contents.
To use Ninject in your project, add a reference to the ‘Ninject.dll’ highlighted above. That is it, you are now ready to use Ninject in your project.
Description of Ninject Core Elements
Before using Ninject it is a good idea to understand fundamentally what the Ninject core elements are and the features they provide.
- StandardKernel – This class is the container. Bindings are setup and passed into the constructor. An instance of the StandardKernel can be used to request concrete instances of our service types. The StandardKernel instance can resolve the requested type to a type configured in the bindings. It then provides an instance of the resolved type. The StandardKernel manages the life time of the resolved type instances.
You should be warned that passing the instance of StandardKernel around to request types is frowned upon and negates many of the benefits of the container.
- NinjectModule – This is an abstract base class that allows bindings to be defined for the application. These bindings are essentially a look up table that match a requested type to a resolved type. Instances of classes that implement NinjectModule can then be passed into the StandardKernel constructor. Using multiple NinjectModule classes can help organize your bindings into logic units.
The process of introducing Ninject into an application generally has two steps:
- Create concrete implementations (at least one is required) of the NinjectModule abstract base classes.
- Instantiate a concrete instance of the StandardKernel. The StandardKernel constructor accepts NinjectModule instances. This sets up the Ninject container.
Application Example
In order to discuss Ninject an application example is necessary. The example used here has elements that may be found in common business applications. The initial requirements of the application are as follows:
- The application must generate an audit log of all activities.
- The application must use an already established data source of transactions. A transaction consists of a timestamp / amount pair.
- The application must generate a summary report for all transactions within the last 6 months. The report will be a text file consisting of the following:
- Report generation date.
- Total of transactions
- A list of transactions for the interval.
This fictional application may not be of sufficient complexity that dependency injection becomes too difficult to do by hand. It does have a number of realistic elements. Let’s write some code.
The logger and reporting requirements will necessitate that we serialize data to the file system. The .NET Framework’s ‘System.IO.File’, ‘System.IO.Directory’ and ‘System.IO.Path’ classes are static. In order to have automated test code that does not write to the actual file system we introduce the following interface:
public interface IFileSystem { // System.IO.Path string GetDirectoryName(string path); string PathCombine(string path1, string path2); // System.IO.Directory bool DirectoryExists(string path); void CreateDirectory(string path); // System.IO.File void AppendAllText(string path, string contents); bool FileExists(string path); string[] ReadAllLines(string path); void WriteAllLines(string path, string[] lines); string ReadAllText(string path); void WriteAllText(string path, string contents); }
This interface is not a complete wrapper for the ‘System.IO’ classes but contains just the features needed in our application. Two concrete implementations of this interface will be developed: ‘FileSystem’ and ‘FakeFileSystem’. ‘FileSystem’ simply calls the ‘System.IO’ counterpart while the ‘FakeFileSystem’ implementation is an in-memory file system that can be used by the test bed. The concrete implementations and the ‘IFileSystem’ interface all cry out REUSE ME. These should exist in their own namespace and assembly.
The application also needs a logger. There are some really nice logging libraries that already exist. I do not recommend reinventing the wheel. However, to keep our example small and reduce complexity we will implement our own. The following is a really simple logger interface.
public interface ILogger { void Error(string info); void Warn(string info); void Info(string info); }
This interface allows three logging levels that take simple strings as arguments. Our application will implement the following concrete versions: ‘FileLogger’ and ‘MemoryLogger’. The ‘FileLogger’ will be used by the deployed application. Once again we have an in-memory counter part for testing.
Data is being stored in an already existing data source. The data source complexity is encapsulated with the following class and interface:
public class Transaction { public DateTime Date { get; set; } public double Amount { get; set; } } public interface IDataRepository { IQueryable<Transaction> FetchTransactions(DateTime begin, DateTime end); }
The ‘Transaction’ class provides a container for data. Implementations of the ‘IDataRepository’ interface hide the complexity of the data source details. The example application will implement the following concrete instances: ‘DataRepository’ and ‘FakeDataRepository’. Again we have a real implementation that queries the data source (database, xml, csv) and a fake implementation that generates in-memory collections for our test bed.
To summarize we have the following objects in the application.
IFileSystem |
ILogger |
IDataRepository |
|
Application |
FileSystem |
FileLogger |
DataRepository |
Test Bed |
FakeFileSystem |
MemoryLogger |
FakeDataRepository |
It is pretty clear that we will want to configure the test bed and application to use their respective implementations of the interfaces. There may also be additional levels in the dependency graph. For instance the implementations of the ‘IDataRepository’ interface will require an instance of ‘ILogger’ to meet our audit logging requirement.
public class DataRepository : IDataRepository { private readonly ILogger _logger; public DataRepository(ILogger logger) { _logger = logger; } public IQueryable<Transaction> FetchTransactions(DateTime begin, DateTime end) { // Fetch data, log and return. // throw new NotImplementedException(); } }
Managing these dependencies by hand may be effective for small projects, but as the application grows in size and complexity, it becomes more and more cumbersome to wire up all of objects. Another option is to use an IOC container, such as Ninject, to manage the dependencies of the application and test bed.
So far the application has a couple of highly decoupled and cohesive core infrastructure objects (file system, logging) that can be re-used in other applications. The application also has an application specific data repository object. The following objects add the remaining business intelligence to complete our application requirements.
public class Calculator { private readonly ILogger _logger; public Calculator(ILogger logger) { _logger = logger; } public double CalculateTotalTransactions(IQueryable<Transaction> data) { double totalTransactions = 0; foreach (Transaction transaction in data) { totalTransactions += transaction.Amount; } _logger.Info("Calculation: num entries = " + data.Count() + ", total = " + totalTransactions); return totalTransactions; } } public class Report { private readonly IFileSystem _fileSystem; private readonly ILogger _logger; private const string OutputFolder = @"c:tempdelete_me"; public Report(IFileSystem fileSystem, ILogger logger) { _fileSystem = fileSystem; _logger = logger; } public void Save(IQueryable<Transaction> data, double total) { DateTime now = DateTime.Now; StringBuilder content = new StringBuilder(); content.AppendLine("Date: " + now); content.AppendLine("Total: " + total); foreach (var datum in data) { content.AppendLine(datum.Date + ", " + datum.Amount); } string outputPath = _fileSystem.PathCombine(OutputFolder, "rpt" + now.ToString("yyyy_MM_dd_hh_mm_ss") + ".txt"); _fileSystem.WriteAllText(outputPath, content.ToString()); _logger.Info("Saved report. filename='" + outputPath + "'"); } }
The implementation of these last two class was quick-n-dirty. Not too much thought was put into optimizing the code. Instances of the ‘Calculator’ class perform the business logic of summing all the supplied transactions. The ‘Report’ object generates a report.
Notice that both the ‘Calculator’ and ‘Report’ classes have dependencies on the previous objects. Specifically they both use an instance of ‘ILogger’ and the ‘Report’ object uses an instance of ‘IFileSystem’. You can easily imagine how we could automatically test the ‘Calculator’ and ‘Report’ objects by injecting in the in-memory versions.
Wire Up The Application By Hand
Managing the dependencies and wiring up our application is not too tough in this case. The following code shows what needs to be done:
// Get config data (probably from a config file) // const string logFile = @"c:tempdelete_melogfile.txt"; const int numberOfMonths = 6; // Create the objects. // IFileSystem fileSystem = new FileSystem.FileSystem(); ILogger logger = new FileLogger(fileSystem, logFile); IDataRepository repo = new FakeDataService(logger); Calculator calculator = new Calculator(logger); Report report = new Report(fileSystem, logger); // Process // var data = repo.FetchTransactions(DateTime.Now.AddMonths(-numberOfMonths), DateTime.Now); double total = calculator.CalculateTotalTransactions(data); report.Save(data, total);
This is really not too bad. There would be an equivalent amount of object creation in our test bed. Because this application is small, the wiring up code is probably manageable by hand.
Use Ninject To Supply Our Dependencies
As discussed earlier, the first step to use Ninject is to create a concrete implementation of the ‘NinjectModule’ abstract class. The following implementation provides the bindings necessary for this application:
public class SimpleModule : NinjectModule { public override void Load() { // Get this from an external file. // const string logFile = @"c:tempdelete_melogfile.txt"; Bind<IFileSystem>().To<FileSystem.FileSystem>(); Bind<IDataRepository>().To<FakeDataService>(); Bind<ILogger>().To<FileLogger>().WithConstructorArgument("fileName", logFile); } }
The generic Ninject ‘Bind’ method takes the type of the service (‘IFileSystem’ for instance) to bind. The Ninject ‘To’ method takes the implementation type (‘FileSystem.FileSystem’). This wires up the Ninject container to provide an instance of ‘FileSystem.FileSystem’ for every ‘IFileSystem’ request. Ninject also provides the ‘WithConstructorArgument’ method that allows the ‘fileName’ parameter to be supplied to the ‘FileLogger’ constructor.
The Ninject binding code is very fluent and offers many features. In later posts we will look at more advanced binding scenarios.
Next we need to wire up the Ninject container. The following code initializes the container, creates the objects and performs the processing.
// Get config data (probably from a config file) // const int numberOfMonths = 6; // Initialize the DI container. // NinjectModule module = new SimpleModule(); IKernel kernel = new StandardKernel(module); // Get object from DI container. // var repo = kernel.Get<IDataRepository>(); var calculator = kernel.Get<Calculator>(); var report = kernel.Get<Report>(); // Process // var data = repo.FetchTransactions(DateTime.Now.AddMonths(-numberOfMonths), DateTime.Now); double total = calculator.CalculateTotalTransactions(data); report.Save(data, total);
This code first instantiates an instance of the ‘SimpleModule’ class that contains the binding information for the application. Next an instance of the ‘StandardKernel’ is instantiated. The objects needed by the application are requested from the Ninject container using the generic ‘Get’ method. Finally, the processing is done.
The code to wire up the Ninject container consists of 15 lines of code versus 10 lines in the hand-wired case (ignoring comments and lines that only contain curly brackets).
Testing Using Ninject
How do we wire up the in-memory fakes that we created earlier for testing? It is as simple as creating another module.
public class SimpleFakeModule : NinjectModule { public override void Load() { Bind<IFileSystem>().To<FakeFileSystem>(); Bind<IDataRepository>().To<FakeDataService>(); Bind<ILogger>().To<MemoryLogger>(); } }
This module wires up the in-memory dependencies. In our test bed, we can then create a ‘StandardKernel’ using this module. This ability to reconfigure an application on the fly is a very powerful feature of using Ninject.
Summary
As we have seen introducing Ninject into our application to provide basic dependency injection is simple. An IOC container does add complexity. In our case we added 5 lines and an addition of a library. The cost of this added complexity must be weighed against the benefits. Don’t make the decision just yet. There is much more that Ninject has to offer (advanced binding and life-cycle management for instance). This post sets the story for a couple of future posts on Ninject.
Any chance that you could post a copy of your sample project? It would be helpful to see where all the pieces fit. Thanks.
Hi Caine,
Unfortunately, I don’t have this code. I put together this code just as a sample. A really good example though of how everything fits together is from the Ninject web site. This has been open sourced and is available here.
Ninject Web Site
Bob
The above link for downloading ninject.dll fails to download. Is there any other site to download the ninject.dll. I understood DI concepts mostly from this tutorial.
Hi Zahid,
You should really download the latest version of Ninject. You can do that using Nuget or directly from the Ninject project: http://ninject.org/download.html
Bob
Hi I have just stumbled upon your site in search of how depency injection works. I really understand it now. Thanks so much. Do you think there are performance issues with all the bindings thats happening when you ‘inject’?
Also do you have a rule of thumb on how often you use ‘injections’ like this? Would you want to do it for all depencies within your classes or just a few?
Hi Huy,
Thanks for visiting. These are great questions. Let me try to answer them from my perspective:
1. Is there a performance issue with using an IOC container like Ninject? I believe that if the IoC container is designed properly this is not noticeable. I am not saying that for 100% of the cases there is not an issue, but most of the time there is no performance issue. I believe, in medium to large size projects, the tradeoff for centralizing your ‘life time management’ of object into an IoC is worth it.
2. As a rule of thumb, DI as a design pattern provides a level of decoupling that is very beneficial to your application. Is there a hard and fast rule? Probably not. I use it a lot. Anytime you are ‘newing’ up or creating objects within other objects you should consider using DI. If you are doing any kind of automated testing, then DI provides the ability to inject in fakes.
Hope this helps.
Bob
This is a very good post for learning the basics of Ninject! I just started learning about DI, IoC and Ninject and I’ve seen many people say (including Remo Gloor, one of the developers of Ninject) that the Get method of the kernel should only be called once in the composition root of the application. If one calls this method more than once, the code can devolve into the Service Locator anti-pattern. How would you modify the above code to only call Get once? I imagine one could have a YourApplication class with a constructor that takes in an IDataRepository, Calculator, and Report and call kernel.Get.
Forgot to close the bold tag! The word “only” is the only thing that should be bolded!