Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting Lifestyle of collection items during registration in Simple Injector

It seems that Container.Collection.Register does not have an overload which takes a Lifestyle. All discovered implementations will be registered with the default Lifestyle. What is the reasoning behind the omission of such an overload?

What is the preferred way of adding a collection where all items should have a Lifestyle that is not the default lifestyle?

like image 285
AroglDarthu Avatar asked May 20 '19 10:05

AroglDarthu


1 Answers

What is the reasoning behind the omission of such an overload?

First of all, because, (as Eric Lippert stated):

no one ever designed, specified, implemented, tested, documented and shipped that feature. All six of those things are necessary to make a feature happen. All of them cost

Second, the Collection.Register overloads that accept a list of Type instances (e.g. Collection.Register<TService>(params Type[])), accept a list of both implementations and abstractions. Supplying a Lifestyle for abstractions wouldn't make much sense and is even confusing.

To understand why Simple Injector allows supplying abstractions, take a look at the following registration:

container.Collection.Register<ILogger>(
    typeof(ILogger),
    typeof(ISpecialLogger),
    typeof(SqlLogger));

container.Register<ILogger, DefaultLogger>(Lifestyle.Scoped);
container.Register<ISpecialLogger, SpecialLogger>(Lifestyle.Singleton);

In this example, a collection of loggers is registered, where two of the supplied types are abstractions. The idea behind allowing supplying abstractions is that, with it, you can point them to other registrations. This is exactly what the previous example does. When resolving the collection of loggers, it will consist of the Scoped DefaultLogger, the Singleton SpecialLogger, and the Transient SqlLogger.

Now consider a hypothetical new Collection.Register overload that accepts a Lifestyle:

container.Collection.Register<ILogger>(new[]
    {
        typeof(ILogger),
        typeof(ISpecialLogger),
        typeof(SqlLogger)
    },
    Lifestyle.Transient);

container.Register<ILogger, DefaultLogger>(Lifestyle.Scoped);
container.Register<ISpecialLogger, SpecialLogger>(Lifestyle.Singleton);

What does it mean to have the all elements being Transient, while two of the elements point to registrations of different lifestyles?

We haven't found a good API that solves these issues (yet), which is why the container is lacking such overload.

What is the preferred way of adding a collection where all items should have a Lifestyle that is not the default lifestyle?

There are multiple ways to do this.

Option 1: Register the element explicitly

Collections, registered in Simple Injector, use a fallback mechanism to determine their lifestyle. This is done by checking whether there exists a concrete registration for the collection's element. For instance:

container.Collection.Register<ILogger>(typeof(SqlLogger), typeof(FileLogger));

// Ensures that SqlLogger is a Singleton when part of the IEnumerable<ILogger>.
container.Register<SqlLogger>(Lifestyle.Singleton);

When the IEnumerable<ILogger> is first resolved by Simple Injector, for each element it will (in the following order):

  • Try to get an explicitly registered concrete registration (in the example: Register<SqlLogger>
  • Try to get that registration using unregistered type resolution
  • Try to create the registration itself while making use of the configured Container.Options.LifestyleSelectionBehavior (which defaults to Transient).

Option 2: Use the Register(Type, IEnumerable<Registration>) overload

Instead of supplying a list of types or assemblies to Collection.Register, you can also supply a list of Registration instances. A Registration describes the creation of a certain component for a certain lifestyle and this class is used by Simple Injection internally when you call Container.Register or Container.Collection.Register. But you can also create Registration instances manually and supply them to a Collection.Register overload as follows:

// Load the list of types without registering them
Type[] types = container.GetTypesToRegister<ILogger>(assemblies);

// Register them using the overload that takes in a list of Registrations
container.Collection.Register<ILogger>(
    from type in types
    select Lifestyle.Transient.CreateRegistration(type, container));

This forces all registered types of the collection to have the Transient lifestyle. You can also give each type its own specific lifestyle if you want.

Option 3: Override LifestyleSelectionBehavior

When no registration can be found, Collection.Register makes the registration itself, last minute, using the configured LifestyleSelectionBehavior. The default selection behavior always returns Lifestyle.Transient, but this behavior can be changed. For instance:

class CustomLifestyleSelectionBehavior : ILifestyleSelectionBehavior
{
    public Lifestyle SelectLifestyle(Type implementationType) =>
        implementationType == typeof(SqlLogger)
            ? Lifestyle.Singleton
            : Lifestyle.Transient;
}

This implementation if, obviously, a bit naive, but shows the concept. You can override the default behavior as follows:

container.Options.LifestyleSelectionBehavior =
    new CustomLifestyleSelectionBehavior();

Option 4: Append

Next to Collection.Register, which allows the registration of all elements in one go, you can make use of the Collection.Append methods. They allow one-by-one registration of collection elements:

container.Collection.Append<ILogger, SqlLogger>(Lifestyle.Singleton);
container.Collection.Append<ILogger, FileLogger>(Lifestyle.Transient);

There are also non-generic overloads available, which simplify auto-registering these types, for instance when returned using Container.GetTypesToRegister.

These are your basic options in Simple Injector v4.6. We might decide to add a convenient Collection.Register<T>(IEnumerable<Type>, Lifestyle) overload in the future, as the ommision of this overload does cause some confusion from time to time.

like image 199
Steven Avatar answered Oct 23 '22 10:10

Steven