Chapter 13
Dependency Injection

—by Brad Wilson

What's In This Chapter?

  • Understanding software design patterns
  • Using the dependency resolver in MVC
  • Using the dependency resolver in Web API

As of version 3, ASP.NET MVC has included a dependency resolver that dramatically improves the ability of an application to participate in dependency injection for both services consumed by MVC and commonly created classes like controllers and view pages.

To understand how the dependency resolver works, we first need to define some of the common software patterns that it uses. If you're already familiar with patterns such as service location and dependency injection, you might want to skim or skip the next section and go directly to the “Dependency Resolution in MVC” section.

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 identification 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 definition 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: Inversion of Control

Everyone has probably seen (or written) code like this:

public class EmailService
{
      public void SendMessage() { ... }
}
public class NotificationSystem
{
      private EmailService svc;
      public NotificationSystem()
      {
            svc = new EmailService();
      }
      public void InterestingEventHappened()
      {
            svc.SendMessage();
      }
}

You can see that NotificationSystem has a dependency on EmailService. When a component has a dependency on something else, it's called coupling. In this case, the notification system creates an instance of the e-mail service directly inside of the notification system's constructor; in other words, the notification system knows exactly what kind of service class it's creating and consuming. This coupling is an indication of how interconnected your code is. A class that knows a lot about the other classes it interacts with (as in the preceding example) is said to be tightly coupled.

In software design, tight coupling is often considered to be a liability in your design. When one class knows explicitly about the design and implementation of another class, you raise the risk that changes to one class will break the other class.

Also consider another potential problem with the preceding design: What if the notification system wants to start sending other kinds of messages when the interesting event happens? For example, maybe the administrator of the system wants to start getting text messages instead of e-mails, or also wants to start logging every notification into a database so they can be reviewed at a later time. To enable this behavior, you have to dive back into the implementation of the notification system.

To reduce coupling, you generally take two separate but related steps:

  1. Introduce an abstraction layer between two pieces of code.

    To perform this step in .NET, you often use interfaces (or abstract classes) to represent the abstractions between two classes. Using the previous example, you introduce an interface to represent your abstraction, and ensure that your code only calls methods or properties on that interface. Your private copy becomes an instance of that interface rather than the concrete type, and you limit the knowledge of the actual type to the constructor, as follows:

    public interface IMessagingService
    {
          void SendMessage();
    }
    public class EmailService : IMessagingService
    {
          public void SendMessage() { ... }
    }
    public class NotificationSystem
    {
          private IMessagingService svc;
          public NotificationSystem()
          {
                 svc = new EmailService();
          }
          public void InterestingEventHappened()
          {
                 svc.SendMessage();
          }
    }
  2. Move the responsibility of choosing the implementation of the abstraction to outside of the consuming class.
  3. You need to move the creation of the EmailService class outside of NotificationSystem.

The inversion of control (IoC) pattern is abstract; it says that one should move dependency creation out of the consumer class, but it doesn't talk about exactly how to achieve that. The following sections explore two popular ways to apply the inversion of control pattern to achieve this responsibility shift: service locator and dependency injection.

Design Pattern: Service Locator

The service locator pattern says that inversion of control is achieved by having components get their dependencies through an external component known as the service locator. Sometimes a service locator is a very specific interface, with strongly typed requests for specific services, and sometimes it may show up as a very generic way to request services of any arbitrary type.

Strongly Typed Service Locator

A strongly typed service locator for the sample application might have an interface like this:

public interface IServiceLocator
{
      IMessagingService GetMessagingService();
}

In this case, when you need an implementation of IMessagingService, you know to call GetMessagingService. The method returns exactly IMessagingService, so you won't need to cast the result.

You'll notice that I'm showing the service locator as an interface here rather than as a concrete type. Remember that one of your goals is to reduce the tight coupling between components; this includes the coupling between the consumer code and the service locator itself. If the consumer code is coded against IServiceLocator, that means you can substitute alternative implementations at run time as appropriate. This can have tremendous value in unit testing, as discussed in Chapter 14.

