In this post, we will discuss the process we use to refactor an existing application to use Inversion of Control.  The main goal of this process (aside from the obvious) is to keep the application in a release state throughout the process.  As long as they are done in order, these steps can be done in any sprint and the application may be released at sprint end.

Our Assumptions

For this post to make sense, there were some assumptions we had to make while writing it.

Assumption 1:  .NET

The first assumption is that the target application is written in ASP.NET.  Unfortunately, there is no IoC Container that exists for Classic ASP, so converting an existing ASP application is not possible.  This article DOES apply for hybrid Classic/ASP.NET projects, but only for the .NET side.

Assumption 2: Continuous Deployment

Alobria is an Agile development shop, and we practice continuous deployment and continuous integration.  The process for introducing IoC embraces these practices by breaking the process up into small, stand-alone steps.  Each of the steps below may be done individually and in a single sprint without making the application unreleasable.  During sprint planning, you can pick up one or more of these tasks along with any other backlog items, and still release at the end of the sprint.

We developed the process like this because the majority of our work is with existing systems that cannot be taken down for maintenance.  The steps were designed to never get in the way of the release schedule.

Introducing IoC

Though Inversion of Control is a software architectural pattern that has been around for a while, there is a significant number of legacy applications that do not use it, but would benefit from the loose coupling it provides.  The most common implementation of IoC, Dependency Injection, is difficult to implement in existing applications.  A basic assumption of IoC Containers is that all necessary objects can be resolved using the container.  This means that legacy applications must be converted all-or-nothing.  Each and every class and service must be converted to DI in one release for it to work.

To see why this is so, consider the following dependency graph.

Dependency Graph

In this graph we can see that the classes are tightly coupled. In other words, there are hard references from one class to another.  If we were to try to convert this application to Dependency Injection, we might start with the Controller.  However, that requires references to the DAL, EmailService, APIServices, and ErrorLogger so the IoC Container must be able to resolve each of these for the controller.  To be resolved, each class that these classes depend upon must also be resolvable, etc.  As the dominoes fall, we see that we must convert all classes at once or this approach will not work.

However, there is a solution.  This article will lead step by step through the process of converting an existing application to use Dependency Injection, without the need for rewriting the application or taking it down for an extended period of time.

Interfaces

One of the core reasons we convert applications to IoC is for loose coupling.  This is achieved by updating the calling code to use an interface instead of public methods on the object itself.  (There are many discussions of when, why, and how Interfaces should be used – check here, here, and here.)   This requires that all objects we want to store in the container be associated with an interface.  Luckily, converting an object to an interface is a trivial prospect.

Interfaces can be introduced without changing the logic of any of your code.  It is simply a slight change in references in the code.  Logically, the execution is exactly the same, which minimizes the QA requirements.

First decide what namespace you will put the interface in.  You can put the interface in the same project as the logic, but i prefer to have it in a different namespace (project).  I normally have a project called Infrastructure where I keep generic logic that has no knowledge of the specific application, such as string manipulation utilities, an email sending service, etc.  If you have a similar project, this is a good location for the interface, or you can create a project specifically for holding interfaces.

When creating the interface itself and attaching it to the class, there are many automated tools that can help.  If you don’t happen to have one handy, you can easily create an interface by hand by simply exposing all the public methods and properties in the concrete class.  For example, consider the following class.

