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.


Note
Although this chapter talks about the mechanics of how to provide services to MVC, it doesn't talk about how to implement any of those specific services; for that, you should consult Chapter 14.

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();

Using NuGet to Get Your Container
It certainly would be ideal if you didn't have to implement the IDependencyResolver interface on your own, just because you want to use dependency injection. Thankfully, NuGet can come to the rescue here.
NuGet is the package manager included with ASP.NET MVC. It enables you to add references to common open source projects on the Web with almost no effort. For more information on using NuGet, see Chapter 10.
At the time of this writing, a search on NuGet for phrases like “IoC” and “dependency” shows several dependency injection containers available for download. Many of them have a corresponding MVC support package, which means they come bundled with an implementation of MVC's IDependencyResolver.

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 there are two methods on the dependency resolver interface — that's because MVC consumes services in two different ways.


Should You Consume DependencyResolver in Your Application?
You might be tempted to consume IDependencyResolver from within your own application. Resist that temptation.
The dependency resolver interface is exactly what MVC needs — and nothing more. It's not intended to hide or replace the traditional API of your dependency injection container. Most containers have complex and interesting APIs; in fact, it's likely that you will choose your container based on the APIs and features that it offers more than any other reason.

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 12.1 shows the list of singly registered services that MVC uses.

Table 12.1 Singly Registered Services in MVC

Service (Traditional Registration API)

Default Service Implementation
IControllerActivator (none)
DefaultControllerActivator
IControllerFactory (ControllerBuilder.Current
.SetControllerFactory)
DefaultControllerFactory
IViewPageActivator (none)
DefaultViewPageActivator
ModelMetadataProvider
(ModelMetadataProviders.Current)
DataAnnotationsModelMetadataProvider

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 there are none present in the resolver.

When listing the multiply registered services that MVC supports, there is a designation titled “multi-service model,” with one of two values:

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

Table 12.2 shows the list of multiply registered services that MVC uses, including designations to show which are cooperative or competitive.

Table 12.2 Multiply Registered Services in MVC

Service (Traditional Registration API)

Default Service Implementations
IFilterProvider (FilterProviders.Providers)
Multi-service model: cooperative
FilterAttributeFilterProvider
GlobalFilterCollection
ControllerInstanceFilterProvider
IModelBinderProvider
(ModelBinderProviders.BinderProviders)
Multi-service model: competitive
None
IViewEngine (ViewEngines.Engines)
Multi-service model: competitive
WebFormViewEngine
RazorViewEngine
ModelValidatorProvider
(ModelValidatorProviders.Providers)
Multi-service model: cooperative
DataAnnotationsModelValidatorProvider
DataErrorInfoModelValidatorProvider
ClientDataTypeModelValidatorProvider
ValueProviderFactory
(ValueProviderFactories.Factories)
Multi-service model: competitive
ChildActionValueProviderFactory
FormValueProviderFactory
JsonValueProviderFactory
RouteDataValueProviderFactory
QueryStringValueProviderFactory
HttpFileCollectionValueProviderFactory

Arbitrary Objects in MVC

There are two special cases 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.


Custom Controller Factories and Activators
It's important to note that because the controller factory is ultimately responsible for turning controller names into controller objects, any replacement of the controller factory may disable the functionality of the controller activator. In MVC versions prior to MVC 3, the controller activator did not exist, so any custom controller factory designed for an older version of MVC will not know about the dependency resolver or controller activators. If you write a new controller factory, you should consider using controller activators whenever possible.

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.


ASP.NET's Build Manager
The compilation of views into classes is the responsibility of a component of the core ASP.NET run time called BuildManager. This class has many duties, including converting .aspx and .ascx files into classes for consumption by WebForms applications.
The build manager system is extensible, like much of the ASP.NET core run time, so you can take advantage of this compilation model to convert input files into classes at run time in your applications. In fact, the ASP.NET core run time doesn't know anything about Razor; the ability to compile .cshtml and .vbhtml files into classes exists because the ASP.NET Web Pages team wrote a build manager extension called a build provider.
Examples of third-party libraries that did this were the earlier releases of the SubSonic project, an object-relational mapper (ORM) written by Rob Conery. In this case, SubSonic would consume a file that described a database to be mapped, and at run time it would generate the ORM classes automatically to match the database tables.
The build manager operates during design time in Visual Studio, so any compilation that it's doing is available while writing your application. This includes IntelliSense support inside of Visual Studio.

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

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