14
The Simple Injector DI Container

In this chapter

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

In the previous chapter, we looked at the Autofac DI Container, created by Nicholas Blumhardt in 2007. Three years later, Steven created Simple Injector, which we’ll examine in this chapter. We’ll give Simple Injector the same treatment that we gave Autofac in the last chapter. You’ll see how you can use Simple Injector 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. You can read this chapter apart from the rest of the chapters in part 4, specifically to learn about Simple Injector, or you can read it together with the other chapters to compare DI Containers.

Although this chapter isn’t a complete treatment of the Simple Injector container, it gives enough information that you can start using it. This chapter includes information on how to deal with the most common questions that may come up as you use Simple Injector. For more information about this container, see the Simple Injector home page at https://simpleinjector.org.

14.1 Introducing Simple Injector

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

Table 14.1 Simple Injector at a glance
QuestionAnswer
Where do I get it?From Visual Studio, you can get it via NuGet. The package name is SimpleInjector.
Which platforms are supported?.NET 4.0 and .NET Standard 1.0 (.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).
How much does it cost?Nothing. It’s open source.
How is it licensed?MIT License
Where can I get help?There’s no guaranteed support, but you’re likely to get help in the official forum at https://simpleinjector.org/forum or by asking your question on Stack Overflow at https://stackoverflow.com/.
On which version is this chapter based?4.4.3

At a high level, using Simple Injector isn’t that different from using the other DI Containers. As with the Autofac DI Container (covered in chapter 13) and the Microsoft.Extensions.DependencyInjection DI Container (covered in chapter 15), usage is a two-step process, as figure 14.1 illustrates.

14-01.eps

Figure 14.1 The pattern for using Simple Injector. First, you configure a Container, and then, using the same container instance, you resolve components from it.

As you might remember from chapter 13, to facilitate the two-step process, Autofac uses a ContainerBuilder class that produces an IContainer. Simple Injector, on the other hand, integrates both registration and resolution in the same Container instance. Still, it forces the registration to be a two-step process by disallowing any explicit registrations to be made after the first service is resolved.

Although resolution isn’t that different, Simple Injector’s registration API does differ quite a lot from how most DI Containers work. In its design and implementation, it eliminates many pitfalls that are a common cause of bugs. We’ve discussed most of these pitfalls throughout the book, so in this chapter, we’ll discuss the following differences between Simple Injector and other DI Containers:

  • Scopes are ambient, allowing object graphs to always be resolved from the container itself to prevent memory and concurrency bugs.
  • Sequences are registered through a different API to prevent accidental duplicate registrations from overriding each other.
  • Primitive types can’t be registered directly to prevent registrations from becoming ambiguous.
  • Object graphs can be verified to spot common configuration errors, such as Captive Dependencies.

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

14.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 Simple Injector.

If you remember the discussion about resolving components with Autofac, you may recall that Autofac requires you to register all relevant components before you can resolve them. This isn’t the case with Simple Injector; if you request a concrete type with a parameterless constructor, no configuration is necessary. The following listing shows one of the simplest possible uses of Simple Injector.

Listing 14.1 Simplest possible use of Simple Injector

var container = new Container();    ①  

SauceBéarnaise sauce =
    container.GetInstance<SauceBéarnaise>();  ②  

Given an instance of SimpleInjector.Container, you can use the generic GetInstance method to get an instance of the concrete SauceBéarnaise class. Because this class has a parameterless constructor, Simple Injector automatically creates an instance of it. No explicit configuration of the container is necessary.

As you learned in section 12.1.2, Auto-Wiring is the ability to automatically compose an object graph by making use of the type information. Because Simple Injector supports Auto-Wiring, even in the absence of a parameterless constructor, it can create instances without configurations as long as the involved constructor parameters are all concrete types, and all parameters in the entire tree have leaf types with parameterless constructors. As an example, consider this Mayonnaise constructor:

public Mayonnaise(EggYolk eggYolk, SunflowerOil oil)

Although the mayonnaise recipe is a bit simplified, suppose both EggYolk and SunflowerOil are concrete classes with parameterless constructors. Although Mayonnaise itself has no parameterless constructor, Simple Injector creates it without any configuration:

var container = new Container();
Mayonnaise mayo = container.GetInstance<Mayonnaise>();

This works because Simple Injector is able to figure out how to create all required constructor parameters. But as soon as you introduce loose coupling, you must configure Simple Injector by mapping Abstractions to concrete types.

Mapping abstractions to concrete types

Although Simple Injector’s ability to Auto-Wire concrete types certainly can come in handy from time to time, 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 container = new Container();

container.Register<IIngredient, SauceBéarnaise>();  ①  

IIngredient sauce =
    container.GetInstance<IIngredient>();    ②  

You use the Container instance to register types and define maps. Here, the generic Register method allows an Abstraction to be mapped to a particular implementation. This lets you register a concrete type. Because of the previous Register call, SauceBéarnaise can now be resolved as IIngredient.

In many cases, the generic API is all you need. Still, there are situations where you need a more weakly typed way to resolve services. 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 one:

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 GetInstance<T> method, but must resort to a weakly typed API. Simple Injector offers a weakly typed overload of the GetInstance method that lets you implement the Create method like this:

Type controllerType = context.ActionDescriptor.ControllerTypeInfo.AsType();
return container.GetInstance(controllerType);

The weakly typed overload of GetInstance lets you pass the controllerType variable directly to Simple Injector. Typically, this means you have to cast the returned value to some Abstraction because the weakly typed GetInstance 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 GetInstance you use, Simple Injector 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, Simple Injector can Auto-Wire the requested type.

To be able to resolve the requested type, all loosely coupled Dependencies must have been previously configured. You can configure Simple Injector in many ways; the next section reviews the most common ones.

14.1.2 Configuring the container

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 14.2 shows these options again.

14-02.eps

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

Simple Injector’s core configuration API is centered on code and supports both Configuration as Code and convention-based Auto-Registration. File-based configuration is left out completely. This shouldn’t be a obstacle to using Simple Injector because, as we discussed in chapter 12, this configuration method should generally be avoided. Still, if your application requires late binding, it’s quite easy to add a file-based configuration yourself, as we’ll discuss later in this section.

