13
The Autofac DI Container

In this chapter

  • Working with Autofac’s basic registration API
  • Managing component lifetime
  • Configuring difficult APIs
  • Configuring sequences, Decorators, and Composites

In the previous chapters, we discussed patterns and principles that apply to DI in general, but, apart from a few examples, we’ve yet to take a detailed look at how to apply them using any particular DI Container. In this chapter, you’ll see how these overall patterns map to Autofac. You’ll need to be familiar with the material from the previous chapters to fully benefit from this.

Autofac is a fairly comprehensive DI Container that offers a carefully designed and consistent API. It’s been around since late 2007 and is, at the time of writing, one of the most popular containers.1 

In this chapter, we’ll examine how Autofac can be used to apply the principles and patterns presented in parts 1–3. This chapter is divided into four sections. You can read each section independently, though the first section is a prerequisite for the other sections, and the fourth section relies on some methods and classes introduced in the third section.

This chapter should enable you to get started, as well as deal with the most common issues that can come up as you use Autofac on a daily basis. It’s not a complete treatment of Autofac; that would take several more chapters or perhaps a whole book in itself. If you want to know more about Autofac, the best place to start is at the Autofac home page at https://autofac.org.

13.1 Introducing Autofac

In this section, you’ll learn where to get Autofac, what you get, and how you start using it. We’ll also look at common configuration options. Table 13.1 provides fundamental information that you’re likely to need to get started.