Now if you rewrite NotificationSystem in terms of the strongly typed service locator, it might look like this:

public class NotificationSystem
{
      private IMessagingService svc;
      public NotificationSystem(IServiceLocator locator)
      {
             svc = locator.GetMessagingService();
      }
      public void InterestingEventHappened()
      {
             svc.SendMessage();
      }
}

This assumes that anybody who creates an instance of NotificationSystem will have access to a service locator. What's convenient is that if your application creates instances of NotificationSystem through the service locator, then the locator can pass itself to the NotificationSystem constructor; if you create instances of NotificationSystem outside of the service locator, you must provide an implementation of the service locator to NotificationSystem so that it can find its dependencies.

Why might you choose a strongly typed service locator? It's fairly easy to understand and consume: You know exactly what kinds of things you can get from this service locator (and, perhaps just as important, what kinds of services you cannot get). Additionally, if you need some parameters to create the implementation of IMessagingService, you can request them directly as parameters to the call to GetMessagingService.

Why might you not choose a strongly typed service locator? One reason is that this service locator is limited to creating objects of types that have been predetermined at the time that IServiceLocator was designed. It's not capable of creating any other types. Another is that it could become a maintenance burden from having to constantly expand the definition of IServiceLocator as you find need for more services in your application.

Weakly Typed Service Locator

If the downsides of a strongly typed service locator seem to outweigh the upsides, you could consider using a weakly typed service locator instead. It might look something like this:

public interface IServiceLocator
{
      object GetService(Type serviceType);
}

This variant of the service locator pattern is much more flexible, because it allows you to ask for any arbitrary service type. It's called a weakly typed service locator because it takes a Type and returns an un-typed instance (that is, an object of type Object). You need to cast the result of the call to GetService to get the correctly typed object back.

NotificationSystem with this version of the service locator might look something like this:

public class NotificationSystem
{
      private IMessagingService svc;
      public NotificationSystem(IServiceLocator locator)
      {
             svc = (IMessagingService)
                   locator.GetService(typeof(IMessagingService));
      }
      public void InterestingEventHappened()
      {
             svc.SendMessage();
      }
}

This code is a little less pretty than the previous version, owing primarily to the required casting to IMessagingService. With the introduction of generics in .NET 2.0, you could have also included a generic version of the GetService method:

public interface IServiceLocator
{
      object GetService(Type serviceType);
      TService GetService<TService>();
}

The contract for such a method implies that it will return an object already cast to the correct type (notice that its return type is TService now instead of Object). That makes the consuming code quite a bit cleaner:

public class NotificationSystem
{
      private IMessagingService svc;
      public NotificationSystem(IServiceLocator locator)
      {
             svc = locator.GetService<IMessagingService>();
      }
      public void InterestingEventHappened()
      {
             svc.SendMessage();
      }
}

The downside to this weakly typed service locator approach is that it forces implementers of IServiceLocator to create two nearly identical methods instead of one. This unfortunate duplication of effort can be eliminated with a feature introduced into .NET 3.5: extension methods.

Extension methods are written as static methods on a static class, and utilize the special this keyword on their first parameter to indicate what type this extension method is attached to. Separating the generic GetService method into an extension method yields the following:

public interface IServiceLocator
{
      object GetService(Type serviceType);
}
public static class ServiceLocatorExtensions
{
      public static TService GetService<TService>(this IServiceLocator locator)
      {
             return (TService)locator.GetService(typeof(TService));
      }
}

This eliminates the duplication and extra effort associated with the generic version of the method. You write it once and everybody can take advantage of your implementation.

Why might you choose a weakly typed locator? It allows you to fix many of the downsides of the strongly typed locator; that is, you get an interface that can create arbitrary types without knowing about them ahead of time, and it reduces your maintenance burden because the interface is not constantly evolving.

On the other hand, a weakly typed locator interface doesn't communicate anything about the kinds of services that might be requested, and it doesn't offer a simple way to customize the creation of the service. You could add an arbitrary optional array of objects as “creation parameters” for the service, but the only way you know services would require parameters is by way of external documentation.

