An Unexpected Benefit of Using an IoC Container

posted on 10/13/08 at 12:57:22 am by Joel Ross

In the past few weeks, I've basically come full circle on the usage of dependency injection frameworks and Inversion of Control containers. Back in March, I was questioning whether one was necessary, since I hadn't seen a need to do anything so complex that I couldn't wire it up by hand.

It turns out there's a bit of a chicken and egg thing going on here. I didn't need a container because my software wasn't complex enough to warrant one, and my software wasn't complex enough because I didn't have a container to manage that complexity.

That changed a few weeks ago, when I started down the road of working with repositories in conjunction with MVC. That gave me an opportunity to start from the ground up with a container. As a result, wiring pieces together became trivial. That, in turn, allowed me to build interactions between components that before would have been very difficult to manage.

But that's not what this post is about. As I started to get into using my container, I discovered a side benefit I hadn't considered before. I'll demonstrate it through a refactoring that I went through to get my code in better shape. First up, how the code started out:

   1:  public static class MyAppContext
   2:  {
   3:    public User CurrentUser
   4:    { 
   5:      get
   6:      {
   7:        return HttpContext.Current.Items["CurrentUser"] as User;
   8:      }
   9:      set
  10:      {
  11:        HttpContext.Current.Items["CurrentUser"] = value;
  12:      }
  13:  }

It's a simple example that maintains the current user for the duration of an HTTP request. Nothing fancy, but this type of model gets used a lot. At first glance, it doesn't look too bad. But there's issues here. It's static, which means I can't do much to test it (or code that relies on it). And even if I refactored it so it wasn't static, there's that HttpContext.Current statement in there, which would still make it un-testable (or at least difficult).

The other problem is that this class has multiple responsibilities. I never saw it before, but now that I've noticed it, it sticks out like a sore thumb. This class is responsible for not only giving access to the current user, but also how that current user is stored - essentially, this class is managing it's own lifecycle. That's a violation of SRP.

So, we take a two-pronged attack to fix it. First, make it non-static. That's trivial, so I won't show that. Second, inject the storage into the class. Come up with a simple interface for storage:

   1:  public interface IStorage
   2:  {
   3:    public void Set<T>(string key, T value);
   4:    public T Get<T>(string key);
   5:  }

Then, create an implementation that uses HttpContext to achieve what we had before:

   1:  public class ContextStorage : IStorage
   2:  {
   3:    public void Set<T>(string key, T value)
   4:    {
   5:      HttpContext.items[key] = value;
   6:    }
   8:    public T Get<T>(string key)
   9:    {
  10:      return HttpContext.Current.Items[key] as T;
  11:    }
  12:  }

Then we change the original MyAppContext to use the interface, rather than HttpContext directly:

   1:  public class MyAppContext
   2:  {
   3:    private IStorage _storage;
   5:    MyAppContext(IStorage _storage)
   6:    {
   7:      _storage = storage;
   8:    }  
  10:    public User CurrentUser
  11:    { 
  12:      get
  13:      {
  14:        return _storage.Get<User>("CurrentUser");
  15:      }
  16:      set
  17:      {
  18:        _storage.Set<User>("CurrentUser", value);
  19:      }
  20:  }

This is pretty good. Now, the class delegates to another class to handle how it's data is stored, and I could create an implementation of IStorage that uses a dictionary internally and pass it to MyAppContext for testing purposes, without ever changing my code.

Now, the next question. How do you manage MyAppContext now that it's not static? How do you create one? How do you keep track of it throughout it's duration? Who is responsible for it's lifecycle? All valid questions, and all difficult to handle manually. But if I'm using an IoC container, I can let the container manage the life cycle for me. And it does it outside of the class, so MyAppContext becomes even simpler, because it can use normal techniques for storage:

   1:  public class MyAppContext
   2:  {
   3:    public User CurrentUser { get; set; }
   4:  }

That was definitely a long-winded explanation of how I got from a static, HTTP-based class to your every day run of the mill class, yet still get the same functionality with no real extra cost. Well, no cost except the IoC container implementation. Which brings me full circle, and back to what the title of this post alludes to: Using an IoC container allows me to easily implement and use classes that are ignorant of their surroundings. When I first started down the IoC path, I knew the benefits I expected - being able to program against interfaces and let my container figure out how to inject the actual implementations. But what I didn't expect was to be able to rely on the container to manage the lifecycle of my objects and allow me to write code the way I normally would.

Tags: | |

Categories: Development, C#