Cannot reproduce in tests the exception Cannot consume scoped service from singleton outside Mvc framework

Viewed 76

The InvalidOperationException Cannot consume scoped service from singleton is a well-known scenario described very well here

I am investigating a way to reproduce this exception (assuming it comes from the Dependency Injection framework) but I am not succeeding.

I've created a repo with a commit to illustrate it but basically I have the following test:

public class Given_Scoped_Repository_And_Singleton_Service_That_Uses_The_Repository_When_Getting_Service_From_Different_Scope_After_Disposing_First_Scope
    : Given_When_Then_Test
{
    private IServiceScope _scopeOne;
    private IServiceScope _scopeTwo;
    private ServiceSample _serviceSampleOne;
    private ServiceSample _serviceSampleTwo;

    protected override void Given()
    {
        var serviceCollection =
            new ServiceCollection()
                .AddScoped<RepositorySample>()
                .AddSingleton<ServiceSample>()
                .BuildServiceProvider();

        _scopeOne = serviceCollection.CreateScope();
        _scopeTwo = serviceCollection.CreateScope();

        _serviceSampleOne = _scopeOne.ServiceProvider.GetService<ServiceSample>();

        _scopeOne.Dispose();
    }

    protected override void When()
    {
        _serviceSampleTwo = _scopeTwo.ServiceProvider.GetService<ServiceSample>();
    }

    [Fact]
    public void Then_It_Should_Get_The_Same_Service_Instance()
    {
        _serviceSampleOne.Should().Be(_serviceSampleTwo);
    }

    [Fact]
    public void Then_It_Should_Have_The_Same_Repository_Instance()
    {
        _serviceSampleOne.RepositorySample.Should().Be(_serviceSampleTwo.RepositorySample);
    }
}

and

class RepositorySample { }
class ServiceSample
{
    public RepositorySample RepositorySample { get; }

    public ServiceSample(RepositorySample repositorySample)
    {
        RepositorySample = repositorySample;
    }
}

I would expect to see that InvalidOperationException thrown because I am getting a singleton service from a different scope expecting the repository to be different (since it's scoped). These tests are not failing even if I explicitly dispose the first context that instantiated the scoped RepositorySample and I am a bit confused here.

  • Why the dependency injection framework Microsoft.Extensions.DependencyInjection 3.1.3 doesn't throw the exception alerting me of the singleton-scope trap?
  • If I dispose the first scope (as if it was a scoped DbContext in an Mvc scenario that should die when a response is issued), shouldn't this cause the RepositorySample instance to be disposed? It isn't, even when I have RepositorySample implement IDisposable I can see the scoped instance is never disposed (the Dispose method is not executed)
  • How could I design my test to see the exception as if it was happening with a scoped DbContext that is instantiated in a singleton service?
1 Answers

It's not throwing the exception because the option to validate scopes hasn't been specified.

Change this:

.BuildServiceProvider();

to this:

.BuildServiceProvider(new ServiceProviderOptions { ValidateScopes = true });

ServiceProviderOptions.ValidateScopes

true to perform check verifying that scoped services never gets resolved from root provider;

You can also perform the validation when the ServiceProvider is built instead of waiting until a service is resolved:

.BuildServiceProvider(
    new ServiceProviderOptions
    {
        ValidateScopes = true,
        ValidateOnBuild = true
    });

That's what an MVC app does at startup. That's why the exception is thrown at startup, not when a controller is resolved. It will throw that exception if it detects errors even if the registered dependencies will never be resolved at runtime. To see it, register your dependencies - ServiceSample, RepositorySample - as you did in your unit test, but don't inject them into any controllers. You'll still get the exception at startup when tries to build the ServiceProvider.

Related