The Pros and Cons of Service Locators

Using a service locator is relatively straightforward: You get the service locator from somewhere and ask it for your dependencies. You might find the service locator in a known (global) location, or you might get the service locator provided to you by whoever is creating it. As your dependencies change, your signature stays the same, because the only thing you require to find your dependencies is the locator.

The benefit of the constant signature is as much a downside as it is an upside. It creates opacity of requirements for your component: The developers who consume your component can't tell just by looking at the constructor signature what your service requirements are going to be. They are forced to consult documentation, which may be out of date, or simply to pass in an empty service locator and see what kinds of things you request.

This opacity of requirements is a strong driver behind choosing your next IoC pattern: dependency injection.

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 significant 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 first benefit is that the implementation of the constructor is dramatically simplified. 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 benefit 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 benefit, 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 definitely 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 fulfilled. The consumer of your class could manually provide you with all those dependencies, but that can become a pretty significant burden over time. If your entire system is designed to support dependency injection, creating any component means you have to understand how to fulfill 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 fulfilling 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 configured 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 fulfill 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 configuration 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 configuration 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 preconfigured, 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 find documentation online that will discuss how to do advanced configuration operations.

DEPENDENCY RESOLUTION IN MVC

Now that you understand the fundamentals of inversion of control, we can talk about how it works inside of ASP.NET MVC.

The primary way that MVC talks to containers is through an interface created for MVC applications: IDependencyResolver. The interface is defined as follows:

public interface IDependencyResolver
{
      object GetService(Type serviceType);
      IEnumerable<object> GetServices(Type serviceType);
}

This interface is consumed by the MVC framework itself. If you want to register a dependency injection container (or a service locator, for that matter), you need to provide an implementation of this interface. You can typically register an instance of the resolver inside your Global.asax file, with code much like this:

DependencyResolver.Current = new MyDependencyResolver();

