Dependency Injection

08 May 2018
—by Brad Wilson

SOFTWARE DESIGN PATTERNS

To understand what dependency injection is and how you can apply it to MVC applications, you need to understand software design patterns.
A software design pattern is used to formalize the description of a problem and a solution to that problem, so that developers can use the pattern to simplify the identifcation and communication of common problems and solutions.
The design pattern isn’t necessarily to claim the invention of something new or novel, but rather exists to give a formal name and defnition from common practices in the industry.
When you read about a design pattern, you might recognize it from solutions you’ve used in particular problems in the past.

Design Pattern: Dependency Injection

The dependency injection (DI) pattern is another form of the inversion of control pattern, wherein there is no intermediary object like the service locator.
Instead, components are written in a way that allows their dependencies to be stated explicitly, usually by way of constructor parameters or property setters.
Developers who choose dependency injection over service location are often making a conscious decision to choose transparency of requirements over opacity.
Choosing the transparency of dependency injection also has signifcant advantages during unit testing, as discussed in the next chapter.
Constructor Injection The most common form of dependency injection is called constructor injection.
This technique involves creating a constructor for your class that expresses all of its dependencies explicitly (as opposed to the previous service location examples, where your constructor took the service locator as its only constructor parameter).
Now take a look at what NotificationSystem would look like if designed to support constructor injection:

public class NotificationSystem
{
    private IMessagingService svc;
    public NotificationSystem(IMessagingService service)
    {
        this.svc = service;
    }
    public void InterestingEventHappened()
    {
        svc.SendMessage();
    }
}

In this code, the frst beneft is that the implementation of the constructor is dramatically simplifed.
The component is always expecting whoever creates it to pass the required dependencies.
It only needs to store the instance of IMessagingService for later use.
Another beneft is that you’ve reduced the number of things NotificationSystem needs to know about.
Previously, it needed to understand service locators in addition to its own dependencies; now, it is focused solely on its own dependencies.
The third beneft, as alluded to previously, is this new transparency of requirements.
Any code that wants to create an instance of NotificationSystem can look at the constructor and know exactly what kinds of things are necessary to make NotificationSystem function.
There is no guesswork and no indirection through the service locator.

Property Injection

A less common form of dependency injection is called property injection.
As the name implies, dependencies for a class are injected by setting public properties on the object rather than through the use of constructor parameters.
A version of NotificationSystem that uses property injection would look like this:

public class NotificationSystem
{
    public IMessagingService MessagingService {get;set;}
    public void InterestingEventHappened()
    {
        MessagingService.SendMessage();
    }
}

This code removes the constructor arguments (in fact, it removes the constructor entirely) and replaces it with a property.
This class expects any consumers to provide you with your dependencies via properties rather than the constructor.
The InterestingEventHappened method is now slightly dangerous.
It presumes that the service dependency has already been provided; if it hasn’t, then it will throw a NullReferenceException You should update the InterestingEventHappened method to ensure that it has been provided with its dependency before using the service:

public void InterestingEventHappened()
{
  if (MessagingService == null)
  {
      throw new InvalidOperationException(
      "Please set MessagingService before calling " +
      "InterestingEventHappened().");
  }
  MessagingService.SendMessage();
}

It should be obvious that you’ve slightly reduced your transparency of requirements here; property injection is not quite as opaque as using the service locator, but it’s defnitely more error prone than constructor injection.
With this reduced transparency, you’re probably wondering why a developer would choose property injection over constructor injection.
Two situations might warrant that choice:
➤ If your dependencies are truly optional in the sense that you have some fallback when the consumer doesn’t provide you with one, property injection is probably a good choice.
➤ Instances of your class might be created in such a way that you don’t have control over the constructor that’s being called.
This is a less obvious reason.
You’ll see a couple of examples of this later in the chapter during the discussion of how dependency injection is applied to view pages.
In general, developers tend to favor using constructor injection whenever possible, falling back to property injection only when one of the preceding reasons dictates.
Obviously, you can mix both techniques in a single object: put your mandatory dependencies in as constructor parameters, and your optional dependencies in as properties.
Dependency Injection Containers One big piece of the puzzle that’s missing in both examples of dependency injection is exactly how it takes place.
It’s one thing to say, “Write your dependencies as constructor arguments,” but it’s another to understand how they might be fulflled.
The consumer of your class could manually provide you with all those dependencies, but that can become a pretty signifcant burden over time.
If your entire system is designed to support dependency injection, creating any component means you have to understand how to fulfll everybody’s requirements.
Using a dependency injection container is one way to make the resolution of these dependencies simpler.
A dependency injection container is a software library that acts as a factory for components, automatically inspecting and fulflling their dependency requirements.
The consumption portion of the API for a dependency injection container looks a lot like a service locator because the primary action you ask it to perform is to provide you with some component, usually based on its type.
The difference is in the details, of course.
The implementation of a service locator is typically very simple: You tell the service locator, “If anybody asks for this type, you give them this object.” Service locators are rarely involved in the process of actually creating the object in question.
A dependency injection container, on the other hand, is often confgured with logic like, “If anybody asks for this type, you create an object of this concrete type and give them that.” The implication is that creating the object of that concrete type will, in turn, often require the creation of other types to fulfll its dependency requirements.
This difference, while subtle, makes a fairly large difference in the actual usage of service locators versus dependency injection containers.
More or less, all containers have confguration APIs that allow you to map types (which is the equivalent of saying, “When someone asks for type T1, build an object of type T2 for them.”).
Many also allow confguration by name (“When someone asks for the type T1 named N1, build an object of type T2.”).
Some will even attempt to build arbitrary types, even if they have not been preconfgured, as long as the requested type is concrete and not abstract.
A few containers even support a feature called interception, wherein you can set the equivalent of event handlers for when types get created, and/or when methods or properties get called on those objects.
For the purposes of this book, the discussion of the use of these advanced features is beyond our scope.
When you have decided on a dependency injection container, you will typically fnd documentation online that will discuss how to do advanced confguration operations.