Simple Injector lets you mix all three approaches. In this section, you’ll see how to use each of these three types of configuration sources.

Configuring the container using Configuration as Code

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

All configuration in Simple Injector uses the API exposed by the Container class. One of the most commonly used methods is the Register method that you’ve already seen:

container.Register<IIngredient, SauceBéarnaise>();

Because you want to program to interfaces, most of your components will depend on Abstractions. This means that most components will be registered by their corresponding Abstraction. When a component is the topmost type in the object graph, its not uncommon to resolve it by its concrete type instead of its Abstraction. MVC controllers, for instance, are resolved by their concrete type.

In general, you would register a type either by its Abstraction or by its concrete type, but not both. There are exceptions to this rule, however. In Simple Injector, registering a component both by its concrete type and its Abstraction is simply a matter of adding an extra registration:

container.Register<IIngredient, SauceBéarnaise>();
container.Register<SauceBéarnaise>();

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.

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 Register:

container.Register<IIngredient, SauceBéarnaise>();
container.Register<ICourse, Course>();

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 what would happen if you register the same Abstraction several times?

container.Register<IIngredient, SauceBéarnaise>();
container.Register<IIngredient, Steak>();    ①  

Here, you register IIngredient twice, which results in an exception being thrown on the second line with the following message:

Type IIngredient has already been registered. If your intention is to resolve a collection of IIngredient implementations, use the Collection.Register overloads. For more information, see https://simpleinjector.org/coll1.

In contrast to most other DI Containers, Simple Injector doesn’t allow stacking up registrations to build up a sequence of types, as the previous code snippet shows. Its API explicitly separates the registration of sequences from single Abstraction mappings.1  Instead of making multiple calls to Register, Simple Injector forces you to use the registration methods of the Collection property, such as Collection.Register:

container.Collection.Register<IIngredient>(
    typeof(SauceBéarnaise),
    typeof(Steak));

This example registers all ingredients in one single call. Alternatively, you can use Collection.Append to add implementations to a sequence of ingredients:

container.Collection.Append<IIngredient, SauceBéarnaise>();
container.Collection.Append<IIngredient, Steak>();

With the previous registrations, any component that depends on IEnumerable<IIngredient> gets a sequence of ingredients injected. Simple Injector handles multiple configurations for the same Abstraction well, but we’ll get back to this topic in section 14.4.

Although there are more-advanced options available for configuring Simple Injector, 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 container 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 an ever-changing list of Type instances supplied to the Collection.Register method. What’s worse is that, every time you add a new IIngredient implementation, you must also explicitly register it with the Container 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 some of the Register and Collection.Register method overloads. These particular overloads let you specify an assembly and configure all selected classes from this assembly in a single statement. To get the Assembly instance, you can use a representative class; in this case, Steak:

Assembly ingredientsAssembly = typeof(Steak).Assembly;

container.Collection.Register<IIngredient>(ingredientsAssembly);

The previous example unconditionally configures all implementations of the IIngredient interface, but you can provide filters that enable you to select only a subset. Here’s a convention-based scan where you add only classes whose name starts with Sauce:

Assembly assembly = typeof(Steak).Assembly;

var types = container.GetTypesToRegister<IIngredient>(assembly)
    .Where(type => type.Name.StartsWith("Sauce"));

container.Collection.Register<IIngredient>(types);

This scan makes use of the GetTypesToRegister method, which searches for types without registering them. This allows you to filter the selection using a predicate. Instead of supplying Collection.Register using a list of assemblies, you now supply it with a list of Type instances.

Apart from selecting the correct types from an assembly, another part of Auto-Registration is defining the correct mapping. In the previous examples, you used the Collection.Register method with a specific interface to register all selected types against that interface. Sometimes, however, you may 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 by their base type:

Assembly policiesAssembly = typeof(DiscountPolicy).Assembly;

var policyTypes =
    from type in policiesAssembly.GetTypes()    ①  
    where type.Name.EndsWith("Policy")    ②  
    select type;

foreach (Type type in policyTypes)
{
    container.Register(type.BaseType, type);    ③  
}

In this example, you hardly use any part of the Simple Injector API. Instead, you use the reflection and LINQ APIs provided by the .NET framework to filter and get the expected types.

Even though Simple Injector’s convention-based API is limited, by making use of existing .NET framework APIs, convention-based registrations are still surprisingly easy. The Simple Injector’s convention-based API mainly focuses around the registration of sequences and generic types. This becomes a different ball game when it comes to generics, which is why Simple Injector has explicit support for registering types based on generic Abstractions, as we’ll discuss next.

Auto-Registration of generic Abstractions

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 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 14.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, and these are ideal candidates for using Auto-Registration. With Simple Injector, this couldn’t be simpler.

Listing 14.3 Auto-Registration of ICommandService<TCommand> implementations

Assembly assembly = typeof(AdjustInventoryService).Assembly;

container.Register(typeof(ICommandService<>), assembly);

In contrast to the previous listing that used Collection.Register, you again make use of Register. This is because there’ll always be exactly one implementation of a requested command service; you don’t want to inject a sequence of command services.

Using the supplied open-generic interface, Simple Injector iterates through the list of assembly types and registers 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>.

Not all ICommandService<TCommand> implementations will be registered, though. Simple Injector skips open-generic implementations, Decorators, and Composites, as they often require special registration. We’ll discuss this in section 14.4.