Because prior versions of MVC did not have this concept of a dependency resolver, it is considered optional (and there isn't one registered by default). If you don't need dependency resolution support, you are not required to have a resolver. In addition, almost everything that MVC can consume as a service can be registered either inside of the resolver or with a more traditional registration point (and, in many cases, both).

When you want to provide services to the MVC framework, you can choose which registration mode suits you best. MVC generally consults the dependency resolver first when it needs services, and falls back to the traditional registration points when it can't find the service in the dependency resolver.

The code we can't show here is how to register something in the dependency resolver. Why not? Because the registration API that you'll utilize is dependent on which dependency injection container you choose to use. You should consult the container documentation for information on registration and configuration.

You'll notice that two methods are on the dependency resolver interface—that's because MVC consumes services in two different ways.

Singly Registered Services in MVC

MVC has services that it consumes for which the user can register one (and exactly one) instance of that service. It calls these services singly registered services, and the method used to retrieve singly registered services from the resolver is GetService.

For all the singly registered services, MVC consults the dependency resolver for the service the first time it is needed, and caches the result for the lifetime of the application. You can either use the dependency resolver API or the traditional registration API (when available), but you cannot use both because MVC is expecting to use exactly one instance of any singly registered service.

Implementers of GetService should return an instance of the service that is registered in the resolver, or return null if the service is not present in the resolver. Table 13.1 below shows the default service implementations for singly registered MVC services; Table 13.2 shows the traditional registration APIs for these services.

Table 13.1 Default Service Implementations for Singly Registered Services in MVC

Service Default Service Implementation
IControllerActivator DefaultControllerActivator
IControllerFactory DefaultControllerFactory
IViewPageActivator DefaultViewPageActivator
ModelMetadataProvider DataAnnotationsModelMetadataProvider

Table 13.2 Traditional Registration APIs for Singly Registered Services in MVC

Service Traditional Registration API
IControllerActivator None
IControllerFactory ControllerBuilder.Current
.SetControllerFactory
IViewPageActivator None
ModelMetadataProvider ModelMetadataProviders.Current

Multiply Registered Services in MVC

In contrast with singly registered services, MVC also consumes some services where the user can register many instances of the service, which then compete or collaborate to provide information to MVC. It calls these kinds of services multiply registered services, and the method that is used to retrieve multiply registered services from the resolver is GetServices.

For all the multiply registered services, MVC consults the dependency resolver for the services the first time they are needed, and caches the results for the lifetime of the application. You can use both the dependency resolver API and the traditional registration API, and MVC combines the results in a single merged services list. Services registered in the dependency resolver come before services registered with the traditional registration APIs. This is important for those multiply registered services that compete to provide information; that is, MVC asks each service instance one by one to provide information, and the first one that provides the requested information is the service instance that MVC will use.

Implementers of GetServices should always return a collection of implementations of the service type that are registered in the resolver, or return an empty collection if none are present in the resolver.

MVC supports two “multi-service models” for multiply registered services as explained below:

  • Competitive services: Those where the MVC framework will go from service to service (in order), and ask the service whether it can perform its primary function. The first service that responds that it can fulfill the request is the one that MVC uses. These questions are typically asked on a per-request basis, so the actual service that's used for each request may be different. An example of competitive services is the view engine service: Only a single view engine will render a view in a particular request.
  • Cooperative services: Those where the MVC framework asks every service to perform its primary function, and all services that indicate that they can fulfill the request will contribute to the operation. An example of cooperative services is filter providers: Every provider may find filters to run for a request, and all filters found from all providers will be run.

The following lists show the multiply registered services that MVC uses, including designations to show which are cooperative or competitive.

Service: Filter Provider

  1. Interface: IFilterProvider
  2. Traditional Registration API: FilterProviders.Providers
  3. Multi-service model: cooperative
  4. Default Service Implementations:
    • FilterAttributeFilterProvider
    • GlobalFilterCollection
    • ControllerInstanceFilterProvider

Service: Model Binder Provider

  1. Interface: IModelBinderProvider
  2. Traditional Registration API: ModelBinderProviders.BinderProviders
  3. Multi-service model: competitive
  4. Default Service Implementations: None

Service: View Engine

  1. Interface: IViewEngine
  2. Traditional Registration API: ViewEngines.Engines
  3. Multi-service model: competitive
  4. Default Service Implementations:
    • WebFormViewEngine
    • RazorViewEngine

Service: Model Validator Provider

  1. Type: ModelValidatorProvider
  2. Traditional Registration API: ModelValidatorProviders.Providers
  3. Multi-service model: cooperative
  4. Default Service Implementations:
    • DataAnnotationsModelValidatorProvider
    • DataErrorInfoModelValidatorProvider
    • ClientDataTypeModelValidatorProvider

Service: Value Provider Factory

  1. Type: ValueProviderFactory
  2. Traditional Registration API: ValueProviderFactories.Factories
  3. Multi-service model: competitive
  4. Default Service Implementations:
    • ChildActionValueProviderFactory
    • FormValueProviderFactory
    • JsonValueProviderFactory
    • RouteDataValueProviderFactory
    • QueryStringValueProviderFactory
    • HttpFileCollectionValueProviderFactory

Arbitrary Objects in MVC

Two special cases exist where the MVC framework will request a dependency resolver to manufacture arbitrary objects—that is, objects that are not (strictly speaking) services. Those objects are controllers and view pages.

As you saw in the previous two sections, two services called activators control the instantiation of controllers and view pages. The default implementations of these activators ask the dependency resolver to create the controllers and view pages, and failing that, they will fall back to calling Activator.CreateInstance.

Creating Controllers

If you've ever tried to write a controller with a constructor with parameters before, at run time you'll get an exception that says, “No parameterless constructor defined for this object.” In an MVC application, if you look closely at the stack trace of the exception, you'll see that it includes DefaultControllerFactory as well as DefaultControllerActivator.

The controller factory is ultimately responsible for turning controller names into controller objects, so it is the controller factory that consumes IControllerActivator rather than MVC itself. The default controller factory in MVC splits this behavior into two separate steps: the mapping of controller names to types, and the instantiation of those types into objects. The latter half of the behavior is what the controller activator is responsible for.

Because the default controller activator simply asks the dependency resolver to make controllers for you, many dependency injection containers automatically provide dependency injection for controller instances because they have been asked to make them. If your container can make arbitrary objects without preconfiguration, you should not need to create a controller activator; simply registering your dependency injection container should be sufficient.

However, if your dependency injection container does not like making arbitrary objects, it will also need to provide an implementation of the activator. This allows the container to know that it's being asked to make an arbitrary type that may not be known of ahead of time, and allow it to take any necessary actions to ensure that the request to create the type will succeed.

The controller activator interface contains only a single method:

public interface IControllerActivator
{
      IController Create(RequestContext requestContext, Type controllerType);
}

In addition to the controller type, the controller activator is also provided with the RequestContext, which includes access to the HttpContext (including things like Session and Request), as well as the route data from the route that mapped to the request. You may also choose to implement a controller activator to help make contextual decisions about how to create your controller objects, because it has access to the context information. One example of this might be an activator that chooses to make different controller classes based on whether the logged-in user is an administrator or not.

Creating Views

Much as the controller activator is responsible for creating instances of controllers, the view page activator is responsible for creating instances of view pages. Again, because these types are arbitrary types that a dependency injection container will probably not be preconfigured for, the activator gives the container an opportunity to know that a view is being requested.

The view activator interface is similar to its controller counterpart:

public interface IViewPageActivator
{
   object Create(ControllerContext controllerContext, Type type);
}

In this case, the view page activator is given access to the ControllerContext, which contains not only the RequestContext (and thus HttpContext), but also a reference to the controller, the model, the view data, the temp data, and other pieces of the current controller state.

Like its controller counterpart, the view page activator is a type that is indirectly consumed by the MVC framework, rather than directly. In this instance, it is the BuildManagerViewEngine (the abstract base class for WebFormViewEngine and RazorViewEngine) that understands and consumes the view page activator.

A view engine's primary responsibility is to convert view names into view instances. The MVC framework splits the actual instantiation of the view page objects out into the view activator, while leaving the identification of the correct view files and the compilation of those files to the build manager view engine base class.

DEPENDENCY RESOLUTION IN WEB API

The new Web API feature (refer to Chapter 11) also includes the ability to support dependency resolution. The design of the dependency resolver in Web API is slightly different from the one in MVC but, in principle, serves the same purposes: to allow developers to easily get dependency injection for their controllers, as well as making it easy to provide services to Web API that are themselves created via dependency-injection techniques.

There are two significant differences in the dependency resolution implementation in Web API. One is that there are no static APIs for default registration of services; these old static APIs in MVC were there for historical reasons. Instead, there is a loosely typed service locator that can be accessed at HttpConfiguration.Services, where developers can enumerate and replace the default services used by Web API.

Another difference is that the actual dependency resolver API has been modified slightly to support the notion of scopes. One criticism of the original dependency resolver interface in MVC was the lack of any kind of resource-cleanup mechanism. After consultation with the community, we landed on a design that used the concept of a scope as the way that Web API would trigger this cleanup. The system automatically creates a new scope per request, which is available as an HttpRequestMessage extension method named GetDependencyScope. Like the dependency resolver interface, the scope interface has both GetService and GetServices methods; the difference is that resources acquired from the request-local scope will be released when the request is completed.

Getting or setting the dependency resolver for Web API is done via HttpConfiguration.DependencyResolver.

Singly Registered Services in Web API

Like MVC, Web API has services that it consumes for which the user can register one (and exactly one) instance of that service. The resolver retrieves these singly registered services by calling GetService.

For all the singly registered services, Web API consults the dependency resolver for the service the first time it is needed, and caches the result for the lifetime of the application. When Web API cannot find the service in the resolver, it uses the service found in the default services list in HttpConfiguration.Services. Table 13.3 shows the list of singly registered services that Web API uses.

Table 13.3 Singly Registered Services in Web API

Service Default Service Implementation
IActionValueBinder DefaultActionValueBinder
IApiExplorer ApiExplorer
IAssembliesResolver DefaultAssembliesResolver*
IBodyModelValidator DefaultBodyModelValidator
IContentNegotiator DefaultContentNegotiator
IDocumentationProvider None
IHostBufferPolicySelector None
IHttpActionInvoker ApiControllerActionInvoker
IHttpActionSelector ApiControllerActionSelector
IHttpControllerActivator DefaultHttpControllerActivator
IHttpControllerSelector DefaultHttpControllerSelector
IHttpControllerTypeResolver DefaultHttpControllerTypeResolver**
ITraceManager TraceManager
ITraceWriter None
ModelMetadataProvider CachedDataAnnotationsModel-MetadataProvider

* When the application is running in ASP.NET, this is replaced by WebHostAssembliesResolver.

** When the application is running in ASP.NET, this is replaced by WebHostHttpControllerTypeResolver.

Multiply Registered Services in Web API

Again borrowing the concepts from MVC, Web API has multiply registered services, and combines the list of those services from the dependency resolver with the list in HttpConfiguration.Services. To retrieve the services from the dependency resolver, Web API calls the GetServices method. The following lists show the multiply registered services that Web API uses, and whether those services are cooperative or competitive.

Service: Filter Provider

  1. Interface: IFilterProvider
  2. Multi-service model: cooperative
  3. Default Service Implementations:
    • ConfigurationFilterProvider
    • ActionDescriptorFilterProvider

Service: Model Binder Provider

  1. Type: ModelBinderProvider
  2. Multi-service model: competitive
  3. Default Service Implementations:
    • TypeConverterModelBinderProvider
    • TypeMatchModelBinderProvider
    • KeyValuePairModelBinderProvider
    • ComplexModelDtoModelBinderProvider
    • ArrayModelBinderProvider
    • DictionaryModelBinderProvider
    • CollectionModelBinderProvider
    • MutableObjectModelBinderProvider

Service: Model Validator Provider

  1. Type: ModelValidatorProvider
  2. Multi-service model: cooperative
  3. Default Service Implementations:
    • DataAnnotationsModelValidatorProvider
    • DataMemberModelValidatorProvider
    • InvalidModelValidatorProvider

Service: Value Provider Factory

  1. Type: ValueProviderFactory
  2. Multi-service model: competitive
  3. Default Service Implementations:
    • QueryStringValueProviderFactory
    • RouteDataValueProviderFactory

Arbitrary Objects in Web API

Three special cases exist where the Web API framework will request a dependency resolver to manufacture arbitrary objects—that is, objects that are not (strictly speaking) services. Like MVC, controllers are one class of these objects. The other two are model binders attached with the [ModelBinder] attribute and the per-controller services that are attached to controllers via [HttpControllerConfiguration].

The services attached via the attributes are cached for the lifetime of the application, just like the built-in services, which means Web API will request them from the dependency resolver attached to the configuration. Controllers, on the other hand, typically have request-scoped lifetimes, so they are requested from the scope that's attached to the request.

Dependency Resolvers in MVC vs. Web API

Although MVC and Web API share the idea of dependency resolvers, the actual interfaces are different, as described previously. In addition, the actual services that might be contained in those dependency resolvers are different, because MVC and Web API share no common service interfaces, either. That means that the implementation of the two dependency resolver interfaces differ, and you shouldn't expect an MVC dependency resolver to work in Web API (or vice versa).

That said, having those two dependency resolver interface implementations backed by the same concrete dependency injection container so that any custom services you use throughout your application would be available to both MVC and Web API controllers is perfectly reasonable. You should consult the documentation for your dependency injection container to determine how to use a single container for an application that includes both MVC and Web API.

SUMMARY

The dependency resolvers in ASP.NET MVC and Web API enable several new and exciting opportunities for dependency injection in your web applications. This can help you design applications that reduce tight coupling and encourage better “plugability,” which tends to lead to more flexible and powerful application development.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.178.165