Simple injector register per web request and with context

c#, dependency-injection, simple-injector

Solution

The `RegisterWithContext` extension method explicitly registers the supplied delegate as `Transient`. This is done, because registering the type with any other lifestyle makes little sense.

The idea of a lifestyle such as the `WebRequestLifestyle` is to cache and reuse the same instance throughout the object graph (and possibly beyond that). This concept however, makes little sense when dealing with a context-based registration, simply because a context-based instance is expected to be different each time it gets injected. In other words, supplying each consumer with a unique instance conflicts with the concept of reusing the same instance.

Take for instance a look at the following object graph:

new HomeController(
    new Logger("HomeController"),
    new LoggingRepositoryDecorator<User>(
        new Logger("LoggingRepositoryDecorator<User>"),
        new SqlRepository<User>(
            new DbContext())),
    new LoggingCommandHandlerDecorator<ShipOrder>(
        new Logger("LoggingCommandHandlerDecorator<ShipOrder>"),
        new ShipOrderCommandHandler(
            new DbContext())));

In this object graph we create a `HomeController` with its dependencies. The `Logger` component is clearly a context-based component, since its initialized differently every time, based on its parent. This would be the registration for `Logger`:

container.RegisterWithContext<ILogger>(context =>
    new Logger(context.ImplementationType.Name));

But if we allowed the `ILogger` registration to be registered with the `WebRequestLifestyle`, the same instance should be applied every time, which might lead to the following object graph:

ILogger logger = new Logger(typeName: "HomeController");

new HomeController(
    logger,
    new LoggingRepositoryDecorator<User>(
        logger,
        new SqlRepository<User>(
            new DbContext())),
    new LoggingCommandHandlerDecorator<ShipOrder>(
        logger,
        new ShipOrderCommandHandler(
            new DbContext())));

In this object graph the same `Logger("HomeController")` is injected, which is clearly not what we want. Furthermore, the behavior becomes very unpredictable, since it is the consumer that gets created first, that will determine the logger's `typeName` that gets reused throughout the graph. But nobody would expect that removing the `ILogger` from the `HomeController`'s constructor, would cause the logger of the `LoggingCommandHandlerDecorator<ShipOrder>` to change.

That's why there is no `Lifestyle` argument in the `RegisterWithContext` extension method.

Problem

There is `RegisterPerWebRequest` and `RegisterWithContext` (this one is not originally shipped with simple injector, however it supplied on their advanced-scenarios section). Separately both approaches works fine but I need to combine them. I figured out that in `RegisterPerWebRequest` used `new WebRequestLifestyle()` lifestyle (found it there). So instead of using `Lifestyle.Transient` `RegisterWithContext` I supplied `new WebRequestLifestyle()` but it appears that `DependencyContext.ImplementationType` and `DependencyContext.ServiceType` are null. What is wrong with that? Update 1. So I wanted to Register type per web request as does `RegisterPerWebRequest` but also to be able to provide instance creator with access to type where registered type is injected. I modified (extracted lifestyle as a parameter) `RegisterWithContext` to be: ``` public static void RegisterWithContext<TService>( this Container container, Func<DependencyContext, TService> contextBasedFactory, Lifestyle lifestyle) where TService : class { //original code container.Register<TService>(rootFactory, lifestyle); //original code } ``` And for "per web request and with context" registration I expected to be able use: ``` container.RegisterWithContext<IUnitOfWork>(dependencyContext => { var implementationType = dependencyContext.ImplementationType; //do some stuff and return preconfigured UnitOfWork }, new WebRequestLifestyle()); ``` And as I already mentioned `dependencyContext.ImplementationType` is `NULL` I am using SimpleInjector 2.3.0.0

Original source