C# Corner

Inversion of Control Patterns for the Microsoft .NET Framework

Explore new ways to manage dependencies in your applications with Inversion of Control containers and Dependency Injection.

This month I'm going to talk about that magical phrase "Inversion of Control" (going forward, I'll simply refer to it as IoC). There's lots of buzz in the industry around IoC, as well as around Dependency Injection (DI). They are different concepts, but utilizing IoC usually gives you DI for free.

Who Has Control?
When I talk about "control" and who has it, I'm talking about the creation of your objects. Every application has objects, and those objects have to be managed. Lots of objects are temporary (method level), while some are passed around the entire application to provide specific functionality. The creation and lifespan of objects is usually for you, the developer, to control.

Here's a simple example:

SmtpClient smtpClient = new SmtpClient();

This is code that may exist in your application. You created the SmtpClient instance and you have control over its lifetime. Place it in a method and it will go out of scope as soon as the method exits. Or you can place it at the class level and all methods within the class will have access to it. It's up to you.

Suppose you need only a single instance of the SmtpClient. That's simple. The singleton pattern is easy to implement in C#:

   public sealed class Mail
   {
      private static SmtpClient smtpClient;

      public static SmtpClient SmtpClient
      {
         get
         {
            if( smtpClient == null)
            {
               smtpClient = new SmtpClient();
            }

            return smtpClient;
         }
      }
   }

Of course, if you're familiar with this implementation, you'll know it's not thread-safe. Suppose thread 1 evaluates smtpClient == null to true? It plans to move forward and create the new instance, but it's interrupted by the operating system, and another thread runs. Thread 2 hits the same code and evaluates the smtpClient == null to true also. It then creates the SmtpClient instance. Eventually -- in a few microseconds -- control will go back to thread 1 and it will continue what it was doing: creating another instance of SmtpClient. Now, in this example, having a second instance of SmtpClient created is not going to break your application. But you're implementing the singleton pattern for a reason -- you only want one instance created. The thread-safety issue can be avoided with some careful locking code. But it's code you have to write, debug and maintain.

There are other scenarios where object lifetime can be a pain to maintain. Suppose you only want one instance per-thread? Or maybe just one instance per ASP.NET Web request? Is that code you want to spend time writing? You don't spend time writing code to draw a 3-D shadow around a button, so why take the time to write this kind of stuff?

Give Control to a Container
Just as the Windows Forms library is a nice abstraction over the Windows OS GUI capabilities, an IoC container is a nice abstraction to control the instantiation and lifetime of your objects.

The term "Inversion of Control" is used because you're inverting the control. Instead of you having control over the creation of your objects (via "new"), you'll give control to the container. The container will create your object and handle its lifespan. Let's look at a simple example. For this article, I'll be using the Windsor container from the Castle Project. You're free to use Ninject, Sprint.NET or other IoC containers. The syntax may be a little different but the concepts are the same:

   public class IoCSample
   {
      private IWindsorContainer container;

      public void InitializeContainer()
      {
         container = new WindsorContainer();
         container.Register(
            Component.For<SmtpClient>());
      }
   }

First, create a new instance of the Windsor Container. Then, register a component that you want the container to manage -- in this case, the SmtpClient class. Notice how Windsor takes advantage of generics and a fluent interface to make the code easier to read.

Let's have an instance of SmtpClient actually do something. You're not using "new" anymore, so how do you get an SmtpClient instance out of the container? You ask the container to "resolve" an instance of your component:

   public void ProcessInvoice()
   {
      // code to process an invoice
      var smtpClient = container.Resolve<SmtpClient>();
      // set smtpClient properties
      // use smtpClient to send email
   }

Where's the benefit of the container? One big thing is lifestyle management. Let's say you want only one instance of SmtpClient in this application. Well, that's what you have already. The default lifestyle in Windsor is singleton. To check this, you can run the following code:

   public void CheckInstances()
   {
      var client1 = container.Resolve<SmtpClient>();
      var client2 = container.Resolve<SmtpClient>();

      if( client1 != client2)
      {
         throw new ApplicationException("Lifestyle problem!");
      }
   }

If you run this code on the container as initialized earlier, it won't throw an exception. And this implementation of the singleton pattern is thread-safe and has been tested by thousands of users.

What if you want a new instance every time you resolve -- for example, a transient? You just change how you add the component to the container:

         container.Register(
            Component.For<SmtpClient>()
            .LifeStyle.Is(LifestyleType.Transient)
            );

Now if you go back and run the CheckInstances method, it will throw the ApplicationException because each call to Resolve creates a new SmtpClient instance.

The Windsor container comes with support for these lifestyles:

  • Singleton (default)
  • Transient
  • Thread
  • Pooled
  • Per Web Request

If that isn't enough, you can create your own lifestyle type and plug it into Windsor. Other IoC containers provide similar functionality. Check their documentation for the exact syntax.

Types and Interfaces
One of the nice things about IoC containers is that you can let the containers handle the specifics while your code deals with more general concerns (loosely coupled). A good example of this is how the container handles concrete types and interfaces. For example, take the following class:

   public class Foo
   {
      public void Bar()
      {
      }
   }

And register it with Windsor:

container.Register(Component.For<Foo>());