The Register method takes a params array of Assembly instances, so you can supply as many assemblies as you like to a single convention. It’s not a far-fetched idea to scan a folder for assemblies and supply them all to implement add-in functionality where add-ins can be added without recompiling a core application. (For an example, see https://simpleinjector.org/registering-plugins-dynamically.) This is one way to implement late binding; another is to use configuration files.

Configuring the container using configuration files

When you need to be able to change a configuration without recompiling the application, configuration files are a good option. The most natural way to use configuration files is to embed them into a standard .NET application configuration file. This is possible, but you can also use a standalone configuration file if you need to be able to vary the Simple Injector configuration independently of the standard .config file.

As stated in the beginning of this section, there’s no explicit support in Simple Injector for file-based configuration. By making use of .NET Core’s built-in configuration system, however, loading registrations from a configuration file is rather straightforward. For this purpose, you can define your own configuration structure that maps Abstractions to implementations. Here’s a simple example that maps the IIngredient interface to the Steak class.

Listing 14.4 Simple mapping from IIngredient to Steak using a configuration file

{
  "registrations": [
    {
      "service":
        "Ploeh.Samples.MenuModel.IIngredient, Ploeh.Samples.MenuModel",
      "implementation":
        "Ploeh.Samples.MenuModel.Steak, Ploeh.Samples.MenuModel"
    }
  ]
}

The registrations element is a JSON array of registration elements. The previous example contained a single registration, but you can add as many registration elements as you like. In each element, you must specify a concrete type with the implementation attribute. To map the Steak class to IIngredient, you can use the service attribute.

Using .NET Core’s built-in configuration system, you can load a configuration file and iterate through it. You then append the defined registrations to the container:

var config = new ConfigurationBuilder()    ①  
    .AddJsonFile("simpleinjector.json")    ①  
    .Build();    ①  

var registrations = config    ②  
    .GetSection("registrations").GetChildren();    ②  

foreach (var reg in registrations)    ③  
{    ③  
    container.Register(    ③  
        Type.GetType(reg["service"]),    ③  
        Type.GetType(reg["implementation"]));    ③  
}

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 and use either Auto-Registration or Configuration as Code for the main part of the container’s configuration.

This section introduced the Simple Injector DI Container and demonstrated these fundamental mechanics: how to configure a Container, and, subsequently, how to use it to resolve services. Resolving services is easily done with a single call to the GetInstance 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; we have yet to cover more-advanced areas. One of the most important topics is how to manage component lifetime.

14.2 Managing lifetime

In chapter 8, we discussed Lifetime Management, including the most common conceptual Lifestyles such as Transient, Singleton, and Scoped. Simple Injector’s Lifestyle supports mapping to these three Lifestyles. The Lifestyles shown in table 14.2 are available as part of the API.

Table 14.2 Simple Injector Lifestyles
Simple Injector namePattern nameComments
TransientTransientThis is the default Lifestyle. Transient instances aren’t tracked by the container and, therefore, are never disposed of. Using its diagnostic services, Simple Injector warns if you register a disposable component as Transient.
SingletonSingletonInstances are disposed of when the container is disposed of.
ScopedScopedTemplate for Lifestyles that allows scoping instances. A Scoped Lifestyle is defined by the ScopedLifestyle base class, and there are several ScopedLifestyle implementations. The most commonly used Lifestyle for a .NET Core application is AsyncScopedLifestyle. Instances are tracked for the lifetime of the scope and are disposed of when the scope is disposed of.

Simple Injector’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 in code. We’ll also look at Simple Injector’s concept of ambient scoping and how it can simplify working with the container. We’ll then cover how Simple Injector can verify and diagnose its configuration to prevent common configuration errors. By the end of this section, you should be able to use Simple Injector’s Lifestyles in your own application. Let’s start by reviewing how to configure Lifestyles for components.

14.2.1 Configuring Lifestyles

In this section, we’ll review how to manage Lifestyles with Simple Injector. A Lifestyle is configured as part of registering components. It’s as easy as this:

container.Register<SauceBéarnaise>(Lifestyle.Singleton);

This example 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 Register overload with two generic arguments, while supplying it with the Lifestyle.Singleton:

container.Register<IIngredient, SauceBéarnaise>(Lifestyle.Singleton);

Although Transient is the default Lifestyle, you can explicitly state it. These two examples are equivalent under the default configuration:2 

container.Register<IIngredient, SauceBéarnaise>(    ①  
    Lifestyle.Transient);    ①  

container.Register<IIngredient, SauceBéarnaise>();  ②  

Configuring Lifestyles for convention-based registrations can be done in several ways. When registering a sequence, for instance, one of the options is to supply the Collection.Register method with a list of Registration instances:

Assembly assembly = typeof(Steak).Assembly;

var types = container.GetTypesToRegister<IIngredient>(assembly);

container.Collection.Register<IIngredient>(
    from type in types
    select Lifestyle.Singleton.CreateRegistration(type, container));

You can use Lifestyle.Singleton to define the Lifestyle for all registrations in a convention. In this example, you define all IIngredient registrations as Singleton by supplying them all as a Registration instance to the Collection.Register overload.3 

When it comes to configuring Lifestyles for components, there are many options. In all cases, it’s done in a rather declarative fashion. Although configuration is typically easy, you mustn’t forget that some Lifestyles involve long-lived objects, which use resources as long as they’re around.

14.2.2 Releasing components

As discussed in section 8.2.2, it’s important to release objects when you’re done with them. Similar to Autofac, Simple Injector has no explicit Release method, but instead uses a concept called scopes. A scope can be regarded as a request-specific cache. As figure 14.3 illustrates, it defines a boundary where components can be reused.

14-03.eps

Figure 14.3 Simple Injector’s Scope acts as a request-specific cache that can share components for a limited duration or purpose.

A Scope defines a cache that you can use for a particular duration or purpose; the most obvious example is a web request. When a scoped component is requested from a 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 scopes is that they let you properly release components when the scope completes. You create a new scope with the BeginScope method of a particular ScopedLifestyle implementation and release all appropriate components by invoking its Dispose method:

using (AsyncScopedLifestyle.BeginScope(container))    ①  
{
    IMeal meal = container.GetInstance<IMeal>();    ②  

    meal.Consume();    ③  

}    ④  

This example shows how IMeal is resolved from the Container instance, instead of being resolved from a Scope instance. This isn’t a typo — the container automatically “knows” in which active scope it’s operating. The next section discusses this in more detail.

In the previous example, a new scope is created by invoking the BeginScope method on the corresponding Scoped Lifestyle. The return value implements IDisposable, so you can wrap it in a using block.

When you’re done with a scope, you can dispose of it with a using block. This happens automatically 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 during that scope. In the example, it means that you release the meal object graph.

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

container.Register<IIngredient, SauceBéarnaise>(Lifestyle.Scoped);

Similar to Lifestyle.Singleton and Lifestyle.Transient, you can use the Lifestyle.Scoped value to state that the component’s lifetime should live for the duration of the scope that created the instance. This call by itself, however, would cause the container to throw the following exception:

To be able to use the Lifestyle.Scoped property, please ensure that the container is configured with a default scoped lifestyle by setting the Container.Options.DefaultScopedLifestyle property with the required scoped lifestyle for your type of application. For more information, see https://simpleinjector.org/scoped.

Before you can use the Lifestyle.Scoped value, Simple Injector requires that you set the Container.Options.DefaultScopedLifestyle property. Simple Injector has multiple ScopedLifestyle implementations that are sometimes specific to a framework. This means you’ll have to explicitly configure the ScopedLifestyle implementation that works best for your type of application. For ASP.NET Core applications, the proper Scoped Lifestyle is the AsyncScopedLifestyle, which you can configure like this:

var container = new Container();

container.Options.DefaultScopedLifestyle =    ①  
    new AsyncScopedLifestyle();    ①  

container.Register<IIngredient, SauceBéarnaise>(
    Lifestyle.Scoped);    ②  

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 only dispose of it when the application shuts down. In this case, memory would be reclaimed by the operating system.

As we mentioned earlier in this section, with Simple Injector, you always resolve objects from the container — not from a scope. This works because scopes are ambient in Simple injector. Let’s look at ambient scopes next.

14.2.3 Ambient scopes

With Simple Injector, the previous example of the creation and disposal of the scope shows how you can always resolve instances from the Container, even if you resolve scoped instances. The following example shows this again:

using (AsyncScopedLifestyle.BeginScope(container))
{
    IMeal meal = container.GetInstance<IMeal>();    ①  

    meal.Consume();
}

This reveals an interesting feature of Simple Injector, which is that scope instances are ambient and are globally available in the context in which they’re running. The following listing shows this behavior.

Listing 14.5 Ambient scopes in Simple Injector

var container = new Container();

container.Options.DefaultScopedLifestyle =
    new AsyncScopedLifestyle();

Scope scope1 = Lifestyle.Scoped    ①  
    .GetCurrentScope(container);    ①  

using (Scope scope2 =
    AsyncScopedLifestyle.BeginScope(container))
{
    Scope scope3 = Lifestyle.Scoped    ②  
        .GetCurrentScope(container);    ②  
}

Scope scope4 = Lifestyle.Scoped    ③  
    .GetCurrentScope(container);    ③  

This behavior is similar to that of .NET’s TransactionScope class.4  When you wrap an operation with a TransactionScope, all database connections opened within that operation will automatically be part of the same transaction.

In general, you won’t use the GetCurrentScope method a lot, if at all. The Container uses this under the hood on your behalf when you start resolving instances. Still, it demonstrates nicely that Scope instances can be retrieved and are accessible from the container.

A ScopedLifestyle implementation, such as the previous AsyncScopedLifestyle, stores its created Scope instance for later use, which allows it to be retrieved within the same context. It’s the particular ScopedLifestyle implementation that defines when code runs in the same context. The AsyncScopedLifestyle, for instance, stores the Scope internally in an System.Threading.AsyncLocal<T>.5  This allows scopes to flow from method to method, even if an asynchronous method continues on a different thread, as this example demonstrates:

using (AsyncScopedLifestyle.BeginScope(container))
{
    IMeal meal = container.GetInstance<IMeal>();

    await meal.Consume();    ①  

    meal = container.GetInstance<IMeal>();    ②  
}

Although ambient scopes might be confusing at first, their usage typically simplifies working with Simple Injector. For instance, you won’t have to worry about getting memory leaks when resolving from the container, because Simple Injector manages this transparently on your behalf. Scope instances will never be cached in the root container, which is something you need to be cautious about with the other containers described in this book. Another area in which Simple Injector excels is the ability to detect common misconfigurations.

14.2.4 Diagnosing the container for common lifetime problems

Compared to Pure DI, registration and building object graphs in a DI Container is more implicit. This makes it easy to accidentally misconfigure the container. For that reason, many DI Containers have a function that allows all registrations to be iterated to enable verifying whether all can be resolved, and Simple Injector is no exception.

Being able to resolve an object graph, however, is no guarantee of the correctness of the configuration, as the Captive Dependency pitfall of section 8.4.1 illustrates. A Captive Dependency is a misconfiguration of the lifetime of a component. In fact, most errors concerning working with DI Containers are related to lifetime misconfigurations.

Because DI Container misconfigurations are so common and often difficult to trace, Simple Injector lets you verify its configuration, which goes beyond the simple instantiation of object graphs that most DI Containers support. On top of that, Simple Injector scans the object graphs for common misconfigurations — Captive Dependencies being one of them.

Therefore, the configure step of Simple Injector’s two-step process, as outlined in figure 14.1, exists of two substeps. Figure 14.4 shows this process.

14-04.eps

Figure 14.4 The pattern for using Simple Injector is to configure it, including verifying it, and then to resolve components.

The easiest way to let Simple Injector diagnose and detect configuration errors is by calling the Container’s Verify method, as shown in the following listing.

Listing 14.6 Verifying the container

var container = new Container();

container.Register<IIngredient, Steak>();  ①  

container.Verify();    ②  

Letting the container detect Captive Dependencies

The Captive Dependency misconfiguration is one that Simple Injector detects. Now let’s see how you can cause Verify to trip on a Captive Dependency using the Mayonnaise ingredient of section 14.1.1. Its constructor contained two Dependencies:

public Mayonnaise(EggYolk eggYolk, SunflowerOil oil)

The following listing registers Mayonnaise with its two Dependencies. But it misconfigures Mayonnaise as Singleton, whereas its EggYolk Dependency is registered as Transient.

Listing 14.7 Causing the container to detect a Captive Dependency

var container = new Container();

container.Register<EggYolk>(Lifestyle.Transient);    ①  
container.Register<Mayonnaise>(Lifestyle.Singleton);  ②  
container.Register<SunflowerOil>(Lifestyle.Singleton);

container.Verify();    ③  

When you call Register, Simple Injector only performs some rudimentary validations. This includes checking that the type isn’t abstract, that it has a public constructor, and the like. It won’t check for problems such as Captive Dependencies at that stage, because registrations can be made in any arbitrary order. In listing 14.7, for instance, SunflowerOil is registered after Mayonnaise, even though it’s a Dependency of Mayonnaise. It’s completely valid to do so. It’s only after the configuration is completed that verification can be performed. When you run this code example, the call to Verify fails with the following exception message:

The configuration is invalid. The following diagnostic warnings were reported:

-[Lifestyle Mismatch] Mayonnaise (Singleton) depends on EggYolk (Transient). See the Error property for detailed information about the warnings. Please see https://simpleinjector.org/diagnostics how to fix problems and how to suppress individual warnings.

An interesting observation here is that Simple Injector doesn’t allow Transient Dependencies to be injected into Singleton consumers. This is the opposite of Autofac. With Autofac, Transients are implicitly expected to live as long as their consumer, which means that in Autofac, this situation is never considered to be a Captive Dependency. For that reason, Autofac calls a Transient InstancePerDependency, which pretty much describes its behavior: each consumer’s Dependency that’s configured as Transient is expected to get its own instance. Because of that, Autofac only detects the injection of scoped components into Singletons as Captive Dependencies.

Although this might sometimes be exactly the behavior you need, in most cases, it’s not. More often, Transient components are expected to live for a brief period of time, whereas injecting them into a Singleton consumer causes the component to live for as long as the application lives. Because of this, Simple Injector’s motto is: “better safe than sorry,” which is why it throws an exception. Sometimes you might need to suppress such warnings in cases where you know best.

Suppressing warnings on individual registrations

In case you want to ignore EggYolk’s expiration date, Simple Injector lets you suppress the check on that particular registration.

Listing 14.8 Suppressing a diagnostic warning

var container = new Container();

Registration reg = Lifestyle.Transient    ①  
    .CreateRegistration<EggYolk>(container);    ①  

reg.SuppressDiagnosticWarning(    ②  
    DiagnosticType.LifestyleMismatch,    ②  
    justification: "I like to eat rotten eggs.");  ②  

container.AddRegistration(typeof(EggYolk), reg);    ③  

container.Register<Mayonnaise>(Lifestyle.Singleton);
container.Register<SunflowerOil>(Lifestyle.Singleton);

container.Verify();

SuppressDiagnosticWarning contains a required justification argument. It isn’t used by SuppressDiagnosticWarning at all, but serves as a reminder so that you don’t forget to document why the warning is suppressed.

This completes our tour of Lifetime Management with Simple Injector. Components can be configured with mixed Lifestyles, and this is even true when you register multiple implementations of the same Abstraction.

Until now, you’ve allowed the container to wire Dependencies by implicitly assuming that all components use Constructor Injection. But 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.

14.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 Simple Injector 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.

14.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 string and numeric types.

Conceptually, it doesn’t make sense to register a string or number as a component in a container. In particular, when Auto-Wiring is used, the registration of primitive types causes ambiguity. Take string, for instance. Where one component might require a database connection string, another might require a file path. The two are conceptually different, but because Auto-Wiring works by selecting Dependencies based on their type, they become ambiguous. For that reason, Simple Injector blocks the registration of primitive Dependencies. Consider as an example this constructor:

public ChiliConCarne(Spiciness spiciness)

In this example, Spiciness is an enum:

public enum Spiciness { Mild, Medium, Hot }

You might be tempted to register ChiliConCarne as in the following example. That won’t work!

container.Register<ICourse, ChiliConCarne>();

This line causes an exception with the following message:

The constructor of type ChiliConCarne contains parameter 'spiciness' of type Spiciness, which cannot be used for constructor injection because it’s a value type.

When you want to resolve ChiliConCarne with a medium Spiciness, you’ll have to depart from Auto-Wiring and instead use a delegate:7 

container.Register<ICourse>(() => new ChiliConCarne(Spiciness.Medium));

The downside of using delegates is that the registration has to be changed when the ChiliConCarne constructor changes. When you add an IIngredient Dependency to the ChiliConCarne constructor, for instance, the registration must be updated:

container.Register<ICourse>(() =>    ①  
    new ChiliConCarne(
        Spiciness.Medium,
        container.GetInstance<IIngredient>()));    ②  

Besides the additional maintenance in the Composition Root, and because of the lack of Auto-Wiring, the use of delegates disallows Simple Injector from verifying the validity of the relationship between ChiliConCarne and its IIngredient Dependency. The delegate hides the fact that this Dependency exists. This isn’t always a problem, but it can complicate diagnosing problems that are caused due to misconfigurations. Because of these downsides, a more convenient solution is to extract the primitive Dependencies into Parameter Objects.

14.3.2 Extracting primitive Dependencies to 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. For example, the Spiciness of a course could be described in more general terms as a flavoring. Flavoring might include other properties, such as saltiness, so you can wrap Spiciness and the saltiness in a Flavoring class:

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

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

As we mentioned in section 10.3.3, it’s perfectly fine for Parameter Objects to have one parameter. The goal is to remove ambiguity, and not just on the technical level. Such a Parameter Object’s name might do a better job describing what your code does on a functional level, as the Flavoring class so elegantly does. With the introduction of the Flavoring Parameter Object, it now becomes possible to Auto-Wire any ICourse implementation that requires some flavoring:

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

container.Register<ICourse, ChiliConCarne>();

This code creates a single instance of the Flavoring class. Flavoring becomes a configuration object for courses. Because there’ll only be one Flavoring instance, you can register it in Simple Injector using RegisterInstance.

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

14.3.3 Registering objects with code blocks

As we discussed in the previous section, one of the options for creating a component with a primitive value is to use the Register method. This lets you supply a delegate that creates the component. Here’s that registration again:

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

The ChiliConCarne constructor is invoked with Hot Spiciness every time the ICourse service is resolved. Instead of Simple Injector figuring out the constructor arguments, however, you write the constructor invocation yourself using a code block.

When it comes to application classes, you typically have a choice between Auto-Wiring or using a code block. But other classes are 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 the next 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 Simple Injector’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:

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

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

When you end up writing the code to create the instance, how is this in any way 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.
  • You can still configure Lifestyles. 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, and the result cached and reused thereafter.

In this section, you’ve seen how you can use Simple Injector to deal with more-difficult APIs. 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.

14.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.8  This can be the case in these situations:

  • 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 Simple Injector addresses each one 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 fine-grained control in the case of ambiguity.

14.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 Simple Injector deals with multiple registrations of the same Abstraction.

Configuring multiple implementations of the same service

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

container.Collection.Register<IIngredient>(
    typeof(SauceBéarnaise),
    typeof(Steak));

This example registers both the Steak and SauceBéarnaise classes as a sequence of IIngredient services. You can ask the container to resolve all IIngredient components. Simple Injector has a dedicated method to do that: GetAllInstances gets an IEnumerable with all registered ingredients. Here’s an example:

IEnumerable<IIngredient> ingredients =
    container.GetAllInstances<IIngredient>();

You can also ask the container to resolve all IIngredient components using GetInstance instead:

IEnumerable<IIngredient> ingredients =
    container.GetInstance<IEnumerable<IIngredient>>();

Notice that you request IEnumerable<IIngredient>, but you use the normal GetInstance method. Simple Injector interprets this as a convention and gives you all the IIngredient components it has.

When there are multiple implementations of a certain Abstraction, there’ll often be a consumer that depends on a sequence. Sometimes, however, components need to work with a fixed set or a subset of Dependencies of the same Abstraction, which is what we’ll discuss next.

Removing ambiguity using conditional registrations

As useful as Auto-Wiring is, sometimes you need to override the normal behavior to provide fine-grained control over which Dependencies go where, but it may 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. With most DI Containers, the typical solution for this type of problem is to use keyed or named registrations, as you saw with Autofac in the previous chapter. With Simple Injector, the solution is typically to change the registration of the Dependency instead of the consumer. The following listing shows how you could choose to register the ICourse mappings.

Listing 14.9 Registering courses based on the constructor’s parameter names

container.Register<IMeal, ThreeCourseMeal>();    ①  

container.RegisterConditional<ICourse, Rillettes>(    ②  
    c => c.Consumer.Target.Name == "entrée");    ②  
    ②  
container.RegisterConditional<ICourse, CordonBleu>(    ②  
    c => c.Consumer.Target.Name == "mainCourse");    ②  
    ②  
container    ②  
    .RegisterConditional<ICourse, MousseAuChocolat>(  ②  
    c => c.Consumer.Target.Name == "dessert");    ②  

Let’s take a closer look at what’s going on here. The RegisterConditional method accepts a Predicate<PredicateContext> value, which allows it to determine whether a registration should be injected into the consumer or not. It has the following signature:

public void RegisterConditional<TService, TImplementation>(
    Predicate<PredicateContext> predicate)
    where TImplementation : class, TService
    where TService : class;

System.Predicate<T> is a .NET delegate type. The predicate value will be invoked by Simple Injector. If predicate returns true, it uses the registration for the given consumer. Otherwise, Simple Injector expects another conditional registration to have a delegate that returns true. It throws an exception when it can’t find a registration, because, in that case, the object graph can’t be constructed. Likewise, it throws an exception when there are multiple registrations that are applicable.

Simple Injector is strict and never assumes to know what you intended to select, as we discussed previously regarding components with multiple constructors. This does mean, though, that Simple Injector always calls all predicates of all applicable conditional registrations to find possible overlapping registrations. This might seem inefficient, but those predicates are only called when a component is resolved for the first time. Any following resolution has all the information available, which means additional resolutions are fast.

By overriding Auto-Wiring using conditional registered components, you allow Simple Injector to build the entire object graph without having to revert to registering a code block, as we discussed in section 14.3.3. This is useful when working with Simple Injector because of the previously discussed diagnostic capabilities. The use of code blocks blinds a container, which might cause configuration mistakes to stay undetected for too long.

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 Simple Injector deals with lists and sequences.

14.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 a 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 Simple Injector deal with it, it can seem awkward. This is often an indication that you could improve the design of your code.

Instead of feeling constrained by Simple Injector, 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 Simple Injector deals with sequences.

Refactoring to a better course by removing ambiguity

In section 14.4.1, you saw how the ThreeCourseMeal and its inherent ambiguity forced you to complicate your registration. This should prompt you to reconsider the API design. 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 meal: 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 Simple Injector 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

Simple Injector 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, given a set of configured ICourse instances, you can configure the IMeal service like this:

container.Register<IMeal, Meal>();

Notice that this is a completely standard mapping from an Abstraction to a concrete type. Simple Injector 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. This still requires you to register the sequence of ICourse components, for instance, using Auto-Registration:

container.Collection.Register<ICourse>(assembly);

Simple Injector automatically handles sequences, and unless you specify otherwise, it does what you’d expect it to do: it resolves a sequence of Dependencies for all registrations of that Abstraction. Only when you need to explicitly pick only 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

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

14-05.eps

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

When you previously let Simple Injector Auto-Register and 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 Collection.Create method, which allows creating a subset of a sequence. The following listing shows how to inject a subset of a sequence into a consumer.

Listing 14.10 Injecting a sequence subset into a consumer

IEnumerable<ICourse> coursesSubset1 =
    container.Collection.Create<ICourse>(    ①  
        typeof(Rillettes),    ①  
        typeof(CordonBleu),    ①  
        typeof(MousseAuChocolat));    ①  

IEnumerable<ICourse> coursesSubset2 =
    container.Collection.Create<ICourse>(    ②  
        typeof(CeasarSalad),    ②  
        typeof(ChiliConCarne),    ②  
        typeof(MousseAuChocolat));    ②  

container.RegisterInstance<IMeal>(    ③  
    new Meal(sourcesSubset1));    ③  

The Collection.Create method lets you create a sequence of a given Abstraction. The sequence itself won’t be registered in the container — this can be done using Collection.Register. By calling Collection.Create multiple times for the same Abstraction, you can create multiple sequences that are all different subsets, as shown in listing 14.10.

What might be surprising about listing 14.10 is that the call to Collection.Create doesn’t create the courses at that point in time. Instead, the sequence is a stream. Only when you start iterating the sequence will it start to resolve instances. Because of this behavior, the sequence subset can be safely injected into the Singleton Meal without causing any harm. We’ll go into more detail about streams in section 14.4.5.

Simple Injector natively understands sequences. Unless you need to explicitly pick only some components from all services of a given type, Simple Injector automatically does the right thing.

Auto-Wiring works not only with single instances, but also for sequences; 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 Decorator design pattern, which we’ll discuss next.

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

Simple Injector has built-in support for registering Decorators using the RegisterDecorator method. And, in this section, we’ll discuss both registrations of non-generic and generic Abstractions. Let’s start with the former.

Decorating non-generic Abstractions

Using the RegisterDecorator method, you can elegantly register a Decorator. The following example shows how to use this method to apply Breading to a VealCutlet:

var c = new Container();

c.Register<IIngredient, VealCutlet>();    ①  

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

As you learned in chapter 9, you get veal 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:

var c = new Container();

c.Register<IIngredient, VealCutlet>();

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

c.RegisterDecorator<IIngredient, Breading>();

By placing this new registration before the Breading registration, the HamCheeseGarlic Decorator will be 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 Simple Injector. Likewise, you can apply generic Decorators, as you’ll see next.

Decorating generic Abstractions

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 Simple Injector. The following listing demonstrates how to register all ICommandService<TCommand> implementations with the three Decorators presented in section 10.3.

Listing 14.11 Decorating generic Auto-Registered Abstractions

container.Register(    ①  
    typeof(ICommandService<>), assembly);    ①  

container.RegisterDecorator(    ②  
    typeof(ICommandService<>),    ②  
    typeof(AuditingCommandServiceDecorator<>));    ②  
    ②  
container.RegisterDecorator(    ②  
    typeof(ICommandService<>),    ②  
    typeof(TransactionCommandServiceDecorator<>));  ②  
    ②  
container.RegisterDecorator(    ②  
    typeof(ICommandService<>),    ②  
    typeof(SecureCommandServiceDecorator<>));    ②  

As in listing 14.3, you use a Register overload to register arbitrary ICommandService<TCommand> implementations by scanning assemblies. To register generic Decorators, you use the RegisterDecorator method that accepts two Type instances. The result of the configuration of listing 14.11 is figure 14.6, which we discussed previously in section 10.3.4.

14-06.eps

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

When it comes to Simple Injector’s support for Decorators, this is only the tip of the iceberg. Several RegisterDecorator overloads allow Decorators to be made conditionally, like the previously discussed RegisterConditional overload of listing 14.9. A discussion of this and other features, however, is out of the scope of this book.9 

Simple Injector lets you work with multiple Decorator 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, Simple Injector figures out what to do. You can always explicitly define how services are composed if you need more-explicit control.

In this section, we focused on Simple Injector’s methods that were explicitly designed for configuring Decorators. 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.

14.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, such as the CompositeNotificationService from chapter 6 in Simple Injector. The following listing shows this class again.

Listing 14.12 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);
        }
    }
}

