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.
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.
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.