In this chapter
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.
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.
Question | Answer |
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.
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.
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.
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.
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.
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.
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.
ContainerBuilder
using Configuration as CodeIn 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.
ContainerBuilder
using Auto-RegistrationIn 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.
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.
ContainerBuilder
using configuration filesWhen 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.
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.
Autofac name | Pattern name | Comments |
Per-dependency | Transient | This is the default instance scope. Instances are tracked by the container. |
Single instance | Singleton | Instances are disposed of when the container is disposed of. |
Per-lifetime scope | Scoped | Ties 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.
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.
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.
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.
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.
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.
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.
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 ICourse
s. 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.
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:
IMeal
to JunkFood
. This allows consuming classes to stay loosely coupled.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.
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:
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.
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.
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.
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 parameterNamed
entrée,InjectedWith
anICourse
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.
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.
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.
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.
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.
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.
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.
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.
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.
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<>));
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.
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.
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
.
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.
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.18.116.15.161