SimpleInjector icon indicating copy to clipboard operation
SimpleInjector copied to clipboard

Making ServiceCollection resolve DI using Simple Injector

Open avnerms opened this issue 3 years ago • 4 comments

My dependencies are registered in SimpleInjector. A part of my code uses ServiceCollection so I want calls to serviceCollection.GetService<T>() to go and get the service from my SimpleInjector container.

I have seen many examples of the usage of .AddSimpleInjector() and .UseSimpleInjector() but they are all making SimpleInjector resolve services using ServiceCollection and not the other way around.

I wrote this code:

var container = new SimpleInjector.Container();
var provider = new ServiceCollection()
    .AddSimpleInjector(container)
    .BuildServiceProvider(true);

provider.UseSimpleInjector(container);
container.Register<ISIDoer, SIDoer>();
container.Verify();

var serviceCollectionInstance = provider.GetService<ISIDoer>();
var simpleInjectorInsance = container.GetService<ISIDoer>();

serviceCollectionInstance is null, while simpleInjectorInsance has a value.

What am I missing?

avnerms avatar Jul 07 '22 14:07 avnerms

You will have to cross wire each specific service from MS.DI to Simple Injector, for instance:

services.AddTransient<ISIDoer>(c => container.GetInstance<ISIDoer>());

Unfortunately, the only way to achieve this in MS.DI is by making explicit registrations as given in the code sample above. MS.DI doesn't contain any triggers, events, or other interception behavior that allow running custom code when an unregistered service is being resolved, which is what you need to forward any missing registration back to Simple Injector.

dotnetjunkie avatar Jul 07 '22 15:07 dotnetjunkie

If I may hijack this thread as I wanted to ask the same question.

Is there a way to make this invocation aware of the MS.DI scope? We are using a third party library (workflow-core) that depends on Microsoft.Extensions.DependencyInjection.Abstractions, in its internals it is creating a IServiceScope scope and calling scope.ServiceProvider.GetRequiredService , and when we want to use proposed cross wire method above we get the following error:

The registered delegate for type MyStep threw an exception. Error resolving the cross-wired MyStep. You are trying to resolve a cross-wired service, but are doing so outside the context of an active (Async Scoped) scope. To be able to resolve this service the operation must run in the context of such scope.

This is a console application using the GenericHost. I have tried configuring the var asyncScope = AsyncScopedLifestyle.BeginScope(_container) around several places, even host.RunAsync() to see if its possible for this invocation to be aware of the SimpleInjector scope, but to no avail.

The workflow methods all start an internal background service of some kind, and return immediately so any AsyncScopedLifestyle.BeginScope(_container) placed around them are short lived.

Alternative that was working that could probably be abstracted in a way is to inject the Container object, and resolve the dependancies "service locator" style. But I would rather find I way for this to work with cross wiring.

Thank you.

joskoanicic avatar Aug 25 '22 18:08 joskoanicic

I quickly went through Workflow Core's code base. What I found was that, contrary to (unfortunate) common practice, Workflow Core actually does have a decent amount of abstractions in place that would allow an application developer to adjust part of the library's pipeline. (kudos for @danielgerlag)

For instance, I found an IScopeProvider abstraction. Workflow Core uses this to create new MS.DI IServiceScope implementations. This might be your savior. You can replace WorkFlow Core's default IScopeProvider imeplementation with a custom one that wraps the created IServiceScope with a Simple Injector scope.

For instance:

public record SimpleInjectorScopeProvider(IServiceScopeFactory Factory, Container Container)
    : IScopeProvider
{
    public IServiceScope CreateScope(IStepExecutionContext context) =>
         new DisposableWrapper(
            AsyncScopedLifestyle.BeginScope(Container),
            Factory.CreateScope());
    
    private record DisposableWrapper(IDisposable Disposable, IServiceScope Scope) : IServiceScope
    {
        public IServiceProvider ServiceProvider => Scope.ServiceProvider;
        
        public void Dispose()
        {
            try
            {
                Scope.Dispose();
            }
            finally
            {
                Disposable.Dispose();
            }
        }
    }
}

You can replace the default one with your custom one as follows:

services.AddTransient<IScopeProvider, SimpleInjectorScopeProvider>();

Whether this exactly suits your needs, I can't tell. You will have to test this and perhaps @danielgerlag could feedback on this.

dotnetjunkie avatar Aug 25 '22 19:08 dotnetjunkie

Steven, as always you never cease to amaze me.

Thank you on this, I will test this and look further into the code of workflow-core.
I will let you know of the findings.

EDIT: This worked, container.GetInstance no longer throws an exception.

joskoanicic avatar Aug 25 '22 20:08 joskoanicic