SimpleInjector mixed lifestyle for per web request

2019-07-07 07:13发布

问题:

I am using Simple Injector as my IoC container and employ the following technique to enable registering a "mixed" lifestyle for some objects as both per web request or per thread.

interface IUnitOfWork { }
interface IWebUnitOfWork : IUnitOfWork { }
interface IThreadUnitOfWork : IUnitOfWork { }
class UnitOfWork : IWebUnitOfWork, IThreadUnitOfWork { }

container.RegisterPerWebRequest<IWebUnitOfWork, UnitOfWork>();
container.RegisterLifetimeScope<IThreadUnitOfWork, UnitOfWork>();
container.Register<IUnitOfWork>(() => container.GetInstance<UnitOfWork>());

// Register as hybrid PerWebRequest / PerLifetimeScope.
container.Register<UnitOfWork>(() =>
{
    if (HttpContext.Current != null)
        return container.GetInstance<IWebUnitOfWork>() as UnitOfWork;
    else
        return container.GetInstance<IThreadUnitOfWork>() as UnitOfWork;
});

I am not entirely satisfied with this solution as for each requirement I must define extra empty interfaces to make it work and ensure they are referenced by my concrete class.

Is there any reason I should not use the following extension methods instead of defining the extra interfaces? If there is some issue with these methods is there some other way to establish, with complete confidence, that my current instance of the container is running within IIS?

public static void RegisterHybridLifestyle<TService, TImplementation>(
    this Container container)
    where TService : class
    where TImplementation : class, TService
{
    if (System.Web.Hosting.HostingEnvironment.ApplicationHost != null)
        container.RegisterPerWebRequest<TService, TImplementation>();
    else
        container.RegisterLifetimeScope<TService, TImplementation>();
}

public static void RegisterForLifestyle<TConcrete>(
    this Container container)
    where TConcrete : class
{
    if (HostingEnvironment.ApplicationHost != null)
        container.RegisterPerWebRequest<TConcrete>();
    else
        container.RegisterLifetimeScope<TConcrete>();
}

UPDATE

The above question and this follow on question were based on a misunderstanding of SimpleInjector and hybrid registration. The techniques described above and elsewhere on SO are for when the container can be servicing request for both Web Requests and for background processes that are not running within the context of a web request. What I have been trying to achieve is variable registration to cater for a configuration of the container that is suitable for both Web Request OR Thread Request. i.e. I need to configure my container to work within IIS and to work within a windows service. I don't need the dynamic registration that can cater for both a the same time.

The outcome of this is the following extension methods and I have removed the "extra" interfaces from my solution :-)

public static void RegisterForScope<TService, TImplementation>(this Container container)
    where TService : class
    where TImplementation : class, TService
{
    if (System.Web.Hosting.HostingEnvironment.ApplicationHost != null)
        container.RegisterPerWebRequest<TService, TImplementation>();
    else
        container.RegisterLifetimeScope<TService, TImplementation>();
}

public static void RegisterForScope<TConcrete>(this Container container)
    where TConcrete : class
{
    if (System.Web.Hosting.HostingEnvironment.ApplicationHost != null)
        container.RegisterPerWebRequest<TConcrete>();
    else
        container.RegisterLifetimeScope<TConcrete>();
}

回答1:

I am not entirely satisfied with this solution

Yes, I agree with this. To be honest, having to do things like this actually sucks IMO. That's why this is fixed in Simple Injector 2.0. It contains the explicit notion of lifestyle and it will contain a Lifestyle.CreateHybrid method, that makes it much easier to register hybrid lifestyles.

You however, don't seem to need a hybrid lifestyle add all. A hybrid lifestyle is a lifestyle that can switch dynamically (on each call to GetInstance<T> and per each injection), while you only seem to need to switch during start-up. I see no harm in using the RegisterHybridLifestyle extension method as you defined it, but keep in mind that this is not really an hybrid lifestyle (so the name is a bit misleading), but simply a configuration/deployment-switch.

Simple Injector 2 and up make this much easier, and it will allow you to do something like this:

// Define a lifestyle once based on the deployment.
Container.Options.DefaultScopedLifestyle =
    Lifestyle.CreateHybrid(
        lifestyleSelector: HostingEnvironment.ApplicationHost != null,
        trueLifestyle: new WebRequestLifestyle(),
        falseLifestyle: new LifetimeScopeLifestyle());

// And use it for registering the unit of work
// (no extra interfaces needed anymore).
container.Register<IUnitOfWork, UnitOfWork>(Lifestyle.Scoped);

// After setting DefaultScopedLifestyle, you can usr Lifestyle.Scoped
container.RegisterCollection(
    typeof(ISubscriber<>),
    new [] { typeof(ISubscriber<>).Assembly },
    Lifestyle.Scoped);