Because the Simple Injector API separates the registration of sequences from non-sequence registrations, the registration of Composites couldn’t be any easier. You can register the Composite as a single registration, while registering its Dependencies as a sequence:

container.Collection.Register<INotificationService>(
    typeof(OrderApprovedReceiptSender),
    typeof(AccountingNotifier),
    typeof(OrderFulfillment),
);

container.Register<INotificationService, CompositeNotificationService>();

In the previous example, three INotificationService implementations are registered as a sequence using Collection.Register. The CompositeNotificationService, on the other hand, is registered as single, non-sequence registration. All types are Auto-Wired by Simple Injector. Using the previous registration, when an INotificationService is resolved, it results in an object graph similar to the following Pure DI representation:

return new CompositeNotificationService(new INotificationService[]
{
    new OrderApprovedReceiptSender(),
    new AccountingNotifier(),
    new OrderFulfillment()
});

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 Collection.Register overload that accepts an Assembly. This lets you turn the previous list of types into a simple one-liner:

container.Collection.Register<INotificationService>(assembly);

container.Register<INotificationService, CompositeNotificationService>();

You may recall from chapter 13 that a similar construct in Autofac didn’t work, because Autofac’s Auto-Registration would register the Composite as well as part of the sequence. This, however, isn’t the case with Simple Injector. It’s Collection.Register method automatically filters out any Composite types and prevents them from being registered as part of the sequence.