Now use container.Resolve<Foo>() anytime you need an instance of Foo.

What if you like code to be more loosely coupled? With the previous code, you're tied to a concrete implementation. You can refactor this code to use interfaces so it's not tied to a particular implementation:

   public interface IFoo
   {
      void Bar();
   }

   public class Foo : IFoo
   {
      public void Bar()
      {
      }
   }

The container needs to know that the IFoo component is implemented by the Foo class:

container.Register(Component.For<IFoo>().ImplementedBy<Foo>());

The container is now in charge of creation and lifespan tracking of the implementation. All you have to do is ask the container to give you IFoo:

IFoo foo = container.Resolve<IFoo>();

What if you have multiple implementations of IFoo and certain parts of the code need to access the different implementations? Windsor allows you to give your components unique names and reference them by name. So if you have two different implementations of IFoo, give each one a unique name:

container.Register(
  Component.For<IFoo>().ImplementedBy<Foo>().Named("Foo1"));
container.Register(
  Component.For<IFoo>().ImplementedBy<MyBar>().Named("Foo2"));

Now when you resolve IFoo in your code, you ask for a particular implementation of IFoo based on the name:

   IFoo foo1 = container.Resolve<IFoo>("Foo1");
   IFoo foo2 = container.Resolve<IFoo>("Foo2");

The container can manage instantiation and lifespan for any object you need, but you still have to sprinkle "container.Resolve()" code all over the place. This makes testing difficult and makes it tightly coupled to a particular IoC container. Dependency injection will help solve these problems.

Dependency Injection
Suppose you're writing a Web site that allows people to register for a conference. For those of you who read "Interface-Based Programming in C#" (Language Lab, January 2010), this example will sound familiar. This conference is not free, so there's some payment processing going on. You'll also be storing the users' information in a database. In this example, a RegistrationService needs to reference a payment processor and a repository:

   public interface IPaymentProcessor
   {
      bool ProcessPayment(string cardNumber, decimal amount);
   }

   public interface IConferenceRepository
   {
      bool RegisterUser(UserInformation userInformation);
   }

   public class RegistrationService
   {
      public RegistrationService(
        IPaymentProcessor processor,
        IConferenceRepository repository)
      {
         
      }
   }

Thanks to the use of an IoC container, you don't need to know what particular object implements the IPaymentProcessor or the IConferenceRepository. You just ask for the particular implementations and our container handles the rest:

   var paymentProcessor = container.Resolve<IPaymentProcessor>();
   var repository = container.Resolve<IConferenceRepository>();
   var service = new RegistrationService(paymentProcessor, repository);

What's really nice about this is you can register a different IPaymentProcessor in the container without changing any code. Suppose the primary processor goes out of business unexpectedly. You could write a new IPaymentProcessor implementation for the new processor, change the container initialization to use the new implementation and - voilˆ -- be done.

But, as noted earlier, you don't want to call "container.Resolve()" all over the place. The IPaymentProcessor and IConferenceRepository are registered in the container, so everything needed to instantiate a RegistrationService (except the registration service itself) is in the container. The container "injects" those dependencies for us:

   container.Register(
      Component.For<RegistrationService>()
      .LifeStyle.Is(LifestyleType.Transient));

Now when you "Resolve" an instance of RegistrationService, the container looks at the constructor and sees that it needs an IPaymentProcessor and an IConferenceRepository. The container looks to see if those types are registered in the container. If they are, the container will "Resolve" those types and then instantiate RegistrationService. All you need is the following:

var service = container.Resolve<RegistrationService>();

Now you've got a fully initialized RegistrationService ready to use.

Where IoC and DI really shine is when you need to change your application. Let's say you decide to send a confirmation e-mail when registration is completed. You define an interface for sending e-mail as follows:

   public interface IEmailSender
   {
      void SendMail(MailMessage message);
   }

Then, make sure the RegistrationService gets an IEmailSender by adding a constructor parameter:

   public class RegistrationService
   {
      public RegistrationService(
        IPaymentProcessor processor,
        IConferenceRepository repository,
        IEmailSender emailSender)
      {
         
      }
   }

You change the container initialization to make sure you register an IEmailSender implementation, and you're done. You don't have to go back and find all of the places where the RegistrationService is created. The container is managing creation and lifetime, so all you need to do is ensure your dependencies are registered in the container and the rest happens automatically. How many times have you added a new parameter to a constructor and simply did a build to find all of the places the build breaks? You'd then go and modify all of those places to handle the new parameter. With an IoC container handling your objects and doing the DI for you, that's a thing of the past.

I hope you see how easy it is to test the RegistrationService now. It has no concrete, external dependencies. At run time, the container will resolve the actual implementations. During tests, you can create mocks and stubs of those interfaces and pass them into the RegistrationService.

This introduction just scratches the surface of what you can do with an IoC container and DI. Download one of the many free IoC containers for the Microsoft .NET Framework and start playing around with them. It'll take some getting used to, but the effort will pay off immensely.

About the Author

Patrick Steele is a senior .NET developer with Billhighway in Troy, Mich. A recognized expert on the Microsoft .NET Framework, he’s a former Microsoft MVP award winner and a presenter at conferences and user group meetings.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube