Custom IoC Container for Dependency Injection with an Asp.Net Mvc website usage example
I’ve recently been working on some mvc helper controls and wanted to use TDD for both aiding the design and having a test harness for the future.
In order to aid the use of tdd and improve the overall design I wanted to utilise dependency injection.
There are loads of good IoC containers out there which include facilities for dependency injection for example Structure Map (really like this one), Spring.Net (I use this at my real job), Castle Windsor etc. etc to name but a few.
For my controls I wanted a container that was extremely lightweight and fast so I decided it would be really interesting to have a go at rolling my own, I know it’s re-inventing the wheel slightly but I’m a great believer in the worth of knowing how things are done (I imagine most of us in this trade are the same in that regard).
Anyway, I’ve really enjoyed building this and would welcome anyone trying it out – hopefully people will find it both powerful and really easy to use (I’ve tried to make the syntax really intuitive and fluent).
I’ve named it RapidIoc (I do plan to add some extra facilities over time to beef it up a little – probably some AOP support).
Download
Overview
The dll stands at 20.5KB (nice and small).
The key features include:
- Automatic and manual wiring
- Constructor injection
- Property injection
- Fluent interface for object registration
- Supports per-request, singleton and http context object resolving (http context resolving will only work in web apps)
- Automatic wiring can be overridden
Setup
Setting up the container is really simple – you just need to add a reference to the dll.
There aren’t any handlers of configurations as it’s all handled within the dll.
Tutorial
As a lot of people are starting to use dependency injection with the ASP.Net MVC framework, I’ve decided to do a little tutorial on hooking it up with a basic MVC app (included in the download.)
Step 1 – Add a reference
So, first thing to do is create a new Asp.Net Mvc application and add a reference to the RapidIoc.dll.
Step 2 – Create a controller factory
Set up a controller factory to hand over creating the controllers to the RapidIoc container.
Create a class similar to the following:
- using System;
- using System.Web.Mvc;
- using RapidIoc;
- namespace RapidIocTestSite
- {
- public class RapidIocControllerFactory : DefaultControllerFactory
- {
- protected override IController GetControllerInstance(Type controllerType)
- {
- if (controllerType == null)
- {
- return null;
- }
- IController controller = (IController)IocContainer.TryResolve(controllerType);
- if (controller != null)
- {
- return controller;
- }
- else
- {
- return base.GetControllerInstance(controllerType);
- }
- }
- }
- }
In the Global.asax file, add the following line to the Application_Start method:
- protected void Application_Start()
- {
- RegisterRoutes(RouteTable.Routes);
- ControllerBuilder.Current.SetControllerFactory(typeof(RapidIocControllerFactory));
- }
That concludes the setup for the RapidIoc container, nice and simple.
Step 3 – Create your objects
This will be a bit of a contrived example but it will demonstrate a good number of features of the container.
Create the following models:
- public interface ICustomer
- {
- string FirstName { get; set; }
- IContact ContactDetails { get; set; }
- }
- public class Customer : ICustomer
- {
- public string FirstName { get; set; }
- public IContact ContactDetails { get; set; }
- }
- public interface IContact
- {
- string TelephoneNumber { get; set; }
- IAddress CustomerAddress { get; set; }
- }
- public class Contact : IContact
- {
- public string TelephoneNumber { get; set; }
- public IAddress CustomerAddress { get; set; }
- }
- public interface IAddress
- {
- int HouseNumber { get; set; }
- string PostCode { get; set; }
- }
- public class Address : IAddress
- {
- public int HouseNumber { get; private set; }
- public string PostCode { get; private set; }
- public Address() { }
- public Address(int houseNumber, string postCode)
- {
- this.HouseNumber = houseNumber;
- this.PostCode = postCode;
- }
- }
Next thing to do is add a property to the HomeController: this will be the property that we’re going to inject.
Also set the ViewData.Model to the property as we are going to use that later in the view.
For example, your home controller should look something like the following:
- public class HomeController : Controller
- {
- /// <summary>
- /// This is the prpoerty we're going to inject via the Ioc Container.
- /// </summary>
- /// <value>The customer.</value>
- public ICustomer Customer { get; set; }
- public ActionResult Index()
- {
- this.ViewData.Model = this.Customer;
- return View();
- }
- }
Step 4 – Register your objects
This is the last step in setting up the application for injection as the object resolution is handled within the controller factory you set up in step 1.
Firstly create a class similar to the following:
- using RapidIoc;
- namespace RapidIocTestSite
- {
- /// <summary>
- /// Ioc Registration.
- /// </summary>
- public class IocRegistration
- {
- public void Register()
- {
- }
- }
- }
Then call the Register method from the application start method in the Global.asax:
- protected void Application_Start()
- {
- RegisterRoutes(RouteTable.Routes);
- ControllerBuilder.Current.SetControllerFactory(typeof(RapidIocControllerFactory));
- IocRegistration iocRegistration = new IocRegistration();
- iocRegistration.Register();
- }
There are two ways to use the container to inject your properties: Automatically and manually. (Note: automatic wiring does not work when using complex types for constructor injection – only property injection).
You can use either or a combination of these ways to register your objects.
Automatic Wiring Example
When using automatic wiring, the IocContainer inspects each class in the hierarchy (from the HomeController in this case) and checks if a registered object exists for each property interface type, if a registered object exists for a property it is injected.
For example, if you registered multiple controllers with an ICustomer property, they would all be automatically populated with the Customer instance. The Customer class IAddress property would then be automatically injected with the Address class and so on.
- /// <summary>
- /// Ioc Registration.
- /// </summary>
- public class IocRegistration
- {
- public void Register()
- {
- #region Controllers
- IocContainer.Register<HomeController, HomeController>
- (
- Wiring.Manual,
- Scope.Request
- );
- #endregion
- #region Customers
- IocContainer.Register<ICustomer, Customer>
- (
- Wiring.Automatic,
- Scope.Request,
- Properties.Configure()
- .Inject<ICustomer>(x => x.FirstName, "Sean")
- )
- .Register<IContact, Contact>
- (
- Wiring.Automatic,
- Scope.Request,
- Properties.Configure()
- .Inject<IContact>(x => x.TelephoneNumber, "12345 678910")
- )
- .Register<IAddress, Address>
- (
- Wiring.Automatic,
- Scope.Request,
- ConstructorParameters.Configure()
- .Inject(123)
- .Inject("SW10 5ZZ")
- );
- #endregion
- }
- }
Manual Wiring Example
Manual wiring involves explicitly setting each property or constructor for injection. Any manually wired injection will override any automatically wired injection.
- public void Register()
- {
- #region Controllers
- IocContainer.Register<HomeController, HomeController>
- (
- Wiring.Manual,
- Scope.Request,
- Properties.Configure()
- .Inject<HomeController>(x => x.Customer, Resolve.Object<ICustomer, Customer>())
- );
- #endregion
- #region Customers
- IocContainer.Register<ICustomer, Customer>
- (
- Wiring.Manual,
- Scope.Request,
- Properties.Configure()
- .Inject<ICustomer>(x => x.FirstName, "Sean")
- .Inject<ICustomer>(x => x.ContactDetails, Resolve.Object<IContact, Contact>())
- )
- .Register<IContact, Contact>
- (
- Wiring.Manual,
- Scope.Request,
- Properties.Configure()
- .Inject<IContact>(x => x.TelephoneNumber, "12345 678910")
- .Inject<IContact>(x => x.CustomerAddress, Resolve.Object<IAddress, Address>())
- )
- .Register<IAddress, Address>
- (
- Wiring.Manual,
- Scope.Request,
- ConstructorParameters.Configure()
- .Inject(123)
- .Inject("SW10 5ZZ")
- );
- #endregion
- }
Note: when using Resolve.Object<TTypeToResolve, TConcrete> to inject a property or constructor parameter it creates a new instance of the generic parameter TConcrete type passed in. It then checks to see if a registered object exists with the same TTypeToResolve and TConcrete, if one exists it will be injected from the registered objects collection. Basically what this means is if you call Resolve.Object on an object that is not registered, it will create a default instance of the concrete type and inject that.
Scope
In the above example I’ve set everything to Request scope.
Other scopes available are singleton and http context. I imagine it’s fairly obvious what they do but for example, if your controller has a Repository property that should be treated as a singleton, you just set the scope to Singleton.
View
To prove that it is working, add the following to the Home Index view and you should see the data you injected printed out.
- <% if (this.Model != null)
- { %>
- <strong>Customer</strong><br />
- Firstname : <%= this.Model.FirstName %><br />
- <strong>Contact Details</strong><br />
- Telephone: <%= this.Model.ContactDetails.TelephoneNumber %><br />
- <strong>Address</strong><br />
- House number: <%= this.Model.ContactDetails.CustomerAddress.HouseNumber %><br />
- Post code: <%= this.Model.ContactDetails.CustomerAddress.PostCode %>
- <% } %>
Overloads
There are various overloads which should be useful, I’ve commented all the public methods so hopefully it should be fairly easy to use.
Conclusion
So to conclude, it has been a really enjoyable experience building a container instead of just consuming one of the usual frameworks, I would recommend everyone re-inventing the wheel every now and then.
If anyone tries it out, I’d love to hear how you get on, from my initial tests it seems to work really well (but I am probably fairly biased).
If anyone thinks it’s a good approach and would be worth turning into a real project, let me know and I could possibly look at starting an open source project.
Kind Regards,
Sean McAlinden