Composite classes, however, aren’t the only classes that will automatically be removed from the list by Simple Injector. Simple Injector also detects Decorators in the same way. This behavior makes working with Decorators and Composites in Simple Injector a breeze. The same holds true for working with generic Composites.

Wiring generic Composites

In section 14.4.2, you saw how Simple Injector’s RegisterDecorator method made registering generic Decorators look like 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. We’ll start with the Auto-Registration of the event handlers:

container.Collection.Register(typeof(IEventHandler<>), assembly);

In contrast to the registration of ICommandService<T> implementations in listing 14.3, you now use Collection.Register instead of Register. That’s because there’ll potentially be multiple handlers for a particular type of event. This means you have to explicitly state that you know there’ll be more implementations for the single event type. Were you to have accidentally called Register instead of Collection.Register, Simple Injector would have thrown an exception similar to the following:

In the supplied list of types or assemblies, there are 3 types that represent the same closed-generic type IEventHandler<OrderApproved>. Did you mean to register the types as a collection using the Collection.Register method instead? Conflicting types: OrderApprovedReceiptSender, AccountingNotifier, and OrderFulfillment.

A nice thing about this message is that it already indicates you most likely should be using Collection.Register instead of Register. But it’s also possible that you accidentally added an invalid type that was picked up. As we explained before, when it comes to ambiguity, Simple Injector forces you to be explicit, which is helpful in detecting errors.