Example Class Before Interface
  1. namespace MyInternalNamespace
  2. {
  3.     /// <summary>
  4.     /// An implementing class MUST provide the appropriate
  5.     /// items described in the interface or the project will not build.
  6.     /// The implementing class can add any additional
  7.     /// features deemed appropriate.
  8.     /// </summary>
  9.     public class Example
  10.     {
  11.         /// <summary>
  12.         /// Implement the StringProperty described in the interface
  13.         /// </summary>
  14.         public string StringProperty { get; set; }
  15.         /// <summary>
  16.         /// The ReadOnlyStringProperty cannot have a public setter,
  17.         /// but can have a private, protected, or internal setter.
  18.         /// </summary>
  19.         public string ReadOnlyStringProperty { get; internal set; }
  20.         /// <summary>
  21.         /// The implementing class must provide the method signatures
  22.         /// described in the interface, though if no logic is
  23.         /// added to the method, it will still build.
  24.         /// </summary>
  25.         public int OperateOnTwoNumbers(int pNum1, int pNum2)
  26.         {
  27.             return pNum1 + pNum2;
  28.         }
  29.         /// <summary>
  30.         /// Any further customization of the implementing
  31.         /// class is allowed
  32.         /// </summary>
  33.         public bool MethodNotInTheInterface()
  34.         {
  35.             return true;
  36.         }
  37.         /// <summary>
  38.         /// Methods or properties that are private cannot be exposed.
  39.         /// </summary>
  40.         private bool MethodCannotBeExposed()
  41.         {
  42.             return false;
  43.         }
  44.     }
  45. }

 

Implementing an interface for this class is a matter of simply copying the signature of each public method or property.  Here is the interface for this class.

Example Interface
  1. namespace MyInterfaceNamespace
  2. {
  3.     /// <summary>
  4.     /// Interfaces only describe the properties, indexers,
  5.     /// methods, and events that a class exposes for use.
  6.     /// Logic to implement these features is not allowed in
  7.     /// an interface.
  8.     /// </summary>
  9.     public interface IExample
  10.     {
  11.         /// <summary>
  12.         /// Example of a string property the implementing class
  13.         /// must provide.
  14.         /// </summary>
  15.         string StringProperty { get; set; }
  16.         /// <summary>
  17.         /// Example of a read-only property
  18.         /// </summary>
  19.         string ReadOnlyStringProperty { get; }
  20.         /// <summary>
  21.         /// Example of a methods that accepts two parameters.
  22.         /// </summary>
  23.         int OperateOnTwoNumbers(int pNum1, int pNum2);
  24.     }
  25. }

 

Once this is done, scan through the code and ensure that any references to the concrete class are changed to be references to the interface.  The only exceptions are the locations where the class is created – these must remain references to the class. For example, this is how our Example class would have been used before introducing the interface.

Using the class before introducing the interface
  1. namespace MyInternalNamespace
  2. {
  3.     /// <summary>
  4.     /// This is an example of a class that does not use the interface
  5.     /// </summary>
  6.     public class BusinessLogic
  7.     {
  8.         public int UseInterfaceImplementation()
  9.         {
  10.             //we create and use the concrete class
  11.             Example sampleClass = new Example();
  12.             return sampleClass.OperateOnTwoNumbers(1, 2);
  13.         }
  14.         /// <summary>
  15.         /// We can also pass the class as a parameter
  16.         /// </summary>
  17.         public string AcceptClassAsParam(Example param)
  18.         {
  19.             return param.ReadOnlyStringProperty;
  20.         }
  21.     }
  22. }

 

This is how the calling code would look after converting to the interface.

After Interface Conversion
  1. namespace MyInternalNamespace
  2. {
  3.     /// <summary>
  4.     /// This is an example of a class that uses the interface we just created.
  5.     /// </summary>
  6.     public class BusinessLogic
  7.     {
  8.         public int UseInterfaceImplementation()
  9.         {
  10.             //Assign to an IExample variable, not the concrete class
  11.             IExample sampleClass = new Example();
  12.             //Use the variable reflecting the interface as normal.
  13.             return sampleClass.OperateOnTwoNumbers(1, 2);
  14.         }
  15.         /// <summary>
  16.         /// Parameters must also be changed to use the Interface, not the class
  17.         /// </summary>
  18.         public string AcceptClassAsParam(IExample param)
  19.         {
  20.             return param.ReadOnlyStringProperty;
  21.         }
  22.     }
  23. }

 

Add IoC container

Once the interfaces are added to your project, you must add the IoC container.  There are many containers available for download, and all have their pros and cons.  Which one you pick is up to you and will not affect how you proceed with your conversion.

