Skip to content

Dependency Injection using Autofac

Last updated on April 1, 2019

In the previous post, we saw how we can inject dependencies without any DI container. However, in this post, we will see how DI containers help us by instantiating the dependencies and provide them whenever and wherever required.

 

What is a DI Container?

In the example for our last post, we had to instantiate dependencies on our own, before injecting them via constructor. Consequently, as the level of dependencies increases, injections get quite complex. A DI container, comes to our rescue in such situations.

A container takes the responsibility of instantiating and providing the dependency instances, irrespective of the level of dependencies. As a result, it is a container that manages the scope of the instantiated objects. Additionally, the container shall know about all the types, it has to instantiate and provide at some point.

 

Code Sample

In the following code sample, we will be using autofac, as our DI container. However, some other well known DI containers are StructureMap, CastleWindsor, Spring.NET, Microsoft’s Unity, Ninject and many more. Rather, it’s just a matter of one’s choice. Let’s have a look at some code, and then we shall see how a container works.

class IoCBuilder
{
    internal static IContainer Build()
    {
        ContainerBuilder builder = new ContainerBuilder();
        RegisterTypes(builder);
        return builder.Build();
    }

    private static void RegisterTypes(ContainerBuilder builder)
    {
        builder.RegisterType<Commerce>();
        builder.RegisterType<CurrencyConverter>().As<ICurrencyConverter>();
        builder.RegisterType<TextLogger>().As<Logger>();
        builder.RegisterType<EmailNotifier>().As<NotificationManager>();
        builder.RegisterType<CreditCardProcessor>().As<PaymentProcessor>();            
    }
}

 

class Commerce
{
    private PaymentProcessor _paymentProcessor;
    private NotificationManager _notificationManager;
    private Logger _logger;

    public Commerce(PaymentProcessor paymentProcessor, NotificationManager notificationManager, Logger logger)
    {
        _paymentProcessor = paymentProcessor;
        _notificationManager = notificationManager;
        _logger = logger;
    }

    public void ProcessOrder(Order order)
    {
        decimal paidAmount = order.UnitPrice * order.Quantity;
        bool paymentSuccessful = _paymentProcessor.ProcessPayment(paidAmount);
            
        if(paymentSuccessful)
        {
            _notificationManager.NotifyCustomer(notification: "payment successful");
        }
        else
        {
            _notificationManager.NotifyCustomer(notification: "payment failed");
            _logger.Log(errorMessage: "payment failed");
        }
    }
}

 

class Program
{
    static void Main(string[] args)
    {
        IContainer container = IoCBuilder.Build();
        Order customerOrder = new Order { Id = 1, ProductName = "Product", Quantity = 3, UnitPrice = 75 };
        Commerce commerce = container.Resolve<Commerce>();
        commerce.ProcessOrder(customerOrder);
    }
}

 

How a DI container works?

  • First thing to remember is, that the container needs to know about all the types, we want it to instantiate for us. In other words, we need to register the types with the container. In general, this is known as Registration.
  • As can be seen in the IoCBuilder class, we have registered our types against their respective interface. As a result, when we add a dependency of an interface, the container will provide an instance of the class registered against that particular interface.
  • At the same time, it is not necessary that we register all our types against an interface. Commerce class, for instance, has been registered as self and not against any interface.
  • Moreover, the process of requesting the container to return an instance of a type is known as Resolving a type.
  • While resolving a type, the DI container looks at the type’s constructor. If the type has some dependencies, the container will first resolve these dependencies, and then create its instance.
  • The container enumerates through these dependencies and looks into their constructor. If a dependency class has its own dependencies, then the container will try to resolve them first. And in order to do that, it consistently checks the type registrations.
  • It is equally important to know, that not all DI containers require all types to be explicitly registered. Autofac does, whereas, Unity does not.
Published inDesign Patterns & Principles