What remains is the registration for CompositeEventHandler<TEvent>. Because CompositeEventHandler<TEvent> is a generic type, you’ll have to use the Register overload that accepts Type arguments:

container.Register(    ①  
    typeof(IEventHandler<>),    ①  
    typeof(CompositeEventHandler<>));  ①  

Using this registration, when a particular closed IEventHandler<TEvent> Abstraction is requested (for example, IEventHandler<OrderApproved>), Simple Injector determines the exact CompositeEventHandler<TEvent> type to create. In this case, this is rather straightforward, because requesting an IEventHandler<OrderApproved> results in a CompositeEventHandler<OrderApproved> getting resolved. In other cases, determining the exact closed type can be a rather complex process, but Simple Injector handles this well.

Working with sequences is rather straightforward in Simple Injector. When it comes to resolving and injecting sequences, however, Simple Injector behaves differently compared to other DI Containers in a captivating way. As we alluded earlier, Simple Injector handles sequences as streams.

14.4.5 Sequences are streams

In section 14.1, you registered a sequence of ingredients as follows:

container.Collection.Register<IIngredient>(
    typeof(SauceBéarnaise),
    typeof(Steak));

As shown previously, you can ask the container to resolve all IIngredient components using either the GetAllInstances or GetInstance methods. Here’s the example using GetInstance again:

