dadhi / DryIoc

DryIoc is fast, small, full-featured IoC Container for .NET
MIT License
1.01k stars 123 forks source link

Scope is lost in IResolver inside scope because of singleton #580

Closed Ditriksss closed 8 months ago

Ditriksss commented 1 year ago

Hello!

Assumption: We have service singleton and service transient. Both of them have service A which has IResolver.

We've found that if service singleton is resolved before scope, scope context is lost for IResolver inside scope. We assumed that if service A is registered as a transient then there is no issue to inject it to singleton then to scope.

We are getting following error: DryIoc.ContainerException: 'code: Error.NoCurrentScope; message: No current scope is available - probably you are registering to or resolving from outside of the scope. It also may be because of the scoped dependency has singletons in its parent chain so the dependency is resolved from the root container resolver. Current resolver context is: container without scope.'

Could you eleborate why this error occur ?

Code to repeat this error:

public class ServiceInjecting
    {
        [Fact]
        public void TestDryIoc()
        {
            var container = new Container();

            container.Register<ServiceSingleton>(reuse: Reuse.Singleton);
            container.Register<ServiceTransient>(reuse: Reuse.Transient);
            container.Register<ServiceResolver>(reuse: Reuse.Transient);
            container.Register<ServiceScoped>(reuse: Reuse.Scoped);

            var singleton = container.Resolve<ServiceSingleton>();

            var scope = container.OpenScope();

            var transient = scope.Resolve<ServiceTransient>();
            transient.Do();

            scope.Dispose();
        }

        public class ServiceSingleton
        {
            private readonly ServiceResolver _serviceResolver;

            public ServiceSingleton(ServiceResolver serviceResolver)
            {
                _serviceResolver = serviceResolver;
            }
        }

        public class ServiceTransient
        {
            private readonly ServiceResolver _serviceResolver;

            public ServiceTransient(ServiceResolver serviceResolver)
            {
                _serviceResolver = serviceResolver;
            }

            public void Do()
            {
                _ = _serviceResolver.GetService<ServiceScoped>();
            }
        }

        public class ServiceResolver
        {
            private readonly IResolver _resolver;

            public ServiceResolver(IResolver resolver)
            {
                _resolver = resolver;
            }

            public TService GetService<TService>()
            {
                return _resolver.Resolve<TService>();
            }
        }

        public class ServiceScoped
        {

        }
    }
dadhi commented 1 year ago

@Ditriksss Thanks for the test, will check a bit later.

dadhi commented 1 year ago

@Ditriksss Yeah, something strange - probably a bug. When you omit the var singleton = container.Resolve<ServiceSingleton>();, everything working as it should.

But when the ServiceResolver is injected into the singleton first, for some reason it is cached as bound to the root container, messing up the resolution in the transient.

I will look for the fix.

dadhi commented 10 months ago

@Ditriksss Sorry for the late answer, but the problem is the cache in this case, and I believe it's hard to solve in the current version. But for now you may use the workaround registering the ServiceResolver as:

container.Register<ServiceResolver>(setup: Setup.With(useParentReuse: true));

Basically, it means that ServiceResolver reuse will be dependent on the parent service and so it won't be cached.

I'll keep the issue opened to see if there is any better solution in v6.0.