From the many available IoC containers out there, LinFu is the one that I like most. This is because it is extremely simple to use, needs almost no configuration at all, and yet it is highly flexible and extensible, if you need to do some more complicated things (you can read two good introductory articles about LinFu IoC here and here). During the last weeks, I was doing some stuff with the ASP.NET MVC framework, and I wondered how easy or complicated it would be to use the application's IoC container throughout the entire MVC stack, thus getting a bit closer to the realm of Interface-based programming and diving a bit deeper into the wonderful world of Dependency Injection - e.g. for model or controller creation. As it turned out, this was not very hard to do. Here's how.
Setting the Stage (the "domain")
Let's say you have the following 'domain model' (which is shamelessly copied from the LinFu examples and then slightly modified...):
Consequently, when using a LinFu ServiceContainer
, you will have code similar to this to create a car
along with its related engine
and driver
:
[Test]
public void CanCreateCarWithDependenciesFromContainer()
{
var car = container.GetService<IVehicle>();
Assert.Multiple(() =>
{
Assert.IsNotNull(car);
Assert.IsNotNull(car.Engine);
Assert.IsNotNull(car.Driver);
});
}
This works because the LinFu container sees that the ICar
-implementing class has a constructor which takes an IEngine
and an IPerson
instance as arguments. So far, this is classical constructor-injection...
Using the IoC Container for Controller Creation
Now onto more MVC-specific things: Let's create a custom ControllerFactory
class to do controller creation by means of our LinFu container. - In an ASP.NET MCV application, the controller factory is (you already might have suspected that ;-)) responsible for creating controller instances in reaction to URL requests. - MVC's default factory requires a parameterless constructor to be declared on the requested controller class. With our LinFu container, we don't have that limitation, but we can use Dependency injection instead. Here's the declaration of our new factory class:
public class LinFuControllerFactory : DefaultControllerFactory
{
protected ServiceContainer Container { get; private set; }
public LinFuControllerFactory(ServiceContainer serviceContainer)
{
if (serviceContainer == null)
{
throw new ArgumentNullException("serviceContainer");
}
this.Container = serviceContainer;
}
protected override IController GetControllerInstance(Type controllerType)
{
if (controllerType == null)
{
throw new HttpException(404, string.Format(
"The controller for path '{0}'
could not be found or it does not implement IController.",
RequestContext.HttpContext.Request.Path));
}
return (IController)controllerType.AutoCreateFrom(this.Container);
}
}
As you can see, creating a controller of the desired type through a LinFu container is essentially a one-liner with the AutoCreateFrom()
method. If the controller has a non-default c'tor, then the container will also take care of the controller's dependencies. There is no additional configuration or whatever necessary - it just works, if you have announced the required assembly (containing the controller) to the LinFu container (see below)...
Using the IoC Container for Model Binding
Controller creation is the first area where using an IoC container can be useful, the second one is model binding. What I especially like about it, is that this technique allows for using interfaces instead of concrete classes both in views and controllers.
In the view's .aspx, you can write:
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewPage<My.Model.IVehicle>" %>
...
...and a related controller action might be declared like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Edit(IVehicle car)
{
...
To make this happen, we use a custom model binder that converts the form's values to an ICar
instance:
public class VehicleModelBinder : TypedLinFuModelBinder<IVehicle>
{
public VehicleModelBinder(ServiceContainer serviceContainer) : base(serviceContainer)
{
}
protected internal override IVehicle CreateModelFromFormValues
(NameValueCollection formValues)
{
var engine = this.GetService<IEngine>(formValues["Engine.SerialNumber"]);
var driver = this.GetService<IPerson>(formValues["Driver.Name"],
Convert.ToInt32(formValues["Driver.Age"]));
return this.GetService<IVehicle>(engine, driver);
}
}
The generic TypedLinFuModelBinder
base class is initialized with a service container instance and declares some convenience methods, particularly the CreateModelFromFormValues()
method, which serves to handle the common "take the form's values and create a new object from them" scenario. It is included in the sample solution, which you can download from here. (The internal declaration serves to make testing easier...)
Putting It All Together
First, it's helpful to place the LinFu ServiceContainer
behind a singleton facade, so that we have a unique access point to it throughout the entire application. I usually have an extra assembly for such things (IoC, Logging, custom attributes, etc.; named Infrastructure
or something similar). The singleton to hold the ServiceContainer
instance might look something like this:
public static class DI
{
public static ServiceContainer ServiceContainer { get; private set; }
static DI()
{
string directory =
AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;
if (string.IsNullOrEmpty(directory))
{
directory = AppDomain.CurrentDomain.BaseDirectory;
}
ServiceContainer = new ServiceContainer();
ServiceContainer.LoadFrom(directory, "My.DomainAssembly.dll");
ServiceContainer.LoadFrom(directory, "My.AspNetMvcAssembly.dll");
}
}
The last thing that's left to do is to register both our LinFuControllerFactory
and our custom model binder with the application on startup. This is done in the Application_Start()
event handler in Global.asax.cs, where these two lines must be added:
ControllerBuilder.Current.SetControllerFactory
(new LinFuControllerFactory(DI.ServiceContainer));
ModelBinders.Binders[typeof(IVehicle)] = new VehicleModelBinder(DI.ServiceContainer);
The Sample Solution
A sample solution (VS 2008) is available here. It contains the LinFuControllerFactory
shown here and the LinFu-specific model binder base classes, which are used in the above example.
CodeProject