IEnumerable<IIngredient> ingredients =
    container.GetInstance<IEnumerable<IIngredient>>();

You might expect the call to GetInstance<IEnumerable<IIngredient>>() to create an instance of both classes, but this couldn’t be further from the truth. When resolving or injecting an IEnumerable<T>, Simple Injector doesn’t prepopulate the sequence with all ingredients right away. Instead, IEnumerable<T> behaves like a stream.10  What this means is that the returned IEnumerable<IIngredient> is an object that’s able to produce new IIngredient instances when it’s iterated. This is similar to streaming data from disk using a System.IO.FileStream or a database using a System.Data.SqlClient.SqlDataReader, where data arrives in small chunks rather than prefetching all the data in one go.

The following example shows how iterating a stream multiple times can produce new instances:

IEnumerable<IIngredient> stream =
    container.GetAllInstance<IIngredient>();

IIngredient ingredient1 = stream.First();    ①  
IIngredient ingredient2 = stream.First();    ②  

object.ReferenceEquals(ingredient1, ingredient2);  ③  

When a stream is iterated, it calls back into the container to resolve elements of the sequence based on their appropriate Lifestyle. This means that if the type is registered as Transient, new instances are always produced, as the previous example showed. When the type is Singleton, however, the same instance is returned every time:

var c = new Container();

