In the first part of this post series (which, honestly, I didn’t intend to need a Part 2), I discussed how you can easily create your own container for basic inversion of control (IoC) needs. However, many people may need to use Dependency Injection (DI) as well, so I’ve modified the project to support DI.
Also, a colleague pointed out that there’s another reason why you may want to do this – to simply see how it all works. Sure, you can plop Ninject into a project and start using DI/IoC, but creating your own makes you realize what’s really involved and going on behind-the-scenes.
Here I’ve modified the previous example to support DI, and it took less than an hour.
Warning: I have only briefly tested this code, and have not created unit tests. Before using in production, I’d recommend testing it thoroughly. You’ll also need to get the previous post working on your end before integrating these features. I also noticed a bug after publishing this post reading through the code that if you request an IFoo, and Foo : IFoo has a constructor that is asking for an IFoo parameter, you'll end up in an endless loop and probably stack overflow. Ideally this should detect a circular Resolve() call, and throw an exception.
The IServiceResolver Interface
This bit is not required, however I realized as I was writing the DependencyInjector class below that I was tightly coupling it with ServiceResolver from the previous post. Since this is very much an anti-pattern, I decided to make ServiceResolver implement an interface, so that you can use your own IServiceResolver implementation with this DependencyInjector class. Here’s the interface:
using System;
namespace Core
{
public interface IServiceResolver
{
void Register<TFrom, TTo>();
T Resolve<T>();
object Resolve(Type fromType);
}
}
Notice that I’ve added a non-generic version of this class. This was done for compatibility, and now the ServiceResolver class looks like the following. The major changes are it now implements the IServiceResolver interface, and there is the new non-generic method which is called by the generic method. Also, it does not throw an exception if there has not been a registration for the given type. Instead, it creates a new object and looks for any injections. This is to remove the need to bind items to themselves. Note that there is still a tight coupling in the default constructor to DependencyInjector. You may wish to remove this.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Core
{
/// <summary>
/// Resolves abstract types or interfaces to concrete implementations. Use <see cref="ServiceLocator" /> for static/shared access.
/// </summary>
public class ServiceResolver : IServiceResolver
{
private Dictionary<Type, object> _store;
private Dictionary<Type, Type> _bindings;
/// <summary>
/// Default constructor; instantiates a new ServiceResolver object.
/// </summary>
public ServiceResolver()
{
this.DependencyInjector = new DependencyInjector(this);
_store = new Dictionary<Type, object>();
_bindings = new Dictionary<Type, Type>();
}
/// <summary>
/// Creates a new ServiceResolver with a given IDependencyInjector.
/// </summary>
/// <param name="injector">The IDependencyInjector to use for dependency injection.</param>
public ServiceResolver(IDependencyInjector injector)
{
this.DependencyInjector = injector;
_store = new Dictionary<Type, object>();
_bindings = new Dictionary<Type, Type>();
}
/// <summary>
/// Gets an implementation object of a registered abstract type or interface.
/// </summary>
/// <typeparam name="T">The registered abstract type or interface to look up.</typeparam>
/// <returns>Returns an object of the given type.</returns>
public T Resolve<T>()
{
return (T)Resolve(typeof(T));
}
/// <summary>
/// Gets an implementation object of a registered abstract type or interface.
/// </summary>
/// <param name="fromType">The registered abstract type or interface to look up.</param>
/// <returns>Returns an object of the given type.</returns>
public object Resolve(Type fromType)
{
// check for registration
if (!_bindings.ContainsKey(fromType))
return DependencyInjector.GetInjectedInstance(fromType);
// get destination type
Type dest = _bindings[fromType];
// check for already requested object
if (_store.ContainsKey(dest))
return _store[dest];
// create a new instance of this type
object obj = DependencyInjector.GetInjectedInstance(dest);
// add to store for future use
_store.Add(dest, obj);
return obj;
}
/// <summary>
/// Registers a type with its corresponding implementation type.
/// </summary>
/// <typeparam name="TFrom">The abstract type or interface to use as a key.</typeparam>
/// <typeparam name="TTo">The implementation type to use as a value.</typeparam>
public void Register<TFrom, TTo>()
{
_bindings.Add(typeof(TFrom), typeof(TTo));
}
/// <summary>
/// Gets or sets a dependency injector to use for types that need injection.
/// </summary>
public IDependencyInjector DependencyInjector { get; set; }
}
}
The InjectAttribute class
In order to tell our DependencyInjector class that we want to inject something, we need to decorate either a constructor or some properties (or both) with an [Inject] attribute. If you’ve never created your own attributes, all you have to do is create a new class with a name that ends in Attribute (the “Attribute” bit is cut off on use) and make it inherit from Attribute. Here’s our empty InjectAttribute class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Core
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Constructor)]
public class InjectAttribute : Attribute
{
}
}
the IDependencyInjector Interface
Since we want to make our DependencyInjector loosely coupled with any use of it, we want to make it implement an interface.
using System;
namespace Core
{
public interface IDependencyInjector
{
T GetInjectedInstance<T>() where T : class;
object GetInjectedInstance(Type fromType);
IServiceResolver ServiceResolver { get; set; }
}
}
The DependencyInjector Class
Now that we have all of the plumbing out of the way, we can show off our new DependencyInjector class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
namespace Core
{
/// <summary>
/// A class for getting instances of objects that need dependencies injected to function.
/// </summary>
public class DependencyInjector : IDependencyInjector
{
/// <summary>
/// Creates a new DependencyInjector with a default IServiceResolver for resolving types.
/// </summary>
public DependencyInjector() : this(new ServiceResolver())
{ }
/// <summary>
/// Creates a new DependencyInjector with a given IServiceResolver for resolving types.
/// </summary>
/// <param name="resolver">The IServiceResolver to use for resolving types to implementations.</param>
public DependencyInjector(IServiceResolver resolver)
{
this.ServiceResolver = resolver;
}
/// <summary>
/// Gets or sets an IServiceResolver to use for resolving types to implementations.
/// </summary>
public IServiceResolver ServiceResolver { get; set; }
/// <summary>
/// Gets an injected instance of a given type.
/// </summary>
/// <typeparam name="T">The type to instantiate and inject.</typeparam>
/// <returns>Returns a new instance of the given type.</returns>
public virtual T GetInjectedInstance<T>() where T : class
{
return (T)GetInjectedInstance(typeof(T));
}
/// <summary>
/// Gets an injected instance of a given type.
/// </summary>
/// <param name="fromType">The type to instantiate and inject.</param>
/// <returns>Returns a new instance of the given type.</returns>
public virtual object GetInjectedInstance(Type fromType)
{
object obj = null;
foreach (var constructor in fromType.GetConstructors())
{
// look for inject attribute
var attr = GetConstructorInjectAttribute(constructor);
if (attr != null)
{
// get parameters to inject
var parmValues = GetResolvedParameterValues(constructor);
if (parmValues.Count > 0)
obj = Activator.CreateInstance(fromType, parmValues.ToArray());
break;
}
}
// handle case where no constructor injections
if (obj == null)
obj = Activator.CreateInstance(fromType);
foreach (var prop in fromType.GetProperties())
{
// look for inject attribute
var attr = GetPropertyInjectAttribute(prop);
if (attr != null)
prop.SetValue(obj, ServiceResolver.Resolve(prop.PropertyType), new object[] { });
}
return obj;
}
/// <summary>
/// Gets an InjectAttribute (if any) for the given constructor.
/// </summary>
/// <param name="constructor">The constructor to inspect for an InjectAttribute.</param>
/// <returns>Returns an InjectAttribute if exists, or null if not.</returns>
protected virtual InjectAttribute GetConstructorInjectAttribute(ConstructorInfo constructor)
{
var attrs = constructor.GetCustomAttributes(typeof(InjectAttribute), true);
return attrs.OfType<InjectAttribute>().FirstOrDefault();
}
/// <summary>
/// Gets an InjectAttribute (if any) for the given property.
/// </summary>
/// <param name="prop">The property to inspect for an InjectAttribute.</param>
/// <returns>Returns an InjectAttribute if exists, or null if not.</returns>
protected virtual InjectAttribute GetPropertyInjectAttribute(PropertyInfo prop)
{
var attrs = prop.GetCustomAttributes(typeof(InjectAttribute), true);
return attrs.OfType<InjectAttribute>().FirstOrDefault();
}
/// <summary>
/// Gets a list of parameter values for the given constructor, resolved by the ServiceResolver.
/// </summary>
/// <param name="constructor">The constructor to resolve parameters for.</param>
/// <returns>Returns a new list of parameter values.</returns>
protected virtual List<object> GetResolvedParameterValues(ConstructorInfo constructor)
{
var parms = constructor.GetParameters();
List<object> parmValues = new List<object>();
foreach (var parm in parms)
{
if (parm.ParameterType.IsInterface || parm.ParameterType.IsAbstract)
{
Type parmType = parm.ParameterType;
object parmValue = ServiceResolver.Resolve(parmType);
parmValues.Add(parmValue);
}
else
throw new InvalidOperationException("Unable to inject a parameter that is not an interface or abstract type.");
}
return parmValues;
}
}
}
I’m not going to cover line-by-line what this class does, but basically it looks for any InjectAttributes decorating a constructor or properties, and if it finds them, it resolves instances automatically using the ServiceLocator (another tight coupling) and fills them in.
Usage
Back in our ASP.NET MVC test app, we have created a new interface to use for testing, ILogger. It has one implementation (but could have many), and this one is WebLogger which writes out the log message to the current HttpResponse.
using System;
namespace CustomIoCMvc.Models
{
public interface ILogger
{
void Log(string message);
}
}
And here’s our WebLogger:
using System;
using System.Web;
namespace CustomIoCMvc.Models
{
public class WebLogger : ILogger
{
public void Log(string message)
{
HttpContext.Current.Response.Write(message);
}
}
}
We, of course, need to wire up this implementation in our Global.asax.cs:protected void Application_Start()
{
RegisterRoutes(RouteTable.Routes);
// register types
ServiceLocator.Register<ICustomerRepository, CustomerRepository>();
ServiceLocator.Register<ILogger, WebLogger>();
}
Our CustomerRepository is going to now use an ILogger for all of its calls. Here it is with a Constructor injection:
using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Collections.Generic;
using Core;
namespace CustomIoCMvc.Models
{
public class CustomerRepository : ICustomerRepository
{
[Inject]
public CustomerRepository(ILogger logger)
{
this.Logger = logger;
}
public Customer Find(int id)
{
Logger.Log("Find called with param value " + id.ToString());
return FindAll().Where(c => c.Id == id).FirstOrDefault();
}
public List<Customer> FindAll()
{
Logger.Log("FindAll called.");
var data = new List<Customer>();
data.Add(new Customer() { Id = 1, Name = "Joe" });
data.Add(new Customer() { Id = 2, Name = "Steve" });
data.Add(new Customer() { Id = 3, Name = "Karen" });
return data;
}
public ILogger Logger { get; set; }
}
}
And here’s our CustomerRepository with Property injection:
using System;
using System.Data;
using System.Configuration;
using System.Linq;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Xml.Linq;
using System.Collections.Generic;
using Core;
namespace CustomIoCMvc.Models
{
public class CustomerRepository : ICustomerRepository
{
public Customer Find(int id)
{
Logger.Log("Find called with param value " + id.ToString());
return FindAll().Where(c => c.Id == id).FirstOrDefault();
}
public List<Customer> FindAll()
{
Logger.Log("FindAll called.");
var data = new List<Customer>();
data.Add(new Customer() { Id = 1, Name = "Joe" });
data.Add(new Customer() { Id = 2, Name = "Steve" });
data.Add(new Customer() { Id = 3, Name = "Karen" });
return data;
}
[Inject]
public ILogger Logger { get; set; }
}
}
Both have been tested with the same result. But first, a little background about what’s going on here.
Our CustomersController is requesting from the service locator an instance of ICustomerRepository. It finds that we’ve bound ICustomerRepository to CustomerRepository, and goes to instantiate that class. What we’ve modified our code to do is to look, before we instantiate, if there’s a constructor with an [Inject] attribute. If there is, we fill in the constructor parameters with the needed types. If not, we go ahead and create the object with the default constructor.
Next, after we’ve instantiated the object, it looks for any properties with an [Inject] attribute, and does the same. Finally, it returns the object.
Therefore, in this case, our DependencyInjector is getting a request for a new CustomerRepository, it looks for a constructor with [Inject], sees one (in the first example) and resolves ILogger to the bound WebLogger type, gets an instance of WebLogger, and fills it in to the constructor. In the second example, it uses a default constructor but finds the [Inject] decorated Logger property, resolves ILogger to WebLogger, and sets it.
So here’s our output. You can’t really see it (because it’s a cheap quick hack), but theres a “FindAll called.” string at the top of the page:
Let me know in the comments if you have any issues or constructive criticism of this code. Hope this helps!