The container should be initialized once at application startup.  It needs to be configured to provide the proper services in response to requests made by the application. In other words, it needs to be setup so when your application asks it to resolve one of the newly created interfaces, it knows which concrete class to actually create.  Many of the IoC containers available have an option that scans a given DLL or namespace and automatically maps the given interfaces.  This is an excellent way to go and can save a lot of configuration time as you add classes to your container.

Once the container is installed, remember to add unit tests to ensure that each class can be resolved as necessary.

A Note for Agile Shops

Something to note is that we are only introducing the IoC container, we are not converting any code to use it yet.  After this step is complete, you will have a fully configured container that is never used (except in your unit tests).  So far, we have not changed any logic anywhere in the application so everything should continue to work exactly as it did before.  I point this out because if you are an Agile shop with short sprints (many of our clients have 1 week sprints), this is a good place to stop for the sprint.  Everything on this page is easily done within the confines of a single sprint, and you can still take on other tasks that add features or fix defects.

 

In the next post, we will discuss how to use this foundation to complete the installation of an IoC container and Dependency Injection.

 

(This is a continuation of a previous post)

 

In the previous post, we adapted our existing code to allow the use of an IoC Container, and prepared the container so it is ready to use.  In this post, we put that foundation to work and start altering our logic.

Install Service Locator

When coding new applications, Inversion of Control (IoC) is best done using Dependency Injection, or DI.  However, DI assumes all necessary classes are registered with the container and available for resolution.  When converting an existing application, it is not practical to convert all necessary classes to DI in one step.  Trying to do so can dramatically increase the scope of the work, making the conversion project more difficult and possibly extending it beyond a single sprint.

In order to avoid the requirement of converting the entire application at once, we introduce an intermediate step of using a Service Locator.  Using the Service Locator pattern allows you to convert objects to the IoC pattern slowly over time instead of requiring a massive conversion effort.  Once an application is fully converted to Service Locator, it is a trivial step to further convert it to Dependency Injection.

The Service Locator is a static class that is referenced from all parts of the system (this is the part that Service Locator detractors object to).  As such, it needs to be located in a central project that can be referenced by all classes that need it.  Depending on how your IoC container works, this project may also need to be able to reference the projects where the Interfaces and Classes are kept. If you have a large solution, finding a good place to put the Service Locator can be one of the most difficult parts of this conversion effort.

The contents of the Service Locator are quite simple.  It’s basically just a class factory that uses the IoC container to resolve classes.  Here’s a basic example.

Sample Service Locator
  1.     public static class ServiceLocator
  2.     {
  3.         private static UnityContainer _container;
  4.         static ServiceLocator()
  5.         {
  6.             //Create our container
  7.             _container = new UnityContainer();
  8.             //setup all class registrations so they can be resolved in code later
  9.             _container.RegisterType<IExampleClass, ExampleClass>();
  10.             _container.RegisterType<IDependencyClass, DependencyClass>();
  11.         }
  12.         public static TProxyType Resolve<TProxyType>()
  13.         {
  14.             return _container.Resolve<TProxyType>();
  15.         }
  16.     }
  17. }

In this example, we are creating and configuring our container all within code.  Depending on the container you choose, configuration can be done in different ways.  The key thing to accomplish is for the Service Locator to have a reference to the IoC Container so it can resolve requested classes.

If you are working with an ASP.NET project (as opposed to a Windows app), you may be temped to use the Application object as your Service Locator.  By registering the container in the Application object, you can resolve the container anyplace in your code and use it directly.  This is a convenient and quick way to solve the problem but will cause some problems down the road.  It will force a reference to the particular container throughout your code, locking you into the IoC container you’ve chosen.  By only accessing the container via the Service Locator class, it avoids this reference and allows you to modify the chosen IoC container, as well as providing an extension point for adding custom resolution logic, where necessary.

With our IoC Container now available via the Service Locator we can begin adapting our code to use it.

Convert Class Creation to Use Service Locator

