Software Design Blog

Simple solutions to solve complex problems

Step-by-step guide to use dependency injection


This post will use Microsoft’s Inversion of Control (IoC) Unity container to generate HTML email messages based on Razor templates.

We would like to build a service as displayed below that consists of many small components. The service will save an order, generate a HTML email based on a template and send an email.


The problem is how can we decouple our app from concrete implementations and reduce the number of class dependencies?

The solution is to use the dependency injection (DI) pattern that implements inversion of control for resolving dependencies.

Here is an example where the OrderService class is responsible for obtaining its own references to its dependencies.

  
    public class OrderService
    {
        private readonly OrderRepository _repository;
        private readonly EmailService _emailService;

        public OrderService()
        {
            _repository = new OrderRepository();
            _emailService = new EmailService(new TemplateLocator("C:\templates"), 
                                             new RazaorRenderingService(), 
                                             new SmtpMailClient());
        }

        public void Create(OrderModel order)
        {
            if (order == null) throw new ArgumentNullException("order");
            _repository.Save(order);
            _emailService.Send(order.Customer.Email, "Order Created", order);
        }
    }

The OrderService class has many dependencies on other concrete classes, which makes it hard to swap out behaviour and to test components in isolation. For example, the RazorRendingService cannot be replaced with an XmlRenderingService without modifying the OrderService class.

Composition using Dependency Injection

Let's get started with a DI solution in 4 easy steps.

Download Source Code

1. Define the Interfaces

According to the dependency inversion SOLID principle, our classes should depend on abstractions instead of concretions. We can achieve this goal by defining interfaces/contracts for each component as shown below.

  
    public interface IOrderService
    {
        void Create(OrderModel order);
    }

    public interface IOrderRepository
    {
        void Save(OrderModel order);
    }

    public interface IEmailService
    {
        void Send<T>(string to, string subject, T body);
    }

    public interface IRenderingService
    {
        string Render<T>(string templatePath, T model);
    }

    public interface ITemplateLocator
    {
        string Locate<T>(T model);
    }

    public interface IMailClient
    {
        void Send(string to, string subject, string body);
    }
2. Move dependencies to the constructor

Move the dependencies of each class to the constructor as shown below.

  
    public class OrderService : IOrderService
    {
        private readonly IOrderRepository _repository;
        private readonly IEmailService _emailService;

        public OrderService(IOrderRepository repository, IEmailService emailService)
        {
            if (repository == null) throw new ArgumentNullException("repository");
            if (emailService == null) throw new ArgumentNullException("emailService");
            _repository = repository;
            _emailService = emailService;
        }

        public void Create(OrderModel order)
        {
            if (order == null) throw new ArgumentNullException("order");
            _repository.Save(order);
            _emailService.Send(order.Customer.Email, "Order Created", order);
        }
    }

    public class OrderRepository : IOrderRepository
    {
        public void Save(OrderModel order)
        {
            if (order == null) throw new ArgumentNullException("order");
            Console.WriteLine("Saving order: {0}", order.Description);
            order.Id = 123; // Create an Id
        }
    }

    public class EmailService : IEmailService
    {
        private readonly ITemplateLocator _templateFinder;
        private readonly IRenderingService _renderService;
        private readonly IMailClient _mailClient;

        public EmailService(ITemplateLocator templateFinder, 
                            IRenderingService renderService, 
                            IMailClient mailClient)
        {
            if (templateFinder == null) throw new ArgumentNullException("templateFinder");
            if (renderService == null) throw new ArgumentNullException("renderService");
            if (mailClient == null) throw new ArgumentNullException("mailClient");
            _templateFinder = templateFinder;
            _renderService = renderService;
            _mailClient = mailClient;
        }

        public void Send<T>(string to, string subject, T body)
        {
            var template = _templateFinder.Locate(body);
            var htmlBody = _renderingService.Render(template, body);
            _mailClient.Send(to, subject, htmlBody);
        }
    }

    public class TemplateLocator : ITemplateLocator
    {
        private readonly string _basePath;

        public TemplateLocator(string basePath)
        {
            if (basePath == null) throw new ArgumentNullException("basePath");
            _basePath = basePath;
        }

        public string Locate<T>(T model)
        {
            var templateName = string.Format("{0}.cshtml", model.GetType().Name);
            return Path.Combine(_basePath, templateName);
        }
    }

    public class RazaorRenderingService : IRenderingService
    {
        public string Render<T>(string templatePath, T model)
        {
            using (var templateService = new TemplateService())
            {
                return templateService.Parse(File.ReadAllText(templatePath), 
                                             model, null, null);                
            }
        }
    }

    public class SmtpMailClient : IMailClient
    {
        public void Send(string to, string subject, string body)
        {
            using (var mesage = new MailMessage())
            {
                mesage.Subject = subject;
                mesage.Body = body;
                mesage.IsBodyHtml = true;
                mesage.To.Add(new MailAddress(to));

                using (var client = new SmtpClient())
                {
                    client.Send(mesage);
                }
            }
        }
    }
Constructor injection is preferred because:
  • All dependencies are located together - easily identify class dependencies
  • The number of dependencies are exposed - the fewer dependencies the better
  • Classes are instantiated with all dependencies - reduce the risk of invalid configurations
3. Composing the solution using DI

Let's wire it all up using a DI container as shown below.

  
            var container = new UnityContainer();
            container.RegisterType<IOrderRepository, OrderRepository>();
            container.RegisterType<IEmailService, EmailService>();
            container.RegisterType<IRenderingService, RazaorRenderingService>();
            container.RegisterType<IMailClient, SmtpMailClient>();
            container.RegisterType<IOrderService, OrderService>();

            var baseTemplatePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, 
                                                "EmailTemplates");
            container.RegisterType<ITemplateLocator, TemplateLocator>(
                                  new InjectionConstructor(baseTemplatePath));
4. Run the solution

The razor template is shown below.

 
@model Orders.Interfaces.Models.OrderModel

<!DOCTYPE html>

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>Order</title>
</head>
<body>
    <p>Hi @Model.Customer.Name,</p>
    <p>Thanks for placing the order.</p>
    <p>Your order number is: @Model.Id</p>
</body>
</html>

The App.config is configured to write the emails to C:\temp.

 
      <smtp deliveryMethod="SpecifiedPickupDirectory" from="[email protected]"
        <specifiedPickupDirectory pickupDirectoryLocation="C:\temp"  />
      </smtp>

Let’s create an order and get the OrderService to process it.

  
            var orderModel = new OrderModel()
            {
                Description = "Design Book",
                Customer = new CustomerModel() { Email = "[email protected]", Name = "Jay" }
            };

            var orderService = container.Resolve<IOrderService>();
            orderService.Create(orderModel);

The email output is shown below.


Summary

In this post we have described the process of using the DI pattern to provide inversion of control for resolving dependencies with Unity.

The advantages of DI is:
  • Testing is simplified since we can write unit tests per component/class
  • Mocking is simplified since we can mock/stub out behaviour
  • Small, well tested classes that only do one thing are often easy to understand
  • Coupling is reduced since classes often rely on interface abstractions instead of concretions
  • Works well with patterns such as decorators and interception
The disadvantages of DI is:
  • It may feel like you are writing a lot more code – but the code quality is better due to the reasons above
  • Wiring can become complicated which makes debugging trickier

Next: How to structure DI registrations cleanly

Comments are closed