Table 13.1 Autofac at a glance
QuestionAnswer
Where do I get it?From Visual Studio, you can get it via NuGet. The package name is Autofac. Alternatively, the NuGet package can be downloaded from the GitHub repository (https://github.com/autofac/Autofac/releases).
Which platforms are supported?.NET 4.5 (without a .NET Core SDK) and .NET Standard 1.1 (.NET Core 1.0, Mono 4.6, Xamarin.iOS 10.0, Xamarin.Mac 3.0, Xamarin.Android 7.0, UWP 10.0, Windows 8.0, Windows Phone 8.1). Older builds that support .NET 2.0 and Silverlight are available via NuGet history.
How much does it cost?Nothing. It’s open source.
How is it licensed?MIT License.
Where can I get help?You can get commercial support from companies associated with the Autofac developers. Read more about the options at https://autofac.readthedocs.io/en/latest/support.html. Other than commercial support, Autofac is still open source software with a thriving ecosystem, so you’re also likely (but not guaranteed) to get help by posting on Stack Overflow at https://stackoverflow.com or by using the official forum at https://groups.google.com/group/autofac.
On which version is this chapter based?4.9.0-beta1

Using Autofac isn’t that different from using the other DI Containers that we’ll discuss in the following chapters. As with Simple Injector and Microsoft.Extensions.DependencyInjection, usage is a two-step process, as figure 13.1 illustrates. First, you configure a ContainerBuilder, and when you’re done with that, you use it to build a container to resolve components.

13-01.eps

Figure 13.1 The pattern for using Autofac is to first configure it, and then resolve components.

When you’re done with this section, you should have a good feeling for the overall usage pattern of Autofac, and you should be able to start using it in well-behaved scenarios — where all components follow proper DI patterns like Constructor Injection. Let’s start with the simplest scenario and see how you can resolve objects using an Autofac container.

13.1.1 Resolving objects

The core service of any DI Container is to compose object graphs. In this section, we’ll look at the API that lets you compose object graphs with Autofac.

By default, Autofac requires you to register all relevant components before you can resolve them. This behavior, however, is configurable. The following listing shows one of the simplest possible uses of Autofac.

Listing 13.1 Simplest possible use of Autofac

var builder = new ContainerBuilder();

builder.RegisterType<SauceBéarnaise>();

IContainer container = builder.Build();

ILifetimeScope scope = container.BeginLifetimeScope();

SauceBéarnaise sauce = scope.Resolve<SauceBéarnaise>();

As figure 13.1 shows, you need a ContainerBuilder instance to configure components. Here, you register the concrete SauceBéarnaise class with builder so that when you ask it to build a container, the resulting container is configured with the SauceBéarnaise class. This again enables you to resolve the SauceBéarnaise class from the container.

With Autofac, however, you never resolve from the root container itself, but from a lifetime scope. Section 13.2.1 goes into more detail about lifetime scope and why resolving from the root container is a bad thing.

If you don’t register the SauceBéarnaise component, attempting to resolve it throws a ComponentNotRegisteredException with the following message:

The requested service "Ploeh.Samples.MenuModel.SauceBéarnaise" has not been registered. To avoid this exception, either register a component to provide the service, check for service registration using IsRegistered(), or use the ResolveOptional() method to resolve an optional dependency.

Not only can Autofac resolve concrete types with parameterless constructors, it can also Auto-Wire a type with other Dependencies. All these Dependencies need to be registered. For the most part, you’ll want to program to interfaces, because this introduces loose coupling. To support this, Autofac lets you map Abstractions to concrete types.

Mapping Abstractions to concrete types

Whereas your application’s root types will typically be resolved by their concrete types, loose coupling requires you to map Abstractions to concrete types. Creating instances based on such maps is the core service offered by any DI Container, but you must still define the map. In this example, you map the IIngredient interface to the concrete SauceBéarnaise class, which allows you to successfully resolve IIngredient:

var builder = new ContainerBuilder();

builder.RegisterType<SauceBéarnaise>()
    .As<IIngredient>();    ①  

IContainer container = builder.Build();

ILifetimeScope scope = container.BeginLifetimeScope();

IIngredient sauce = scope.Resolve<IIngredient>();    ②  

The As<T> method allows a concrete type to be mapped to a particular Abstraction. Because of the previous As<IIngredient>() call, SauceBéarnaise can now be resolved as IIngredient.

You use the ContainerBuilder instance to register types and define maps. The RegisterType method lets you register a concrete type.

As you saw in listing 13.1, you can stop right there if you only want to register the SauceBéarnaise class. You can also continue with the As method to define how the concrete type should be registered.2 

In many cases, the generic API is all you need. Although it doesn’t offer the same degree of type safety as some other DI Containers, it’s still a readable way to configure the container. Still, there are situations where you need a more weakly typed way to resolve services. With Autofac, this is also possible.

Resolving weakly typed services

Sometimes you can’t use a generic API, because you don’t know the appropriate type at design time. All you have is a Type instance, but you’d still like to get an instance of that type. You saw an example of that in section 7.3, where we discussed ASP.NET Core MVC’s IControllerActivator class. The relevant method is this:

object Create(ControllerContext context);

As shown previously in listing 7.8, the ControllerContext captures the controller’s Type, which you can extract using the ControllerTypeInfo property of the ActionDescriptor property:

Type controllerType = context.ActionDescriptor.ControllerTypeInfo.AsType();

Because you only have a Type instance, you can’t use the generic Resolve<T> method, but must resort to a weakly typed API. Autofac offers a weakly typed overload of the Resolve method that lets you implement the Create method like this:

Type controllerType = context.ActionDescriptor.ControllerTypeInfo.AsType();
return scope.Resolve(controllerType);

The weakly typed overload of Resolve lets you pass the controllerType variable directly to Autofac. Typically, this means you have to cast the returned value to some Abstraction, because the weakly typed Resolve method returns object. In the case of IControllerActivator, however, this isn’t required, because ASP.NET Core MVC doesn’t require controllers to implement any interface or base class.

No matter which overload of Resolve you use, Autofac guarantees that it’ll return an instance of the requested type or throw an exception if there are Dependencies that can’t be satisfied. When all required Dependencies have been properly configured, Autofac can Auto-Wire the requested type.

In the previous example, scope is an instance of Autofac.ILifetimeScope. To be able to resolve the requested type, all loosely coupled Dependencies must have been previously configured. There are many ways to configure Autofac, and the next section reviews the most common ones.

13.1.2 Configuring the ContainerBuilder

As we discussed in section 12.2, you can configure a DI Container in several conceptually different ways. Figure 12.5 reviewed the options: configuration files, Configuration as Code, and Auto-Registration. Figure 13.2 shows these options again.

13-02.eps

Figure 13.2 The most common ways to configure a DI Container shown against dimensions of explicitness and the degree of binding

The core configuration API is centered on code and supports both Configuration as Code and convention-based Auto-Registration. Support for configuration files can be plugged in using the Autofac.Configuration NuGet package. Autofac supports all three approaches and lets you mix them all within the same container. In this section, you’ll see how to use each of these three types of configuration sources.

Configuring the ContainerBuilder using Configuration as Code

In section 13.1, you saw a brief glimpse of Autofac’s strongly typed configuration API. Here, we’ll examine it in greater detail.

All configurations in Autofac use the API exposed by the ContainerBuilder class, although most of the methods you use are extension methods. One of the most commonly used methods is the RegisterType method that you’ve already seen:

builder.RegisterType<SauceBéarnaise>().As<IIngredient>();

Registering SauceBéarnaise as IIngredient hides the concrete class so that you can no longer resolve SauceBéarnaise with this registration. But you can easily fix this by using an overload of the As method that lets you specify that the concrete type maps to more than one registered type:

builder.RegisterType<SauceBéarnaise>().As<SauceBéarnaise, IIngredient>();

Instead of registering the class only as IIngredient, you can register it as both itself and the interface it implements. This enables the container to resolve requests for both SauceBéarnaise and IIngredient. As an alternative, you can also chain calls to the As method:

builder.RegisterType<SauceBéarnaise>()
    .As<SauceBéarnaise>()
    .As<IIngredient>();

This produces the same result as in the previous example. The difference between the two registrations is simply a matter of style.

Three generic overloads of the As method let you specify one, two, or three types. If you need to specify more, there’s also a non-generic overload that you can use to specify as many types as you like.

In real applications, you always have more than one Abstraction to map, so you must configure multiple mappings. This is done with multiple calls to RegisterType:

builder.RegisterType<SauceBéarnaise>().As<IIngredient>();
builder.RegisterType<Course>().As<ICourse>();

This example maps IIngredient to SauceBéarnaise, and ICourse to Course. There’s no overlap of types, so it should be pretty evident what’s going on. But you can also register the same Abstraction several times:

builder.RegisterType<SauceBéarnaise>().As<IIngredient>();
builder.RegisterType<Steak>().As<IIngredient>();

Here, you register IIngredient twice. If you resolve IIngredient, you get an instance of Steak. The last registration wins, but previous registrations aren’t forgotten. Autofac handles multiple configurations for the same Abstraction well, but we’ll get back to this topic in section 13.4.

There are more-advanced options available for configuring Autofac, but you can configure an entire application with the methods shown here. But to save yourself from too much explicit maintenance of container configuration, you could instead consider a more convention-based approach using Auto-Registration.

Configuring the ContainerBuilder using Auto-Registration

In many cases, registrations will be similar. Such registrations are tedious to maintain, and explicitly registering each and every component might not be the most productive approach, as we discussed in section 12.3.3.

Consider a library that contains many IIngredient implementations. You can configure each class individually, but it’ll result in numerous similar-looking calls to the RegisterType method. What’s worse is that every time you add a new IIngredient implementation, you must also explicitly register it with the ContainerBuilder if you want it to be available. It’d be more productive to state that all implementations of IIngredient found in a given assembly should be registered.

This is possible using the RegisterAssemblyTypes method. This method lets you specify an assembly and configure all selected classes from this assembly into a single statement. To get the Assembly instance, you can use a representative class (in this case, Steak):

Assembly ingredientsAssembly = typeof(Steak).Assembly;

builder.RegisterAssemblyTypes(ingredientsAssembly).As<IIngredient>();

The RegisterAssemblyTypes method returns the same interface as the RegisterType method, so many of the same configuration options are available. This is a strong feature, because it means that you don’t have to learn a new API to use Auto-Registration.

In the previous example, we used the As method to register all types in the assembly as IIngredient services. The previous example also unconditionally configures all implementations of the IIngredient interface, but you can provide filters that let you select only a subset. Here’s a convention-based scan where you add only classes whose name starts with Sauce:

Assembly ingredientsAssembly = typeof(Steak).Assembly;

builder.RegisterAssemblyTypes(ingredientsAssembly)
    .Where(type => type.Name.StartsWith("Sauce"))
    .As<IIngredient>();

When you register all types in an assembly, you can use a predicate to define a selection criterion. The only difference from the previous code example is the inclusion of the Where method, where you select only those types whose names start with Sauce.

There are many other methods that let you provide various selection criteria. The Where method gives you a filter that only lets those types through that match the predicate, but there’s also an Except method that works the other way around.

Apart from selecting the correct types from an assembly, another part of Auto-Registration is defining the correct mapping. In the previous examples, we used the As method with a specific interface to register all selected types against that interface. But sometimes you’ll want to use different conventions.

Let’s say that instead of interfaces, you use abstract base classes, and you want to register all types in an assembly where the name ends with Policy. For this purpose, there are several other overloads of the As method, including one that takes a Func<Type, Type> as input:

Assembly policiesAssembly = typeof(DiscountPolicy).Assembly;

builder.RegisterAssemblyTypes(policiesAssembly)
    .Where(type => type.Name.EndsWith("Policy"))
    .As(type => type.BaseType);

You can use the code block provided to the As method for every single type whose name ends with Policy. This ensures that all classes with the Policy suffix will be registered against their base class, so that when the base class is requested, the container will resolve it to the type mapped by this convention. Convention-based registration with Autofac is surprisingly easy and uses an API that closely mirrors the API exposed by the singular RegisterType method.

Auto-Registration of generic Abstractions using AsClosedTypesOf

During the course of chapter 10, you refactored the big, obnoxious IProductService interface to the ICommandService<TCommand> interface of listing 10.12. Here’s that Abstraction again:

public interface ICommandService<TCommand>
{
    void Execute(TCommand command);
}

As discussed in chapter 10, every command Parameter Object represents a use case and, apart from any Decorators that implement Cross-Cutting Concerns, there’ll be a single implementation per use case. The AdjustInventoryService of listing 10.8 was given as an example. It implemented the “adjust inventory” use case. The next listing shows this class again.

Listing 13.2 The AdjustInventoryService from chapter 10

public class AdjustInventoryService : ICommandService<AdjustInventory>
{
    private readonly IInventoryRepository repository;

    public AdjustInventoryService(IInventoryRepository repository)
    {
        this.repository = repository;
    }

    public void Execute(AdjustInventory command)
    {
        var productId = command.ProductId;

        ...
    }
}

Any reasonably complex system will easily implement hundreds of use cases. This is an ideal candidate for using Auto-Registration. With Autofac, this couldn’t be easier, as the following listing shows.

Listing 13.3 Auto-Registration of ICommandService<TCommand> implementations

Assembly assembly = typeof(AdjustInventoryService).Assembly;

builder.RegisterAssemblyTypes(assembly)
    .AsClosedTypesOf(typeof(ICommandService<>));

As in the previous listings, you make use of the RegisterAssemblyTypes method to select classes from the supplied assembly. Instead of calling As, however, you call AsClosedTypesOf and supply the open-generic ICommandService<TCommand> interface.

Using the supplied open-generic interface, Autofac iterates through the list of assembly types and registers all types that implement a closed-generic version of ICommandService<TCommand>. What this means, for instance, is that AdjustInventoryService is registered, because it implements ICommandService<AdjustInventory>, which is a closed-generic version of ICommandService<TCommand>.

The RegisterAssemblyTypes method takes a params array of Assembly instances, so you can supply as many assemblies to a single convention as you’d like. It’s not a far-fetched thought to scan a folder for assemblies and supply them all to implement add-in functionality. In that way, add-ins can be added without recompiling a core application. This is one way to implement late binding; another is to use configuration files.

Configuring the ContainerBuilder using configuration files

When you need to change a container’s registrations without recompiling the application, configuration files are a viable option. As we stated in section 12.2.1, you should use configuration files only for those types of your DI configuration that require late binding: prefer Configuration as Code or Auto-Registration in all other types and all other parts of your configuration.

The most natural way to use configuration files is to embed those into the standard .NET application configuration file. This is possible, but you can also use a standalone configuration file if you need to vary the Autofac configuration independently of the standard .config file. Whether you want to do one or the other, the API is almost the same.

Once you have a reference to Autofac.Configuration, you can ask the ContainerBuilder to read component registrations from the standard .config file like this:

var configuration = new ConfigurationBuilder()    ①  
    .AddJsonFile("autofac.json")    ①  
    .Build();    ①  

builder.RegisterModule(    ②  
    new ConfigurationModule(configuration));    ②  

Here’s a simple example that maps the IIngredient interface to the Steak class:

{
  "defaultAssembly": "Ploeh.Samples.MenuModel",    ①  
  "components": [
  {
    "services": [{    ②  
      "type": "Ploeh.Samples.MenuModel.IIngredient"    ②  
    }],    ②  
    "type": "Ploeh.Samples.MenuModel.Steak"    ②  
  }]
}

The type name must include the namespace so that Autofac can find that type. Because both types are located in the default assembly Ploeh.Samples.MenuModel, the assembly name can be omitted in this case. Although the defaultAssembly attribute is optional, it’s a nice feature that can save you from a lot of typing if you have many types defined in the same assembly.

The components element is a JSON array of component elements. The previous example contained a single component, but you can add as many component elements as you like. In each element, you must specify a concrete type with the type attribute. This is the only required attribute. To map the Steak class to IIngredient, you can use the optional services attribute.

A configuration file is a good option when you need to change the configuration of one or more components without recompiling the application, but because it tends to be quite brittle, you should reserve it for only those occasions. Use either Auto-Registration or Configuration as Code for the main part of the container’s configuration.

This section introduced the Autofac DI Container and demonstrated these fundamental mechanics: how to configure a ContainerBuilder and, subsequently, how to use the constructed container to resolve services. Resolving services is easily done with a single call to the Resolve method, so the complexity involves configuring the container. This can be done in several different ways, including imperative code and configuration files.

Until now, we’ve only looked at the most basic API, so there are more-advanced areas we have yet to cover. One of the most important topics is how to manage component lifetime.

13.2 Managing lifetime

In chapter 8, we discussed Lifetime Management, including the most common conceptual Lifestyles such as Singleton, Scoped, and Transient. Autofac supports several different Lifestyles, enabling you to configure the lifetime of all services. The Lifestyles shown in table 13.2 are available as part of the API.

Table 13.2 Autofac instance scopes (Lifestyles)
Autofac namePattern nameComments
Per-dependencyTransientThis is the default instance scope. Instances are tracked by the container.
Single instanceSingletonInstances are disposed of when the container is disposed of.
Per-lifetime scopeScopedTies the lifetime of components together with a lifetime scope (see section 13.2.1).

Autofac’s implementations of Transient and Singleton are equivalent to the general Lifestyles described in chapter 8, so we won’t spend much time on them in this chapter. Instead, in this section, you’ll see how you can define Lifestyles for components both in code and with configuration files. We’ll also look at Autofac’s concept of lifetime scopes and how they can be used to implement the Scoped Lifestyle. By the end of this section, you should be able to use Autofac’s Lifestyles in your own application. Let’s start by reviewing how to configure instance scopes for components.

13.2.1 Configuring instance scopes

In this section, we’ll review how to manage component instance scopes with Autofac. Instance scopes are configured as part of registering components, and you can define them both with code and via a configuration file. We’ll look at each in turn.

Configuring instance scopes with code

Instance scope is defined as part of the registrations you make on a ContainerBuilder instance. It’s as easy as this:

builder.RegisterType<SauceBéarnaise>().SingleInstance();

This configures the concrete SauceBéarnaise class as a Singleton so that the same instance is returned each time SauceBéarnaise is requested. If you want to map an Abstraction to a concrete class with a specific lifetime, you can use the usual As method and place the SingleInstance method call wherever you like. These two registrations are functionally equivalent:

builder                                builder
    .RegisterType<SauceBéarnaise>()        .RegisterType<SauceBéarnaise>()
    .As<IIngredient>()                     .SingleInstance()
    .SingleInstance();                     .As<IIngredient>();

Notice that the only difference is that we’ve swapped the As and SingleInstance method calls. Personally, we prefer the sequence on the left, because the RegisterType and As method calls form a mapping between a concrete class and an Abstraction. Keeping them close together makes the registration more readable, and you can then state the instance scope as a modification to the mapping.

Although Transient is the default instance scope, you can explicitly state it. These two examples are equivalent:

builder                                builder
    .RegisterType<SauceBéarnaise>();       .RegisterType<SauceBéarnaise>()
                                           .InstancePerDependency();

Configuring instance scope for convention-based registrations is done using the same method as for singular registrations:

Assembly ingredientsAssembly = typeof(Steak).Assembly;

builder.RegisterAssemblyTypes(ingredientsAssembly).As<IIngredient>()
    .SingleInstance();

You can use SingleInstance and the other related methods to define the instance scope for all registrations in a convention. In the previous example, you defined all IIngredient registrations as Singleton. In the same way that you can register components both in code and in a configuration file, you can also configure instance scope in both places.

Configuring instance scopes with configuration files

When you need to define components in a configuration file, you might want to configure their instance scopes in the same place; otherwise, it would result in all components getting the same default Lifestyle. This is easily done as part of the configuration schema you saw in section 13.1.2. You can use the optional instance-scope attribute to declare the Lifestyle.

Listing 13.4 Using the optional instance-scope attribute

{
  "defaultAssembly": "Ploeh.Samples.MenuModel",
  "components": [
  {
    "services": [{
      "type": "Ploeh.Samples.MenuModel.IIngredient"
    }],
    "type": "Ploeh.Samples.MenuModel.Steak",
    "instance-scope": "single-instance"    ①  
  }]
}

Compared to the example in section 13.1.2, the only difference is the added instance-scope attribute that configures the instance as a Singleton. When you omit the instance-scope attribute, per-dependency is used, which is Autofac’s equivalent to Transient.

Both in code and in a file, it’s easy to configure instance scopes for components. In all cases, it’s done in a rather declarative fashion. Although configuration is easy, you must not forget that some Lifestyles involve long-lived objects that use resources as long as they’re around.

13.2.2 Releasing components

As discussed in section 8.2.2, it’s important to release objects when you’re done with them. Autofac has no explicit Release method but instead uses a concept called lifetime scopes. A lifetime scope can be regarded as a throw-away copy of the container. As figure 13.3 illustrates, it defines a boundary where components can be reused.

13-03.eps

Figure 13.3 Autofac’s lifetime scopes act as containers that can share components for a limited duration or purpose.

A lifetime scope defines a derived container that you can use for a particular duration or purpose; the most obvious example is a web request. You spawn a scope from a container so that the scope inherits all the Singletons tracked by the parent container, but the scope also acts as a container of local Singletons. When a lifetime-scoped component is requested from a lifetime scope, you always receive the same instance. The difference from true Singletons is that if you query a second scope, you’ll get another instance.

One of the important features of lifetime scopes is that they allow you to properly release components when the scope completes. You create a new scope with the BeginLifetimeScope method and release all appropriate components by invoking its Dispose method like so:

using (var scope = container.BeginLifetimeScope())    ①  
{
    IMeal meal = scope.Resolve<IMeal>();    ②  

    meal.Consume();    ③  

}    ④  

You create a new scope from the container by invoking the BeginLifetimeScope method. The return value implements IDisposable so you can wrap it in a using block. Because it also implements the same interface that the container itself implements, you can use the scope to resolve components in exactly the same way as with the container itself.

When you’re done with a lifetime scope, you can dispose of it. This happens automatically with a using block when you exit the block, but you can also choose to explicitly dispose of it by invoking the Dispose method. When you dispose of a scope, you also release all the components that were created by the lifetime scope. In the example, it means that you release the meal object graph.

Dependencies of a component are always resolved at or below the component’s lifetime scope. For example, if you need a Transient Dependency injected into a Singleton, that Transient Dependency comes from the root container even if you’re resolving the Singleton from a nested lifetime scope. This will track the Transient within the root container and prevent it from being disposed of when the lifetime scope gets disposed of. The Singleton consumer would otherwise break, because it’s kept alive in the root container while depending on a component that was disposed of.

Earlier in this section, you saw how to configure components as Singletons or Transients. Configuring a component to have its instance scope tied to a lifetime scope is done in a similar way:

builder.RegisterType<SauceBéarnaise>()
    .As<IIngredient>()
    .InstancePerLifetimeScope();    ①  

Due to their nature, Singletons are never released for the lifetime of the container itself. Still, you can release even those components if you don’t need the container any longer. This is done by disposing of the container itself:

container.Dispose();

In practice, this isn’t nearly as important as disposing of a scope because the lifetime of a container tends to correlate closely with the lifetime of the application it supports. You normally keep the container around as long as the application runs, so you’d only dispose of it when the application shuts down. In this case, memory would be reclaimed by the operating system.

This completes our tour of Lifetime Management with Autofac. Components can be configured with mixed instance scopes, and this is true even when you register multiple implementations of the same Abstraction. But until now, you’ve allowed the container to wire Dependencies by implicitly assuming that all components use Auto-Wiring. This isn’t always the case. In the next section, we’ll review how to deal with classes that must be instantiated in special ways.

13.3 Registering difficult APIs

Until now, we’ve considered how you can configure components that use Constructor Injection. One of the many benefits of Constructor Injection is that DI Containers like Autofac can easily understand how to compose and create all classes in a Dependency graph. This becomes less clear when APIs are less well behaved.

In this section, you’ll see how to deal with primitive constructor arguments and static factories. These all require special attention. Let’s start by looking at classes that take primitive types, such as strings or integers, as constructor arguments.

13.3.1 Configuring primitive Dependencies

As long as you inject Abstractions into consumers, all is well. But it becomes more difficult when a constructor depends on a primitive type, such as a string, a number, or an enum. This is particularly the case for data access implementations that take a connection string as constructor parameter, but it’s a more general issue that applies to all strings and numbers.

Conceptually, it doesn’t always make sense to register a string or number as a component in a container. But with Autofac, this is at least possible. Consider as an example this constructor:

public ChiliConCarne(Spiciness spiciness)

In this example, Spiciness is an enum:

public enum Spiciness { Mild, Medium, Hot }

If you want all consumers of Spiciness to use the same value, you can register Spiciness and ChiliConCarne independently of each other. This snippet shows how:

builder.Register<Spiciness>(c => Spiciness.Medium);
builder.RegisterType<ChiliConCarne>().As<ICourse>();

When you subsequently resolve ChiliConCarne, it’ll have a Spiciness value of Medium, as will all other components with a Dependency on Spiciness. If you’d rather control the relationship between ChiliConCarne and Spiciness on a finer level, you can use the WithParameter method. Because you want to supply a concrete value for the Spiciness parameter, you can use the WithParameter overload that takes a parameter name and a value:

builder.RegisterType<ChiliConCarne>().As<ICourse>()
.WithParameter(
        "spiciness",    ①  
        Spiciness.Hot);    ②  

Both options described here use Auto-Wiring to provide a concrete value to a component. As discussed in section 13.4, this has advantages and disadvantages. A more convenient solution, however, is to extract the primitive Dependencies into Parameter Objects.

In section 10.3.3, we discussed how the introduction of Parameter Objects allowed mitigating the Open/Closed Principle violation that IProductService caused. Parameter Objects, however, are also a great tool to mitigate ambiguity.

The Spiciness of a course, for instance, could be described in the more general term flavoring. Flavoring might include other properties, such as saltiness. In other words, you can wrap the Spiciness and ExtraSalty in a Flavoring class:4 

public class Flavoring
{
    public readonly Spiciness Spiciness;
    public readonly bool ExtraSalty;

    public Flavoring(Spiciness spiciness, bool extraSalty)
    {
        this.Spiciness = spiciness;
        this.ExtraSalty = extraSalty;
    }
}

With the introduction of the Flavoring Parameter Object, it now becomes easy to Auto-Wire any ICourse implementation that requires some flavoring:

var flavoring = new Flavoring(Spiciness.Medium, extraSalty: true);
builder.RegisterInstance<Flavoring>(flavoring);

builder.RegisterType<ChiliConCarne>().As<ICourse>();

Now you have a single instance of the Flavoring class. Flavoring becomes a configuration object for ICourses. Because there’ll only be one Flavoring instance, you can register it in Autofac using RegisterInstance.

Extracting primitive Dependencies into Parameter Objects should be your preference over the previously discussed options because Parameter Objects remove ambiguity, both at the functional and the technical levels. It does, however, require a change to a component’s constructor, which might not always be feasible. In this case, the use of WithParameter is your second-best pick.

13.3.2 Registering objects with code blocks

Another option for creating a component with a primitive value is to use the Register method. It lets you supply a delegate that creates the component:

builder.Register<ICourse>(c => new ChiliConCarne(Spiciness.Hot));

You already saw the Register method when we discussed the registration of Spiciness in section 13.3.1. Here, the ChiliConCarne constructor is invoked with a Spiciness value of Hot every time the ICourse service is resolved.

When it comes to the ChiliConCarne class, you have a choice between Auto-Wiring or using a code block. Other classes can be more restrictive: they can’t be instantiated through a public constructor. Instead, you must use some sort of factory to create instances of the type. This is always troublesome for DI Containers, because, by default, they look after public constructors. Consider this example constructor for the public JunkFood class:

internal JunkFood(string name)

Even though the JunkFood class might be public, the constructor is internal. In this example, instances of JunkFood should instead be created through the static JunkFoodFactory class:

public static class JunkFoodFactory
{
    public static JunkFood Create(string name)
    {
        return new JunkFood(name);
    }
}

From Autofac’s perspective, this is a problematic API, because there are no unambiguous and well-established conventions around static factories. It needs help — and you can give that help by providing a code block it can execute to create the instance:

builder.Register<IMeal>(c => JunkFoodFactory.Create("chicken meal"));

This time, you use the Register method to create the component by invoking a static factory within the code block. With that in place, JunkFoodFactory.Create is invoked every time IMeal is resolved and the result returned.

When you end up writing the code to create the instance, how is this better than invoking the code directly? By using a code block inside a Register method call, you still gain something:

  • You map from IMeal to JunkFood. This allows consuming classes to stay loosely coupled.
  • Instance scope can still be configured. Although the code block will be invoked to create the instance, it may not be invoked every time the instance is requested. It is by default, but if you change it to a Singleton, the code block will only be invoked once, with the result cached and reused thereafter.

In this section, you’ve seen how you can use Autofac to deal with more-difficult creational APIs. You can use the WithParameter method to wire constructors with services to maintain a semblance of Auto-Wiring, or you can use the Register method with a code block for a more type-safe approach. We have yet to look at how to work with multiple components, so let’s now turn our attention in that direction.

13.4 Working with multiple components

As alluded to in section 12.1.2, DI Containers thrive on distinctness but have a hard time with ambiguity. When using Constructor Injection, a single constructor is preferred over overloaded constructors, because it’s evident which constructor to use when there’s no choice. This is also the case when mapping from Abstractions to concrete types. If you attempt to map multiple concrete types to the same Abstraction, you introduce ambiguity.

Despite the undesirable qualities of ambiguity, you often need to work with multiple implementations of a single Abstraction.5  This can be the case in situations like these:

  • Different concrete types are used for different consumers.
  • Dependencies are sequences.
  • Decorators or Composites are in use.

In this section, we’ll look at each of these cases and see how Autofac addresses each in turn. When we’re done, you should be able to register and resolve components even when multiple implementations of the same Abstraction are in play. Let’s first see how you can provide more fine-grained control than Auto-Wiring provides.

13.4.1 Selecting among multiple candidates

Auto-Wiring is convenient and powerful but provides little control. As long as all Abstractions are distinctly mapped to concrete types, you have no problems. But as soon as you introduce more implementations of the same interface, ambiguity rears its ugly head. Let’s first recap how Autofac deals with multiple registrations of the same Abstraction.

Configuring multiple implementations of the same service

As you saw in section 13.1.2, you can register multiple implementations of the same interface like this:

builder.RegisterType<Steak>().As<IIngredient>();
builder.RegisterType<SauceBéarnaise>().As<IIngredient>();

This example registers both the Steak and SauceBéarnaise classes as the IIngredient service. The last registration wins, so if you resolve IIngredient with scope.Resolve<IIngredient>(), you’ll get a SauceBéarnaise instance.

You can also ask the container to resolve all IIngredient components. Autofac has no dedicated method to do that, but instead relies on relationship types (https://mng.bz/P429). A relationship type is a type that indicates a relationship that the container can interpret. As an example, you can use IEnumerable<T> to indicate that you want all services of a given type:

IEnumerable<IIngredient> ingredients =
    scope.Resolve<IEnumerable<IIngredient>>();

Notice that we use the normal Resolve method, but that we request IEnumerable<IIngredient>. Autofac interprets this as a convention and gives us all the IIngredient components it has.

When you register components, you can give each registration a name that you can later use to select among the different components. This code snippet shows that process:

builder.RegisterType<Steak>().Named<IIngredient>("meat");
builder.RegisterType<SauceBéarnaise>().Named<IIngredient>("sauce");

As always, you start with the RegisterType method, but instead of following up with the As method, you use the Named method to specify a service type as well as a name. This enables you to resolve named services by supplying the same name to the ResolveNamed method:

IIngredient meat = scope.ResolveNamed<IIngredient>("meat");
IIngredient sauce = scope.ResolveNamed<IIngredient>("sauce");

Naming components with strings is a fairly common feature of DI Containers. But Autofac also lets you identify components with arbitrary keys:

object meatKey = new object();
builder.RegisterType<Steak>().Keyed<IIngredient>(meatKey);

The key can be any object, and you can subsequently use it to resolve the component:

IIngredient meat = scope.ResolveKeyed<IIngredient>(meatKey);

Given that you should always resolve services in a single Composition Root, you should normally not expect to deal with such ambiguity on this level. If you do find yourself invoking the Resolve method with a specific name or key, consider if you can change your approach to be less ambiguous. You can also use named or keyed instances to select among multiple alternatives when configuring Dependencies for a given service.

Registering named Dependencies

As useful as Auto-Wiring is, sometimes you need to override the normal behavior to provide fine-grained control over which Dependencies go where; it can also be that you need to address an ambiguous API. As an example, consider this constructor:

public ThreeCourseMeal(ICourse entrée, ICourse mainCourse, ICourse dessert)

In this case, you have three identically typed Dependencies, each of which represents a different concept. In most cases, you want to map each of the Dependencies to a separate type. The following listing shows how you could choose to register the ICourse mappings.

Listing 13.5 Registering named courses

builder.RegisterType<Rillettes>().Named<ICourse>("entrée");
builder.RegisterType<CordonBleu>().Named<ICourse>("mainCourse");
builder.RegisterType<MousseAuChocolat>().Named<ICourse>("dessert");

Here, you register three named components, mapping the Rilettes to an instance named entrée, CordonBleu to an instance named mainCourse, and the MousseAuChocolat to an instance named dessert. Given this configuration, you can now register the ThreeCourseMeal class with the named registrations.

This turns out to be surprisingly complex. In the following listing, we’ll first show you what it looks like, and then we’ll subsequently pick apart the example to understand what’s going on.

Listing 13.6 Overriding Auto-Wiring

builder.RegisterType<ThreeCourseMeal>().As<IMeal>()
    .WithParameter(    ①  
        (p, c) => p.Name == "entrée",
        (p, c) => c.ResolveNamed<ICourse>("entrée"))
    .WithParameter(
        (p, c) => p.Name == "mainCourse",    ②  
        (p, c) => c.ResolveNamed<ICourse>("mainCourse"))
    .WithParameter(
        (p, c) => p.Name == "dessert",
        (p, c) => c.ResolveNamed<ICourse>("dessert"));    ③  

Let’s take a closer look at what’s going on here. The WithParameter method overload wraps around the ResolvedParameter class, which has this constructor:

public ResolvedParameter(
    Func<ParameterInfo, IComponentContext, bool> predicate,
    Func<ParameterInfo, IComponentContext, object> valueAccessor);

The predicate parameter is a test that determines whether the valueAccessor delegate will be invoked. When predicate returns true, valueAccessor is invoked to provide the value for the parameter. Both delegates take the same input: information about the parameter in the form of a ParameterInfo object and an IComponentContext that can be used to resolve other components. When Autofac uses the ResolvedParameter instances, it provides both of these values when it invokes the delegates.

As listing 13.6 shows, the resulting registration is rather verbose. With the aid of two self-written helper methods, however, you can simplify the registration considerably:

builder.RegisterType<ThreeCourseMeal>().As<IMeal>()
    .WithParameter(Named("entrée"), InjectWith<ICourse>("entrée"))
    .WithParameter(Named("mainCourse"), InjectWith<ICourse>("mainCourse"))
    .WithParameter(Named("dessert"), InjectWith<ICourse>("dessert"));

By introducing the Named and InjectWith<T> helper methods, you simplified the registration, reduced its verbosity, and at the same time, made it easier to read what’s going on. It almost starts to read like poetry (or a well-aged bottle of wine):

create thy ThreeCourseMeal, with a parameter Namedentrée, InjectedWith an ICourse named entrée.

The following code shows the two new methods:

Func<ParameterInfo, IComponentContext, bool> Named(string name)
{
    return (p, c) => p.Name == name;
}

Func<ParameterInfo, IComponentContext, object> InjectWith<T>(string name)
{
    return (p, c) => c.ResolveNamed<T>(name);
}

When called, both methods create a new delegate that wraps the supplied name argument. Sometimes there’s no other way than to use the WithParameter method for each and every constructor parameter, but in other cases, you can take advantage of conventions.

Resolving named components by convention

If you examine listing 13.6 closely, you’ll notice a repetitive pattern. Each call to WithParameter addresses only a single constructor parameter, but each valueAccessor does the same thing: it uses the IComponentContext to resolve an ICourse component with the same name as the parameter.

There’s no requirement that says you must name the component after the constructor parameter, but when this is the case, you can take advantage of this convention and rewrite listing 13.6 in a simpler way. The following listing demonstrates how.

Listing 13.7 Overriding Auto-Wiring with a convention

builder.RegisterType<ThreeCourseMeal>().As<IMeal>()
    .WithParameter(
        (p, c) => true,
        (p, c) => c.ResolveNamed(p.Name, p.ParameterType));

It might be a little surprising, but you can address all three constructor parameters of the ThreeCourseMeal class with the same WithParameter call. You do that by stating that this instance will handle any parameter Autofac might throw at it. Because you only use this method to configure the ThreeCourseMeal class, the convention only applies within this limited scope.

As the predicate always returns true, the second code block will be invoked for all three constructor parameters. In all three cases, it’ll ask IComponentContext to resolve a component that has the same name and type as the parameter. This is functionally the same as what you did in listing 13.6.

As in listing 13.6, you can create a simplified version of listing 13.7. But we’ll leave this as an exercise for the reader.

Overriding Auto-Wiring by explicitly mapping parameters to named components is a universally applicable solution. You can do this even if you configure the named components in one part of the Composition Root and the consumer in a completely different part, because the only identification that ties a named component together with a parameter is the name. This is always possible but can be brittle if you have many names to manage. When the original reason prompting you to use named components is to deal with ambiguity, a better solution is to design your own API to get rid of that ambiguity. It often leads to a better overall design.

In the next section, you’ll see how to use the less ambiguous and more flexible approach, where you allow any number of courses in a meal. To this end, you must learn how Autofac deals with lists and sequences.

13.4.2 Wiring sequences

In section 6.1.1, we discussed how Constructor Injection acts as a warning system for Single Responsibility Principle violations. The lesson then was that instead of viewing Constructor Over-Injection as a weakness of the Constructor Injection pattern, you should rather rejoice that it makes problematic design so obvious.

When it comes to DI Containers and ambiguity, we see a similar relationship. DI Containers generally don’t deal with ambiguity in a graceful manner. Although you can make a good DI Container like Autofac deal with it, it can seem awkward. This is often an indication that you could improve on the design of your code.

Instead of feeling constrained by Autofac, you should embrace its conventions and let it guide you toward a better and more consistent design. In this section, we’ll look at an example that demonstrates how you can refactor away from ambiguity, as well as show how Autofac deals with sequences, arrays, and lists.

Refactoring to a better course by removing ambiguity

In section 13.4.1, you saw how the ThreeCourseMeal and its inherent ambiguity forced you to abandon Auto-Wiring and instead use WithParameter. This should prompt you to reconsider the API design. For example, a simple generalization moves toward an implementation of IMeal that takes an arbitrary number of ICourse instances, instead of exactly three, as was the case with the ThreeCourseMeal class:

public Meal(IEnumerable<ICourse> courses)

Notice that instead of requiring three distinct ICourse instances in the constructor, the single dependency on an IEnumerable<ICourse> instance lets you provide any number of courses to the Meal class — from zero to ... a lot! This solves the issue with ambiguity, because there’s now only a single Dependency. In addition, it also improves the API and implementation by providing a single, general-purpose class that can model different types of meals, from a simple meal with a single course to an elaborate 12-course dinner.

In this section, we’ll look at how you can configure Autofac to wire up Meal instances with appropriate ICourse Dependencies. When you’re done, you should have a good idea of the options available when you need to configure instances with sequences of Dependencies.

Auto-Wiring sequences

Autofac has a good understanding of sequences, so if you want to use all registered components of a given service, Auto-Wiring just works. As an example, you can configure the IMeal service like this:

builder.RegisterType<Rillettes>().As<IIngredient>();
builder.RegisterType<CordonBlue>().As<IIngredient>();
builder.RegisterType<MousseAuChocolat>().As<IIngredient>();

builder.RegisterType<Meal>().As<IMeal>();

Notice that this is a completely standard mapping from a concrete type to an Abstraction. Autofac automatically understands the Meal constructor and determines that the correct course of action is to resolve all ICourse components. When you resolve IMeal, you get a Meal instance with the ICourse components: Rillettes, CordonBleu, and MousseAuChocolat.

Autofac automatically handles sequences, and, unless you specify otherwise, it does what you’d expect it to do: it resolves a sequence of Dependencies to all registered components of that type. Only when you need to explicitly pick some components from a larger set do you need to do more. Let’s see how you can do that.

Picking only some components from a larger set

Autofac’s default strategy of injecting all components is often the correct policy, but as figure 13.4 shows, there may be cases where you want to pick only some registered components from the larger set of all registered components.

13-04.eps

Figure 13.4 Picking components from a larger set of all registered components

When you previously let Autofac Auto-Wire all configured instances, it corresponded to the situation depicted on the right side of the figure. If you want to register a component as shown on the left side, you must explicitly define which components should be used. In order to achieve this, you can use the WithParameter method the way you did in listings 13.6 and 13.7. This time, you’re dealing with the Meal constructor that only takes a single parameter. The following listing demonstrates how you can implement the value-providing part of WithParameter to explicitly pick named components from the IComponentContext.

Listing 13.8 Injecting named components into a sequence

builder.RegisterType<Meal>().As<IMeal>()
    .WithParameter(
        (p, c) => true,
        (p, c) => new[]
        {
            c.ResolveNamed<ICourse>("entrée"),
            c.ResolveNamed<ICourse>("mainCourse"),
            c.ResolveNamed<ICourse>("dessert")
        });

As you saw in section 13.4.1, the WithParameter method takes two delegates as input parameters. The first is a predicate that’s used to determine if the second delegate should be invoked. In this case, you decide to be a bit lazy and return true. You know that the Meal class has only a single constructor parameter, so this’ll work. But if you later refactor the Meal class to take a second constructor parameter, this may not work correctly anymore. It might be safer to define an explicit check for the parameter type.

The second delegate provides the value for the parameter. You use IComponentContext to resolve three named components into an array. The result is an array of ICourse instances, which is compatible with IEnumerable<ICourse>.

Autofac natively understands sequences; unless you need to explicitly pick only some components from all services of a given type, Autofac automatically does the right thing. Auto-Wiring works not only with single instances, but also for sequences, and the container maps a sequence to all configured instances of the corresponding type. A perhaps less intuitive use of having multiple instances of the same Abstraction is the Decorators design pattern, which we’ll discuss next.

13.4.3 Wiring Decorators

In section 9.1.1, we discussed how the Decorator design pattern is useful when implementing Cross-Cutting Concerns. By definition, Decorators introduce multiple types of the same Abstraction. At the very least, you have two implementations of an Abstraction: the Decorator itself and the decorated type. If you stack the Decorators, you can have even more. This is another example of having multiple registrations of the same service. Unlike the previous sections, these registrations aren’t conceptually equal but rather Dependencies of each other.

There are multiple strategies for applying Decorators in Autofac, such as using the previously discussed WithParameter or using code blocks, as we discussed in section 13.3.2. In this section, however, we’ll focus on the use of the RegisterDecorator and RegisterGenericDecorator methods because they make configuring Decorators a no-brainer.

Decorating non-generic Abstractions with RegisterDecorator

Autofac has built-in support for Decorators via the RegisterDecorator method. The following example shows how to use this method to apply Breading to a VealCutlet:

builder.RegisterType<VealCutlet>()    ①  
    .As<IIngredient>();    ①  

builder.RegisterDecorator<Breading, IIngredient>();  ②  

As you learned in chapter 9, you get Cordon Bleu when you slit open a pocket in the veal cutlet and add ham, cheese, and garlic into the pocket before breading the cutlet. The following example shows how to add a HamCheeseGarlic Decorator in between VealCutlet and the Breading Decorator:

builder.RegisterType<VealCutlet>()
    .As<IIngredient>();

builder.RegisterDecorator<HamCheeseGarlic,    ①  
    IIngredient>();    ①  

builder.RegisterDecorator<Breading, IIngredient>();

By placing this new registration before the Breading registration, the HamCheeseGarlic Decorator is wrapped first. This results in an object graph equal to the following Pure DI version:

new Breading(    ①  
    new HamCheeseGarlic(    ①  
        new VealCutlet()));    ①  

Chaining Decorators using the RegisterDecorator method is easy in Autofac. Likewise, you can apply generic Decorators, as you’ll see next.

Decorating generic Abstractions with RegisterGenericDecorator

During the course of chapter 10, we defined multiple generic Decorators that could be applied to any ICommandService<TCommand> implementation. In the remainder of this chapter, we’ll set our ingredients and courses aside, and take a look at how to register these generic Decorators using Autofac. The following listing demonstrates how to register all ICommandService<TCommand> implementations with the three Decorators presented in section 10.3.

Listing 13.9 Decorating generic Auto-Registered Abstractions

builder.RegisterAssemblyTypes(assembly)
    .AsClosedTypesOf(typeof(ICommandService<>));

builder.RegisterGenericDecorator(
    typeof(AuditingCommandServiceDecorator<>),
    typeof(ICommandService<>));

builder.RegisterGenericDecorator(
    typeof(TransactionCommandServiceDecorator<>),
    typeof(ICommandService<>));

builder.RegisterGenericDecorator(
    typeof(SecureCommandServiceDecorator<>),
    typeof(ICommandService<>));
13-05.eps

Figure 13.5 Enriching a real command service with transaction, auditing, and security aspects

As you saw in listing 13.3, listing 13.9 uses RegisterAssemblyTypes to register arbitrary ICommandService<TCommand> implementations. To register generic Decorators, however, Autofac provides a different method — RegisterGenericDecorator. The result of the configuration of listing 13.9 is figure 13.5, which we discussed previously in section 10.3.4.

You can configure Decorators in different ways, but in this section, we focused on Autofac’s methods that were explicitly designed for this task. Autofac lets you work with multiple instances in several different ways: you can register components as alternatives to each other, as peers resolved as sequences, or as hierarchical Decorators. In many cases, Autofac figures out what to do, but you can always explicitly define how services are composed if you need more-explicit control.

Although consumers that rely on sequences of Dependencies can be the most intuitive use of multiple instances of the same Abstraction, Decorators are another good example. But there’s a third and perhaps a bit surprising case where multiple instances come into play, which is the Composite design pattern.

13.4.4 Wiring Composites

During the course of this book, we discussed the Composite design pattern on several occasions. In section 6.1.2, for instance, you created a CompositeNotificationService (listing 6.4) that both implemented INotificationService and wrapped a sequence of INotificationService implementations.

Wiring non-generic Composites

Let’s take a look at how you can register Composites like the CompositeNotificationService from chapter 6 in Autofac. The following listing shows this class again.

Listing 13.10 The CompositeNotificationService Composite from chapter 6

public class CompositeNotificationService : INotificationService
{
    private readonly IEnumerable<INotificationService> services;

    public CompositeNotificationService(
        IEnumerable<INotificationService> services)
    {
        this.services = services;
    }

    public void OrderApproved(Order order)
    {
        foreach (INotificationService service in this.services)
        {
            service.OrderApproved(order);
        }
    }
}

Registering a Composite requires that it be added as a default registration while injecting it with a sequence of named instances:

builder.RegisterType<OrderApprovedReceiptSender>()
    .Named<INotificationService>("service");
builder.RegisterType<AccountingNotifier>()
    .Named<INotificationService>("service");
builder.RegisterType<OrderFulfillment>()
    .Named<INotificationService>("service");

builder.Register(c =>
    new CompositeNotificationService(
        c.ResolveNamed<IEnumerable<INotificationService>>("service")))
    .As<INotificationService>();

Here, three INotificationService implementations are registered by the same name, service, using the Auto-Wiring API of Autofac. The CompositeNotificationService, on the other hand, is registered using a delegate. Inside the delegate, the Composite is newed up manually and injected with an IEnumerable<INotificationService>. By specifying the service name, the previous named registrations are resolved.

Because the number of notification services will likely grow over time, you can reduce the burden on your Composition Root by applying Auto-Registration. Using the RegisterAssemblyTypes method, you can turn the previous list of registrations in a simple one-liner.

Listing 13.11 Registering CompositeNotificationService

builder.RegisterAssemblyTypes(assembly)
    .Named<INotificationService>("service");

builder.Register(c =>
    new CompositeNotificationService(
        c.ResolveNamed<IEnumerable<INotificationService>>("service")))
    .As<INotificationService>();

This looks reasonably simple, but looks are deceiving. RegisterAssemblyTypes will register any non-generic implementation that implements INotificationService. When you try to run the previous code, depending on which assembly your Composite is located in, Autofac might throw the following exception:

Circular component dependency detected: CompositeNotificationService -> INotificationService[] -> CompositeNotificationService -> INotificationService[] -> CompositeNotificationService.

Autofac detected a cyclic Dependency. (We discussed Dependency cycles in detail in section 6.3.) Fortunately, its exception message is pretty clear. It describes that CompositeNotificationService depends on INotificationService[]. The CompositeNotificationService wraps a sequence of INotificationService, but that sequence itself again contains CompositeNotificationService. What this means is that CompositeNotificationService is an element of the sequence that’s injected into CompositeNotificationService. This is an object graph that’s impossible to construct.

CompositeNotificationService became a part of the sequence because Autofac’s RegisterAssemblyTypes registers all non-generic INotificationService implementations it finds. In this case, CompositeNotificationService was placed in the same assembly as all other implementations.

There are multiple ways around this. The simplest solution is to move the Composite to a different assembly; for instance, the assembly containing the Composition Root. This prevents RegisterAssemblyTypes from selecting the type, because it’s provided with a particular Assembly instance. Another option is to filter the CompositeNotificationService out of the list. An elegant way of doing this is using the Except method:

builder.RegisterAssemblyTypes(assembly)
    .Except<CompositeNotificationService>()
    .Named<INotificationService>("service");

Composite classes, however, aren’t the only classes that might require removal. You’ll have to do the same for any Decorator. This isn’t particularly difficult, but because there’ll typically be more Decorator implementations, you might be better off querying the type information to find out whether the type represents a Decorator or not. The following example shows how you can filter out Decorators as well, using a custom IsDecoratorFor helper method:

builder.RegisterAssemblyTypes(assembly)
    .Except<CompositeNotificationService>()
    .Where(type => !IsDecoratorFor<INotificationService>(type))
    .Named<INotificationService>("service");

And the following example shows the IsDecoratorFor method:

private static bool IsDecoratorFor<T>(Type type)
{
    return typeof(T).IsAssignableFrom(type) &&
        type.GetConstructors()[0].GetParameters()
            .Any(p => p.ParameterType == typeof(T));
}

The IsDecoratorFor method expects a type to have a single constructor. A type is considered to be a Decorator when it both implements the given T Abstraction and its constructor also requires a T.

Wiring generic Composites

In section 13.4.3, you saw how using Autofac’s RegisterGenericDecorator method made registering generic Decorators child’s play. In this section, we’ll take a look at how you can register Composites for generic Abstractions.

In section 6.1.3, you specified the CompositeEventHandler<TEvent> class (listing 6.12) as a Composite implementation over a sequence of IEventHandler<TEvent> implementations. Let’s see if you can register the Composite with its wrapped event handler implementations.

Let’s start with Auto-Registration of the event handlers. As you’ve seen previously, this is done using the RegisterAssemblyTypes method:

builder.RegisterAssemblyTypes(assembly)
    .As(type =>
        from interfaceType in type.GetInterfaces()
        where interfaceType.IsClosedTypeOf(typeof(IEventHandler<>))
        select new KeyedService("handler", interfaceType));

This example makes use of the As overload that allows supplying a sequence of Autofac.Core.KeyedService instances. A KeyedService class is a small data object that combines both a key and a service type.

Autofac runs any type it finds in the assembly through the As method. You can use a LINQ query to find the type’s implemented interface that’s a closed-generic version of IEventHandler<TEvent>. For most types in the assembly, this query won’t yield any results, because most types don’t implement IEventHandler<TEvent>. For those types, no registration is added to ContainerBuilder.

Even though this is quite complex, generic Composites and Decorators don’t have to be filtered out. RegisterAssemblyTypes only selects non-generic implementations. Generic types, such as CompositeEventHandler<TEvent>, won’t cause any problem, and don’t have to be filtered out or moved to a different assembly. This is fortunate, because it wouldn’t be fun at all to have to write a version of IsDecoratorFor that could handle generic Abstractions.

What remains is the registration for CompositeEventHandler<TEvent>. Because this type is generic, you can’t use the Register overload that takes in a predicate. Instead, you use RegisterGeneric. This method allows making a mapping between a generic implementation and its Abstraction, similar to what you saw with RegisterGenericDecorator. To get the sequence of named registrations to be injected into the Composite’s constructor argument, you can once more use the versatile WithParameter method:

builder.RegisterGeneric(typeof(CompositeEventHandler<>))
    .As(typeof(IEventHandler<>))
    .WithParameter(
        (p, c) => true,
        (p, c) => c.ResolveNamed("handler", p.ParameterType));

Because CompositeEventHandler<TEvent> contains a single constructor parameter, you simplify the registration to apply to all parameters by letting the predicate return true.

The WithParameter delegates are called when a closed IEventHandler<TEvent> is requested. Therefore, at the time of invocation, you can get the type of the constructor parameter by calling p.ParameterType. For example, if an IEventHandler<OrderApproved> is requested, the parameter type will be IEnumerable<IEventHandler<OrderApproved>>. By passing this type on to the ResolveNamed method with the sequence name handler, Autofac resolves the previously registered sequence of named instances that implement IEventHandler<OrderApproved>.

Although the registration of Decorators is simple, this unfortunately doesn’t hold for Composites. Autofac hasn’t been designed — yet — with the Composite design pattern in mind. It’s likely this will change in a future version.

This completes our discussion of the Autofac DI Container. In the next chapter, we’ll turn our attention to Simple Injector.

Summary

  • The Autofac DI Container offers a fairly comprehensive API and addresses many of the trickier situations you typically encounter when you use DI Containers.
  • An important overall theme for Autofac seems to be one of explicitness. It doesn’t attempt to guess what you mean, but rather offers an easy-to-use API that provides you with options to explicitly enable features.
  • Autofac enforces stricter separation of concerns between configuring and consuming a container. You configure components using a ContainerBuilder instance, but a ContainerBuilder can’t resolve components. When you’re done configuring a ContainerBuilder, you use it to build an IContainer that you can use to resolve components.
  • With Autofac, resolving from the root container directly is a bad practice. This can easily lead to memory leaks or concurrency bugs. Instead, you should always resolve from a lifetime scope.
  • Autofac supports the standard Lifestyles: Transient, Singleton, and Scoped.
  • Autofac allows working with ambiguous constructors and types by providing an API that allows supplying code blocks. This allows any code that creates a service to be executed.
..................Content has been hidden....................

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