This article intends to outline the concept of Dependency Injection and its uses within a software engineering project. From Wikipedia.org:
“it is a design pattern that separates behaviour from dependency resolution, thus decoupling highly dependent components”.
Later, I demonstrate the use of Dependency Injection within the context of an Asp.Net MVC 3 application.
Dependency Injection (or DI) allows us to provide implementations and services to other classes for consumption in a very loosely-coupled way. The key tenet is that such implementations can be swapped out for other implementations by changing a minimal amount of code, as the implementation and the consumer are linked by contract only.
In C#, this means that your service implementations should adhere to an interface, and when creating consumers for your services you should program against the interface and not the implementation, and require that the implementation is provided for you, or injected rather than having to create instances yourself. Doing this allows your classes to not worry about how dependencies are create nor where they come from; all that matters is the contract.
Dependency Injection by Example
Let’s go through a simple example where DI could be useful. First, let’s create an interface (the contract) which will allow us to perform some task, say logging a message:
public interface ILogger { void LogMessage(string message); }
Notice that nothing about this interface describes how the message is logged and where it is logged to; it simply has the intention of recording a string to some repository. Next, lets create something which uses this interface. Say we create a class which watches a particular directory on disk, and logs a message whenever the directory is changed:
public class DirectoryWatcher { private ILogger _logger; private FileSystemWatcher _watcher; public DirectoryWatcher(ILogger logger) { _logger = logger; _watcher = new FileSystemWatcher(@"C:\Temp"); _watcher.Changed += new FileSystemEventHandler(Directory_Changed); } void Directory_Changed(object sender, FileSystemEventArgs e) { _logger.LogMessage(e.FullPath + " was changed"); } }
The key thing to notice is that the constructor, we require that something which implements ILogger is given to us, but again notice that we don’t care about where the log goes or how it is created. We can just program against the interface and not worry about it.
This means that in order to create an instance of our DirectoryWatcher we must also have an implementation of ILogger ready. Let’s go ahead and create one which logs messages to a text file:
public class TextFileLogger : ILogger { public void LogMessage(string message) { using (FileStream stream = new FileStream("log.txt", FileMode.Append)) { StreamWriter writer = new StreamWriter(stream); writer.WriteLine(message); writer.Flush(); } } }
Let’s create another which logs messages to the Windows Event Log:
public class EventFileLogger : ILogger { private string _sourceName; public EventFileLogger(string sourceName) { _sourceName = sourceName; } public void LogMessage(string message) { if (!EventLog.SourceExists(_sourceName)) { EventLog.CreateEventSource(_sourceName, "Application"); } EventLog.WriteEntry(_sourceName, message); } }
Now we have two separate implementations which log messages in very different ways, but both implement ILogger, which means that either one can be used where an instance of ILogger is required. Now we can create an instance of DirectoryWatcher and have it use one of our loggers:
ILogger logger = new TextFileLogger(); DirectoryWatcher watcher = new DirectoryWatcher(logger);
Or, by just changing the right-hand side of the first line we can use our other implementation:
ILogger logger = new EventFileLogger(); DirectoryWatcher watcher = new DirectoryWatcher(logger);
This happens without any changes to the implementation of DirectoryWatcher, and this is the key concept. We are injecting our logger implementation into the consumer, so that the consumer doesn’t have to create this instance on its own. The example shown is trivial, but imagine using this in a large-scale project where you have several dependencies which need to be used by many times more consumers, and then suddenly a requirement comes along which means that the method of logging a message must change (say the messages are required to be logged into Sql Server for auditing purposes). Without some form of dependency injection, you will have to carefully examine the code and change anything which actually creates an instance of a logger and then uses it. In a large project this can be painful and error prone. With DI, you would just have to change the dependency in one place, and the rest of your application will effectively absorb the change and immediately start using the new logging method.
Essentially, it solves the classic software problem of high-dependency and allows you to create a loosely-couple system which is extremely agile and easy to change.
Dependency Injection Containers
Many DI frameworks which you can download and use go a step further and employ the use of a Dependency Injection Container. This is essentially a class which stores a mapping of types and returns the registered implementation for that type. In our simple example we would be able to query the container for an instance of ILogger and it would return an instance of TextFileLogger, or whichever instance we had initialised the container with.
This has the advantage that we can register all of our type mappings in one place, usually in an “Application Start” event, and that gives us quick and clear visibility as to what dependencies we have in the system. Also, many professional frameworks allow us to configure the lifetime of such objects, either creating fresh instances every time we ask for one, or re-using instances across calls.
The container is usually created in such a way that we can get access to the ‘Resolver’ (the thing which allows us to query for instances) from anywhere in the project.
Finally, professional frameworks usually support the concept of “sub-dependencies”, where a dependency has itself one or more dependencies to other types also known to the container. In this case, the resolver can fulfil these dependencies too, giving you back a full chain of correctly created dependencies according to your type mappings.
Let’s create ourselves a very simple DI container to see how the concept works. This implementation doesn’t support nested dependencies, but does allow you to map an interface to an implementation and then query for that implementation later:
public class SimpleDIContainer { Dictionary<Type, object> _map; public SimpleDIContainer() { _map = new Dictionary<Type, object>(); } /// <summary> /// Maps an interface type to an implementation of that interface, with optional arguments. /// </summary> /// <typeparam name="TIn">The interface type</typeparam> /// <typeparam name="TOut">The implementation type</typeparam> /// <param name="args">Optional arguments for the creation of the implementation type.</param> public void Map<TIn, TOut>(params object [] args) { if (!_map.ContainsKey(typeof(TIn))) { object instance = Activator.CreateInstance(typeof(TOut), args); _map[typeof(TIn)] = instance; } } /// <summary> /// Gets a service which implements T /// </summary> /// <typeparam name="T">The interface type</typeparam> public T GetService<T>() where T : class { if (_map.ContainsKey(typeof(T))) return _map[typeof(T)] as T; else throw new ApplicationException("The type " + typeof(T).FullName + " is not registered in the container"); } }
Then, we can construct a small program which creates a container, maps the types, and then queries for a service. Again a simple compact example but imagine what this would look like in a much larger application:
class Program { static SimpleDIContainer Container = new SimpleDIContainer(); static void Main(string[] args) { // Map ILogger to TextFileLogger Container.Map<ILogger, TextFileLogger>(); // Create a DirectoryWatcher using whatever implementation for ILogger contained in the DI container DirectoryWatcher watcher = new DirectoryWatcher(Container.GetService<ILogger>()); } }
Dependency Injection with Asp.Net MVC 3 and Ninject
Asp.Net MVC 3 is very well suited to dependency injection as it provides hooks and the framework for any DI vendor to create a suitable container. Whenever any controllers or views are created, they pass through the DI container for dependency resolution. It also provides a global way to retrieve dependencies from anywhere within the project.
Ninject (http://ninject.org/) is a DI container especially written for .Net and now has code to specifically make use of the new DI features in MVC 3. Coupled with the latest versions of MVC 3, VS2010 and the popular Nuget Package Manager, it is extremely easy to get up and running with DI using Ninject within an Asp.Net MVC 3 project.
Let’s run through an example of using Ninject with MVC 3 to implement our small logging framework. This tutorial requires a few bits and pieces which you should install if you haven’t done so already:
- VS2010 (Professional or Express editions)
- Asp.Net MVC 3 (http://www.asp.net/mvc/mvc3)
- Nuget Package Manager for VS2010 (http://nuget.codeplex.com/)
If you wish, you can download the complete project source to follow along with.
Once the above has been installed, let’s begin:
1) Open up VS2010 and create a new MVC 3 Web Application (I’ve called mine MvcNinjectExample):
After this screen you will get a dialog asking you to choose an Empty application or an Internet Application – just choose “Empty”.
2) Once the project has loaded, right-click ‘References’ in solution explorer and choose “Add library package reference..” (this is Nuget in action):
3) On the left, choose “Online” then search for Ninject. In the search results, you should be able to see “Ninject.Mvc3”. Select this and click the ‘Install’ button:
This will download Ninject and all the other things it needs in order to work, including the WebActivator library, which gives us a place to create our dependencies.
4) Once everything has installed, look for the ‘AppStart_NinjectMVC3.cs’ file which has now appeared in your solution and open it:
namespace MvcNinjectExample { public static class AppStart_NinjectMVC3 { public static void RegisterServices(IKernel kernel) { //kernel.Bind<IThingRepository>().To<SqlThingRepository>(); } public static void Start() { // Create Ninject DI Kernel IKernel kernel = new StandardKernel(); // Register services with our Ninject DI Container RegisterServices(kernel); // Tell ASP.NET MVC 3 to use our Ninject DI Container DependencyResolver.SetResolver(new NinjectServiceLocator(kernel)); } } }
You can see here how the Ninject Kernel is created, which is responsible for resolving our types. In RegisterServices() is where we would bind our contracts to our implementations. The DependencyResolver is used to register Ninject for type injection, which means that Ninject is used to resolve dependencies whenever they are needed by MVC.
Let’s have a quick look at how we can apply our logger to this and get our controllers using our logging framework.
5) Add a new folder called ‘Logging’ to the solution and create a new class called ILogger, and copy this code into it (overwrite the class which has been generated for you):
using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace MvcNinjectExample.Logging { public interface ILogger { void LogMessage(string message); } }
Create another class called TextFileLogger and copy this code into it, again overwriting the class which was generated for you:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.IO; using System.Web.Hosting; namespace MvcNinjectExample.Logging { public class TextFileLogger : ILogger { public void LogMessage(string message) { string path = Path.Combine(HostingEnvironment.MapPath("~/app_data"), "log.txt"); using (FileStream stream = new FileStream(path, FileMode.Append)) { StreamWriter writer = new StreamWriter(stream); writer.WriteLine(message); writer.Flush(); } } } }
You will need to include a using statement for System.IO at the top of the file to get access to the FileStream class. Once all done, you should end up with your code and solution looking like this:
Note that I have also created the App_Data directory (which is needed by the TextFileLogger) so that the log file has somewhere to go.
6) Now, head back to your “AppStart_NinjectMVC3.cs” file and set up the binding for this class:
public static void RegisterServices(IKernel kernel) { kernel.Bind<ILogger>().To<TextFileLogger>(); }
7) Finally, lets create a controller which uses it. Right-click on the ‘Controllers’ folder in Solution Explorer, select ‘Add >’ and choose the ‘Controller…’ option at the very top. Give it a name of “HomeController” and press enter.
Inside this controller, we create a constructor which takes a parameter of MvcNinjectExample.Logging.ILogger, and we’ll save that into a class-level private variable for later. Ninject is going to give us the actual implementation of that in accordance with the bindings we set up in the last step. I’ve also modified the index view so that is just returns a piece of content rather than looking for a view; this is just to prevent exceptions from being thrown as I’m not creating a view for this example. Finally, I’ve also called into the logger from the index view to log a message for us. After we run the page, we should get a log.txt file in the App_Data directory containing our message.
After my modifications, the controller now looks like this:
public class HomeController : Controller { MvcNinjectExample.Logging.ILogger _logger; public HomeController(MvcNinjectExample.Logging.ILogger logger) { _logger = logger; } public ActionResult Index() { _logger.LogMessage("Running index page!"); return Content("Message logged"); } }
This is the general pattern you will use with Dependency Injection in MVC; including your dependency contracts in your controller’s constructor, having Ninject fulfil the dependencies for you then consuming them from within the controller actions.
Let’s put a breakpoint in the constructor and see what we get when we run the project:
As we intended, without doing any additional code than what I’ve presented here, we now have an instance of ILogger passed to us when the controller was created. This has happened because MVC has effectively asked Ninject to create this controller for us. During creation, Ninject has noticed that it requires an implementation of ILogger in order to create this controller. Since we registered a binding to ILogger in AppStart_NinjectMVC3, it knows how to create one of these and so it can go ahead and completely fulfil the dependency on this controller. Hence we have Dependency Injection.
If I let the solution run again, we should hit the Index action and the text file will be created with the log message inside it:
If you were to change this to use our EventFileLogger implementation as in one of the earlier examples, you will see that all you would have to do is create the implementation, make it implement ILogger and then register it in your kernel bindings. No changes to any controllers which use it are necessary.
You should now follow this pattern when adding more dependencies to your project, and as your project scales in size you will see how easy it is to manage components in a loosely-coupled fashion even within a web project such as this. It gives you lots of flexibility and ultimately makes your projects a lot more maintainable and easy to change and adapt.