Once the Service Locator is installed, you will be able to sweep through the application and find all code that creates new objects, replacing those lines with calls to the Service Locator instead. For example, the old code could look like this:

Old Code Reference Object
  1. public void GetClientData()
  2. {
  3.     DataAccessRepository repo = new DataAccessRepository();
  4.     var clientData = repo.GetClientData();
  5. }

With our Service Locator, this same call would become this.  Notice the only change is in the line creating our repository.  All other logic remains exactly the same, reducing QA requirements.

Code Snippet
  1. public void GetClientData()
  2. {
  3.     IDataAccessRepository repo = DServiceLocatr();
  4.     var clientData = repo.GetClientData();
  5. }

Once you’re done with this, you may (hopefully) remove all references to the namespace containing the concrete class.  I say “hopefully” because there may be other classes in that namespace that you still have hard references to.

Convert to Dependency Injection (Optional)

By replacing the “new” statements with calls to the Service Locator, you are done with the conversion to IoC via Service Location.  The application is now more loosely coupled than it was before, and much more testable.  However, many people dislike the Service Locator pattern, saying that it just moves the dependency to a different class.  If you agree with this, there is an easy way to continue with the conversion and move to Dependency Injection.

Start by finding a class that has no references to other classes.  In large applications this can be difficult, but it is worth the effort because this is the first domino that must fall.

Find all places where this class is referenced and alter the constructor of the calling code to have that class injected instead.  For example, here’s a sample of a class that does not reference any other classes.

Sample Class with No Outside References
  1. public class ClassWithNoReferencesToOtherClasses : IClassWithNoReferencesToOtherClasses
  2. {
  3.     public void DoWork(int i)
  4.     {
  5.         //do important work here
  6.     }
  7. }
  8. public interface IClassWithNoReferencesToOtherClasses
  9. {
  10.     void DoWork(int i);
  11. }

Here is a sample calling class.

Sample Class That Uses The Class Above
  1. public class CallingClass : ICallingClass
  2. {
  3.     public void DoManyImportantThings()
  4.     {
  5.         var noRefClass = ServiceLocator.Resolve<IClassWithNoReferencesToOtherClasses>();
  6.         noRefClass.DoWork(1);
  7.         noRefClass.DoWork(2);
  8.         noRefClass.DoWork(3);
  9.     }
  10. }
  11. public interface ICallingClass
  12. {
  13.     void DoManyImportantThings();
  14. }

We add a constructor to the CallingClass that allows the dependency to be passed in from the outside, like this.

Implementing Dependency Injection via Constructor
  1. public class CallingClass : ICallingClass
  2. {
  3.     private IClassWithNoReferencesToOtherClasses noRefClass { get; set; }
  4.     public CallingClass(IClassWithNoReferencesToOtherClasses dependency)
  5.     {
  6.         noRefClass = dependency;
  7.     }
  8.     public void DoManyImportantThings()
  9.     {
  10.         //var noRefClass = ServiceLocator.Resolve<IClassWithNoReferencesToOtherClasses>();
  11.         noRefClass.DoWork(1);
  12.         noRefClass.DoWork(2);
  13.         noRefClass.DoWork(3);
  14.     }
  15. }

In this section, notice we have removed the ServiceLocator call, and are now using a class-level variable that is injected in during object creation.  Assuming all code that creates a CallingClass object is using the ServiceLocator, this class is now converted to Dependency Injection and references to the ServiceLocator can be removed.

This process can be continued either ad-hoc as time permits, or as a concerted effort by a dedicated resource.  Each of these small conversion can stand by itself, and so they can be done over multiple sprints while the application is always releasable.

My Story

Hi, I’m Brad Irby. Since I began my career in Software Development 30 years ago, nearly every project I’ve worked on has been with existing code, not building from scratch. In working with companies like General Electric, Wells Fargo,and Bank of America, I have learned how to work with large legacy systems, and bring them up to date by injecting current development techniques into existing code. In fact I’ve done it so often, Addison Wesley ask me to write a book about it.

Learn More