Dependency Injection With A Framework

posted on 03/29/08 at 09:56:54 pm by Joel Ross

In a recent post, I laid out how I went about adding Dependency Injection to a particular class to give it flexibility and make it easier to maintain over time. At the end, I mentioned I'd never used a DI framework, mainly because I hadn't felt the need to do so. In the comments, Matt Blodgett mentioned that he'd like to see a simple example using a framework, and I agreed.

So last night, that's exactly what I did. I picked Castle Windsor, since that seemed to be a popular one, recommended by Michael Eaton and first on Scott Hanselman's list of DI frameworks.

All in all, it took about 30 minutes to get my existing code base to use it, and that included the time it took me to read the documentation to figure out what I'm doing. I'm obviously not an expert, and there's more I would like to learn, but I did get a basic example working.

For reference, here's our User class that we've been working with:

   1:  public class User
   2:  {
   3:     private IServiceAPI _service;
   4:     public IServiceAPI Service
   5:     {
   6:        get { return _service;}
   7:     }
   8:   
   9:     private string _userName;
  10:     public string UserName
  11:     {
  12:        get { return _userName; }
  13:     }
  14:   
  15:     public User(string userName, IServiceAPI service)
  16:     {
  17:        _userName = userName;
  18:        _service = service;
  19:     }
  20:   
  21:     public void SendMessageTo(string message)
  22:     {
  23:        service.SendMessage(String.Format("@{0} - {1}", this.UserName, message));
  24:     }
  25:  }

I realized after I posted my original post that I never showed what code to use this class would look like, but if you're not using a framework, it might look something like this:

   1:  IServiceAPI service = new TwitterAPI();
   2:  User user = new User("RossCode", service);

That's not really that complicated because we're just using one service, but if you start to get a few different services, you can see how complicated this could get.

To use Windsor, your code to instantiate the User class looks like this:

   1:  IWindsorContainer container = new WindsorContainer(new XmlInterpreter(new ConfigResource("castle")));
   2:  IDictionary parameters = new HashTable();
   3:  parameters.Add("userName", "RossCode");
   4:  User user = container.Resolve<User>(paramters);

We use a dictionary to pass any non-injected parameters - our User class expects a username and an IServcieAPI implementation. We know the username, so we pass that in directly. We let the framework inject the IServiceAPI implementation.

There's actually more code to do it this way, but here's the key - this code doesn't change if I want to use a different IServiceAPI implementation. In the first example, I would have to add or change the code to get another IServiceAPI implementation. With the new way, the code doesn't change - Just the configuration. There's a decoupling advantage that I'll touch on later as well.

I did everything through configuration. It doesn't have to be, but that's how I did it. Let's take a look at the configuration file:

   1:  <configuration>
   2:    <configSections>
   3:      <section 
   4:        name="castle" 
   5:        type="Castle.Windsor.Configuration.AppDomain.CastleSectionHandler, Castle.Windsor" 
   6:      />
   7:    </configSections>
   8:    <castle>
   9:      <components>
  10:        <component 
  11:          id="business.user" 
  12:     type="Business.User, Business" 
  13:        />
  14:        <component 
  15:          id="service.twitter" 
  16:     service="Interfaces.IServiceAPI, Interfaces" 
  17:     type="Twitter.TwitterAPI, Twitter" 
  18:        />
  19:      </components>
  20:    </castle>
  21:  </configuration>

The first just sets up Castle as a valid configuration section. The key parts are in the <castle> section. I'm only using components - there's a lot more I could do here, but I don't need anything more than this. I've registered two components. The first (line 10) is the type I want to inject into, and the second (line 14) is the type I want to inject with. So, Business.User is the class that I want Windsor to construct for me. It'll look for a constructor, which in this case, expects a username, which we supplied, and an IServiceAPI-supported type. The second component has a service attribute, saying that it can be used when you need to find an IServiceAPI type. Then it specifies the concrete type to use - Twitter.TwitterAPI. So when Windsor tries to construct the first component (Business.User), it'll end up resolving the IServiceAPI parameter to Twitter.TwitterAPI. All of this happens with nothing more than some configuration settings!

Now let's say we want to swap out TwitterAPI for our ConsoleAPI class. Before, we'd have to modify the code to create a ConsoleAPI instance and pass it in. Now we just change our configuration to add a new service and specify which service to use:

   1:    <castle>
   2:      <components>
   3:        <component 
   4:          id="business.user" 
   5:         type="Business.User, Business" 
   6:        >
   7:          <parameters>
   8:            <service>${service.console}</service>
   9:         </parameters>
  10:        </component>
  11:        <component 
  12:          id="service.twitter" 
  13:         service="Interfaces.IServiceAPI, Interfaces" 
  14:          type="Twitter.TwitterAPI, Twitter" 
  15:        />
  16:        <component 
  17:          id="service.console" 
  18:          service="Interfaces.IServiceAPI, Interfaces" 
  19:         type="Console.ConsoleAPI, Console" 
  20:        />
  21:      </components>
  22:    </castle>

I've added a second implementation for the IServiceAPI service, and specified which implementation using the <parameters> element. the <service> element is what to pass in as the service argument in the Business.User constructor. In this case, I specified to use Console.ConsoleAPI, which would be another service that implements IServiceAPI. I can easily switch between the TwitterAPI and ConsoleAPI just by changing one config setting - no need to recompile. And I can add new IServiceAPI implementations in the future, add them as components, set it as the one to use for Business.User, and I'm ready to go - no code changes.

That last point brings me to another advantage I see: Decoupling. Before, my client code was tightly coupled to the TwitterAPI implementation. My User class wasn't, but the client using it was. Doing dependency injection by hand removed some coupling, but it couldn't remove as much as using a framework did. In the above scenario, I have five assemblies - Business, Twitter, Console, Interfaces, and Client (the only one not listed, but it would be the client code containing the Windsor code). Client doesn't need to have a reference to Console or Twitter, but can still use the classes inside of them. The assemblies just need to be dropped into the same folder as the executable. And if I ever add a Jaiku assembly, that just needs to be dropped in and configured, and suddenly my code can use it - without compilation.

One other thing to note, and then I'll wrap it up. My User class was unchanged. Some frameworks use attributes to specify where to inject to, but that would require changes to the User class. Windsor doesn't (or at least doesn't have to - I'm still learning!), so I'm free to use the class with a framework or I can do the DI by hand.

Overall, I'm really impressed with Castle Windsor, and I think I can see it's usefulness. To be completely honest, the recompilation (or more specifically, not having to recompile) isn't that big of a deal to me - I rarely am in a situation where I want to add functionality but recompiling the software is out of the question. But breaking a(nother) dependency is always a good thing, and being able to use configuration over code seems like a Good Thing to me.

Bottom line: I think I'll be looking at this a bit for future projects.

Categories: Development, C#