c.Collection.Append<IIngredient, SauceBéarnaise>();    ①  
c.Collection.Append<IIngredient, Steak>(    ①  
    Lifestyle.Singleton);    ①  

var s = c.GetInstance<IEnumerable<IIngredient>>();

object.ReferenceEquals(s.First(), s.First());    ②  
object.ReferenceEquals(s.Last(), s.Last());    ③  

Although streaming isn’t a common trait under DI Containers, it has a few interesting advantages. First, when injecting a stream into a consumer, the injection of the stream itself is practically free, because no instance is created at that point in time.11  This is useful when the list of elements is big, and not all elements are needed during the lifetime of the consumer. Take the following Composite ILogger implementation, for instance. It’s a variation of the Composite of listing 8.22 but, in this case, the Composite stops logging directly after one of the wrapped loggers succeeds.

Listing 14.13 A Composite that processes part of the injected stream

public class CompositeLogger : ILogger    ①  
{
    private readonly IEnumerable<ILogger> loggers;

    public CompositeLogger(
        IEnumerable<ILogger> loggers)    ②  
    {
        this.loggers = loggers;
    }

    public void Log(LogEntry entry)
    {
        foreach (ILogger logger in this.loggers)    ③  
        {
            try
            {
                logger.Log(entry);
                break;    ④  
            }
            catch { }    ⑤  
        }
    }
}

As you saw in section 14.4.4, you can register the CompositeLogger and the sequence of ILogger implementations as follows:

container.Collection.Register<ILogger>(assembly);
container.Register<ILogger, CompositeLogger>(Lifestyle.Singleton);

In this case, you registered the CompositeLogger as Singleton because it’s stateless, and its only Dependency, the IEnumerable<ILogger>, is itself a Singleton. The effect of the CompositeLogger and ILogger sequences as Singletons is that the injecting of CompositeLogger is practically free. Even when a consumer calls its Dependency’s Log method, this typically only results in the creation of the first ILogger implementation of the sequence — not all of them.

A second advantage of sequences being streams is that, as long as you only store the reference to IEnumerable<ILogger>, as listing 14.13 showed, the sequence’s elements can never accidentally become Captive Dependencies. The previous example already showed this. The Singleton CompositeLogger could safely depend on IEnumerable<ILogger>, because it also is a Singleton, even though its produced services might not be.

In this section, you’ve seen how to deal with multiple components such as sequences, Decorators, and Composites. This ends our discussion of Simple Injector. In the next chapter, we’ll turn our attention to Microsoft.Extensions.DependencyInjection.

Summary

  • Simple Injector is a modern DI Container that offers a fairly comprehensive feature set, but its API is quite different from most DI Containers. The following are a few of its characteristic attributes:
    • Scopes are ambient.
    • Sequences are registered using Collection.Register instead of appending new registrations of the same Abstraction.
    • Sequences behave as streams.
    • The container can be diagnosed to find common configuration pitfalls.
  • An important overall theme for Simple Injector is one of strictness. It doesn’t attempt to guess what you mean and tries to prevent and detect configuration errors through its API and diagnostic facility.
  • Simple Injector enforces a strict separation of registration and resolution. Although you use the same Container instance for both register and resolve, the Container is locked after first use.
  • Because of Simple Injector’s ambient scopes, resolving from the root container directly is good practice and encouraged: it doesn’t lead to memory leaks or concurrency bugs.
  • Simple Injector supports the standard Lifestyles: Transient, Singleton, and Scoped.
  • Simple Injector has excellent support for registration of sequences, Decorators, Composites, and generics.
..................Content has been hidden....................

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