I teach a class about the principles and patterns of effective software design. I often disconcert the attendees by saying that, although I perceive a great deal of emphasis in the value of Dependency Injection, I don’t believe that Dependency Injection justifies all of the attention. Dependency Injection is just an implementation detail; what really matters is the principle behind it-the Dependency Inversion principle. If you’re familiar with the acronym SOLID, well, the Dependency Inversion principle is just the “D” in the acronym. By the way, SOLID results from the initials of five design principles that are reckoned to be essential in Object-Oriented software design. These five principles are:
- Single Responsibility
- Open/Closed
- Liskov’s principle
- Interface Segregation
- Dependency Inversion
Nearly all of these principles are mere vectors in the sense that they show you the direction but don’t give you concrete guidance on how to do things. Let’s take the Single Responsibility principle as an example. All it says is that you should endeavor to write your classes so that you later have just one reason to change them. The idea behind the principle is that classes should be much more cohesive than they often are. The methods they are made of should be logically related and form a single chain of responsibility. As you can see, the principle heralds a clear and shared idea but it doesn’t give you a step by step procedure on how to accomplish it.
Inside the Dependency Inversion Principle
The Dependency Inversion principle, instead, has a fairly obscure formulation but can be translated into an extremely detailed set of implementation steps. The Dependency Inversion principle says that classes should not have dependencies on concrete classes but only to abstractions. Translated for humans, it means that you should be using interfaces to abstract all of the critical dependencies of a given class. If, for example, your class needs to use a Logger component, then the best you can do is to make your class aware of an ILogger interface rather than a Logger class. In this way, you can change the implementation of the logger class at any time (and for how many times you want) without breaking the host code.
The implementation of the Dependency Inversion principle is bound to an algorithm that passes a list of dependencies to the core code. Let’s have a look at the following code:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class MyComponent { public MyComponent() { : } public void DoSomeWork() { var logger = new Logger(); : } } |
Clearly, the MyComponent class has a dependency on the Logger class. If we decide to change it to ILogger, how can we get a reference to an actual class that implements the interface?
1 2 3 4 5 6 7 8 9 10 11 12 |
public class MyComponent { public MyComponent() { : } public void DoSomeWork() { ILogger logger = ...; // who's going to provide this? : } } |
To implement the Dependency Inversion principle, you have the choice of two main patterns: Service Locator and Dependency Injection. The former allows you “resolve” a dependency within a class; the latter allows you to “inject” a dependency from outside the class. The following listing exemplifies what it means to resolve a dependency:
1 2 3 4 5 6 7 8 9 10 11 12 |
public class MyComponent { public MyComponent() { : } public void DoSomeWork() { ILogger logger = ServiceLocator.GetService(); : } } |
You have a dependency resolver component that typically takes a type (commonly an interface) and returns an instance of a concrete type that implements that interface. The match between the type that is passed and the concretely-instantiated type is hidden in the implementation of the locator component. This pattern is known as the Service Locator pattern. Here’s another approach:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class MyComponent { private ILogger _logger; public MyComponent(ILogger logger) { _logger = logger; } public void DoSomeWork() { // Use the logger component here _logger.Log(); : } } |
In this case, the MyComponent class receives the ILogger component to use from the outside world. Surrounding classes will take care of the initialization of the logger before passing it down to MyComponent. This is the essence of the Dependency Injection pattern.
What’s the difference (if any) between Dependency Injection and Service Locator? Both patterns are good at implementing the Dependency Inversion principle. The Service Locator pattern is easier to use in an existing codebase as it makes the overall design looser without forcing changes to the public interface. For this same reason, code that is based on the Service Locator pattern is less readable than equivalent code that is based on Dependency Injection.
The Dependency Injection pattern makes it clear since the signature which dependencies a class (or a method) is going to have. For this reason, the resulting code is cleaner and more readable. What about ASP.NET MVC?
Dependency Inversion in ASP.NET MVC
ASP.NET MVC is designed with several extensibility points, but generally it lacks a comprehensive support for dependency injection. A service locator is probably the most effective way of making an existing framework more loosely coupled by the addition of new extensibility points, because it is the least intrusive solution. A service locator acts as a black box that you install in a specific point and let it figure out what contract is required and how to get it. ASP.NET MVC has a number of extensibility points, which are system components that can be replaced with custom components. Table 1 lists the known extensibility points as of ASP.NET MVC 3.
Provider | Description |
Action Invoker | // In the constructor of a controller class controller.ActionInvoker = new YourActionInvoker(); |
Controller factory | // In global.asax, Application_Start var factory = new YourControllerFactory(); ControllerBuilder.Current.SetControllerFactory(factory); |
Dictionary values | // In global.asax, Application_Start var providerFactory = new YourValueProviderFactory(); ValueProviderFactories.Factories.Add(providerFactory); |
Model binder | // In global.asax, Application_Start ModelBinders.Binders.Add(typeof(YourType), new YourTypeBinder()); |
Model binder provider | // In global.asax, Application_Start var provider = new YourModelBinderProvider(); ModelBinderProviders.BinderProviders.Add(provider); |
Model metadata | // In global.asax, Application_Start ModelMetadataProviders.Current = new YourModelMetadataProvider(); |
Model validator | // In global.asax, Application_Start var validator = new YourModelValidatorProvider(); ModelValidatorProviders.Providers.Add(validator); |
TempData | // In the constructor of a controller class controller.TempDataProvider = new YourTempDataProvider(); |
View engine | // In global.asax, Application_Start ViewEngines.Engines.Clear(); ViewEngines.Engines.Add(new YourViewEngine()); |
Until ASP.NET MVC 3, there was no standard way to register custom components. Each component listed in Table 1 requires its own API to be integrated in a user application. Starting with version 3, ASP.NET MVC introduces a new (optional) model based on dependency resolvers. To replace a system component, you can either take the route described in Table 1 or register a dependency resolver for the type. The ASP.NET runtime will then detect the dependency resolver and invoke it whenever appropriate.
A dependency resolver is just a service locator integrated with the ASP.NET MVC codebase. Resolvers are a way to add the implementation of the Dependency Inversion principle into an existing (large) codebase. For the size and complexity of the codebase, the use of Dependency Injection is less appropriate because it would have required changes at various levels in the public API. This just isn’t an option for a framework such as ASP.NET MVC. Let’s find out more details about the implementation of dependency resolvers in ASP.NET MVC.
Defining Your Dependency Resolver
An ASP.NET MVC dependency resolver is an object that implements the following interface:
1 2 3 4 |
{ Object GetService(Type serviceType); IEnumerable<Object> GetServices(Type serviceType); } |
The logic you put in the resolver is entirely up to you. It can be as simple as a switch statement that checks the type and returns a newly created instance of a fixed type. It can be made more sophisticated by reading information from a configuration file and using reflection to create instances. Finally, it can be based on Unity or any other IoC framework. Here’s a very simple, yet functional, resolver:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public class SampleDependencyResolver : IDependencyResolver { public object GetService(Type serviceType) { if (serviceType == typeof(ISomeClass)) return new SomeClass(); : } public IEnumerable<object> GetServices(Type serviceType) { return Enumerable.Empty<Object>(); } } |
The code next shows a resolver that uses Unity (and its configuration section) to resolve dependencies.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public class UnityDependencyResolver : IDependencyResolver { private readonly IUnityContainer _container; public UnityDependencyResolver() : this(new UnityContainer().LoadConfiguration()) { } public UnityDependencyResolver(IUnityContainer container) { _container = container; } public Object GetService(Type serviceType) { return _container.Resolve(serviceType); } public IEnumerable<Object> GetServices(Type serviceType) { return _container.ResolveAll(serviceType); } } |
You register your own resolver with the ASP.NET MVC framework through the SetResolver method of the DependencyResolver class, as shown below:
1 2 3 4 5 6 7 8 9 10 |
protected void Application_Start() { // Prepare and configure the IoC container var container = new UnityContainer(); : // Create and register the resolver var resolver = new UnityDependencyResolver(container); DependencyResolver.SetResolver(resolver); } |
If you use an IoC framework from within the resolver then you need to figure out the best way to provide it with the list of registered types. If you prefer to pass this information via fluent code, then you need to fully configure the IoC container object before you create the resolver. If you intend to configure the IoC using the web.config file then, as far as Unity is concerned, you can use the default constructor of the resolver which includes a call to load configuration data. Note, however, that you may need to change this code if you target a different IoC framework.
Overall, the dependency resolver is an internal tool that developers can optionally use to roll their own customized components instead of system components. The power of dependency resolvers is limited by the use of them that ASP.NET MVC makes. Resolvers are invoked in well- known places to achieve well known goals. In other words, if ASP.NET MVC doesn’t invoke the resolver before creating, say, the controller cache, there’s not much you can do to replace the built-in cache with your own one.
Using Resolvers in Applications
It turns out that a dependency resolver is nothing more than the name that ASP.NET MVC uses for a service locator. How would you write one for a real application? The answer is that you have one resolver per application and write it to serve as many as customizations as you like. Here’s a slightly more specific version of the sample dependency resolver that we considered a moment ago.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public class SampleDependencyResolver : IDependencyResolver { public object GetService(Type serviceType) { try { return serviceType == typeof(ModelMetadataProvider) ? new ExtendedAnnotationsMetadataProvider() : Activator.CreateInstance(serviceType); } catch { return null; } } public IEnumerable<object> GetServices(Type serviceType) { return Enumerable.Empty<object>(); } } |
The GetService method receives a type to resolve, and checks it against a list of known types. For some known interface types, it may simply return a manually-created instance of a known type. For other types it may return nothing, meaning that the resolver is not able to resolve that type.
An ASP.NET MVC dependency resolver has no way to resolve the same type in different ways during the lifetime of the application. While this may be a significant limitation in the implementation of a generic service locator, it is much less of a problem in the specific context of ASP.NET MVC. Dependency resolvers are an internal feature of ASP.NET MVC and only the internal code of ASP.NET MVC decides when and how to call application-registered resolvers. In the end, your resolvers will be called only in a limited number of circumstances, and the interface is more than acceptable.
So Should You Resolve or Should You Inject?
Should You Resolve or Should You Inject? If you look at this question in terms of the effectiveness of the design, then Dependency Injection is preferable to service location because it results in a cleaner design and a crystal-clear assignment of responsibilities. To use Dependency Injection, though, you may need to take the liberty of modifying the public interface of the API. This may or may not be acceptable depending on the context; it was not acceptable, for example, in the transition from ASP.NET MVC 2 to ASP.NET MVC 3. For this reason, Microsoft opted for the use of dependency resolvers, a fancy name for a classic service locator component. More realistically, a service locator is the only option you have to add an extensibility point to a large existing codebase that you don’t want (or are not allowed) to refactor significantly.
Load comments