IOC seems to be a bit of a buzzword again recently with frameworks like Asp.Net MVC popping up allowing the possibility of plugging custom Controller Factory objects directly in.
That's great, but sometimes you just don't want the baggage of a Container hanging around, especially if you are writing a small-ish solution. I faced that scenario today and stumbled upon what I think is a really elegant solution!
The problem... I want to inject dependencies into Controller classes, but don't want to use a container.
The solution... OK, bear with me it's easy!
In the global application_onstart method I have the opportunity to construct a custom ControllerFactory and pass it to Asp.Net. I won't cover that. Try here for more information.
My initial thought was to have a ControllerFactory expose a Register<T>(string name) where T:Controller method. That would let me register a Controller against a name, and then do an Activator.CreateInstance to create it. But of course in the real world the whole point of IoC is that the Controller will have dependencies it needs satisfying so this approach is kind of lacking!
One option I thought of was to have a Builder class for each Controller which would know how to create its Controller. I could RegisterBuilder<T>(Builder<T> builder) where T:Controller. Now my ControllerFactory can grab the appropriate builder and just ask it to Build(). The builder would be specific to a Controller so can create it concretely. It works, but it could lead to an explosion of Builder classes.
Then I remembered my old friend Func<>. It's nothing new really and is just a concrete implementation of a Delegate we could have written in .Net 2.0 - delegate T Func<T>() I think is the signature- don't have Reflector to hand. It allows me to do something like this:
ControllerFactory.Register<T>(Func<T> buildController) where T:Controller.
My global.asax can now look something like this
var provider = new DbConnectionProvider(Configuration.ConnectionStrings[....]);
var customerRepository = new CustomerRepository(provider);
var controllerFactory = new ControllerFactory();
controllerFactory.Register<HomeController>(() => new HomeController(customerRepository));
When a request comes in to the ControllerFactory it can say something like:
Dictionary<Type, Func<Controller> _controllerDictionary;
...
_controllerDictionary[controllerTypeToBuild]()
The syntax might look a bit weird, but essentially the dictionary contains functions which build a particular type. So when we grab something from the dictionary we just have to execute it hence the () call.
No container, no builder classes. Just everything wired up ready to go in my application start-up.
That might pose an architectural question of 'What do you mean by wiring up your application at start-up?' To answer than let me link you to Misko Hevery's blog - a google guy with some awesome posts on writing testable applications.
Thursday, January 15, 2009
Subscribe to:
Post Comments (Atom)
3 comments:
But contianers are easy! I often use a generic wrapper for mine so the team dont even have to know that they are using Unity/Castle/Spring etc.
The following interface covers 99% of what i need
/// <summary>
/// Interface for all IoC containers
/// </summary>
public interface IContainer
{
/// <summary>
/// Adds the component.
/// </summary>
/// <typeparam name="T">The abstracted class or interface</typeparam>
/// <typeparam name="K">The concrete implementation of the abstracted class or interface</typeparam>
void AddComponent<T, K>() where K : T;
/// <summary>
/// Adds the component.
/// </summary>
/// <typeparam name="T">The abstracted class or interface</typeparam>
/// <typeparam name="K">The concrete implementation of the abstracted class or interface</typeparam>
/// <param name="componentReferenceName">Name of the component reference.</param>
void AddComponent<T, K>(string componentReferenceName) where K : T;
/// <summary>
/// Adds the component.
/// </summary>
/// <typeparam name="T">The abstracted class or interface</typeparam>
/// <typeparam name="K">The concrete implementation of the abstracted class or interface</typeparam>
/// <param name="componentLifecycle">The component life cycle.</param>
void AddComponent<T, K>(Lifecycle componentLifecycle) where K : T;
/// <summary>
/// Adds the component.
/// </summary>
/// <typeparam name="T">The abstracted class or interface</typeparam>
/// <typeparam name="K">The concrete implementation of the abstracted class or interface</typeparam>
/// <param name="componentLifecycle">The component life cycle.</param>
/// <param name="componentReferenceName">Name of the component reference.</param>
void AddComponent<T, K>(Lifecycle componentLifecycle, string componentReferenceName) where K : T;
/// <summary>
/// Resolves the concrete of type T.
/// </summary>
/// <typeparam name="T">The base class or interface used for component abstraction.</typeparam>
/// <returns>An instance of the registered implementation of the type T</returns>
T Resolve<T>();
/// <summary>
/// Resolves the concrete instance of type T by the specified reference name.
/// </summary>
/// <typeparam name="T">The base class or interface used for component abstraction.</typeparam>
/// <param name="componentReferenceName">Name of the component reference.</param>
/// <returns>An instance of the registered implementation of the type T, by the specified reference name.</returns>
T Resolve<T>(string componentReferenceName);
Jumping on the bandwagon....
I am interested to know under what conditions you feel that this scenario is better than using a container?
ie: is the project size eg too small; Team don't want to use a container; you are a lazy POM; you are a framework-building tyrant that works for a bank....? ;-)
The container available today are pretty lightweight aren't they?
@the Campbell Brothers - the main thinking is I can run with the minimal amount of dependencies to get the application going, and it's trivial to switch to the additional complexity of a Container if I need it.
For the record, I've got nothing against containers at-all - I wrote my own back in 2004 on the back of using Spring in Java - but all I ended up using it for was an overcomplicated configuration system. Add Xml-glue to declaratively describe dependencies (and I know not all containers force this on you) and yuk, yuk yuk...
As Lee once said to a famous "architect" at a bank, we've got a framework - the .Net framework and its pretty good!
Post a Comment