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.