fairybread: Cannot resolved scope service ... from root provider

Describe the bug I receive the following error when performing a mutation with a validator attached. image

Removing .AddFairyBread() fixes the error but that’s obviously not the desired result.

Hot Chocolate setup:

services
    .AddGraphQLServer()
    .AddQueryType()
        .AddTypeExtension<UserQueries>()
        .AddTypeExtension<ClassroomQueries>()
    .AddMutationType()
        .AddTypeExtension<ClassroomMutations>()
        .AddTypeExtension<AuthMutations>()
    .AddType<UserType>()
    .AddType<ClassroomType>()
    .EnableRelaySupport()
    .AddDataLoader<UserByIdDataLoader>()
    .AddDataLoader<ClassroomByIdDataLoader>()
    .AddAuthorization()
    .AddFairyBread();

Services:

services.AddPooledDbContextFactory<ApplicationDbContext>(opt => {
    opt.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"));
    opt.UseSnakeCaseNamingConvention();
});
services.AddScoped(p =>
    p.GetRequiredService<IDbContextFactory<ApplicationDbContext>>()
        .CreateDbContext());
services.AddValidatorsFromAssemblyContaining<Startup>();
services.AddHotChocolateServices();
services.AddIdentityServices();

Versions:

  • FairyBread: v6.0.0
  • HotChocolate: v11.1.0
  • FluentValidation: v10.0.4

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 20 (11 by maintainers)

Commits related to this issue

Most upvoted comments

I’ve tested this today both in my own reproducible, and in our development environment - and everything checks out.

Smooth work @benmccallum - tricky nut this one!

So I think this is the issue:

https://github.com/benmccallum/fairybread/blob/76c39cc4f5ca0b792a37f955636449a255ce12a8/src/FairyBread/IRequestExecutorBuilderExtensions.cs#L21-L22

By registering DefaultValidatorProvider as singleton and passing in sp as the first argument, you’re passing the root service provider, rather than the service provider that’s for the current http request. When you try and resolve validators inside GetValidators, they end up being instantiated by the root service provider, which is causing the error you’re seeing (because validators are registered as scoped, but you’re resolving them from a non-scoped service provider).

You’ll need to make the DefaultValidatorProvider scoped instead, and then I think this should resolve the problem (although I think you may want to split this up into 2 services, as you wouldn’t want the assembly scanning to happen on every request).

Note that even with older versions (where validators were Transient) this could’ve still been a problem if a user injected a request scoped service into a transient validator - you’d have ended up with the same issue. Basically, when it comes to dealing with services in aspnet, singleton scope should be avoided for anything that either could have dependencies injected into it, or calls into the service provider directly. (For example, the example in the readme of injecting a dbcontext would be problematic too, assuming the dbcontext is request-scoped)

Hope that helps clarify what’s going on, give me a shout if you need any more info.

Edit: If you want to detect & catch this behaviour inside your integration tests, take a look at what we do when we spin up the web host: https://github.com/FluentValidation/FluentValidation/blob/5c43678a217d5713cde5a0f716e9b46e23c72f21/src/FluentValidation.Tests.AspNetCore/WebAppFixture.cs#L17-L21

Awesome. Thanks for the confirmation, and for the reproduction!

@rune-vikestad I’m encountering the exact same problem in 6.0.1-preview.1. First time, everything works, but running the mutation a second time causes this error:

{
  "errors": [
    {
      "message": "Unexpected Execution Error",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "sendVerificationSms"
      ],
      "extensions": {
        "message": "Cannot access a disposed object.\r\nObject name: 'IServiceProvider'.",
        "stackTrace": "   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()\r\n   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)\r\n   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)\r\n   at FairyBread.DefaultValidatorProvider.GetValidators(IMiddlewareContext context, IInputField argument)+MoveNext()\r\n   at FairyBread.InputValidationMiddleware.InvokeAsync(IMiddlewareContext context)\r\n   at HotChocolate.Utilities.MiddlewareCompiler`1.ExpressionHelper.AwaitTaskHelper(Task task)\r\n   at HotChocolate.Execution.Processing.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)\r\n   at HotChocolate.Execution.Processing.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)"
      }
    }
  ]
}

I’m having issues with 6.0.1-preview.1 after upgrading FairyBread in https://github.com/rune-vikestad/fairybread-dependencyinjection-bug

The createFoo mutation returns as expected the first time i run it, but running it a second time yields the following error;

{
    "errors": [
        {
            "message": "Unexpected Execution Error",
            "locations": [
                {
                    "line": 2,
                    "column": 5
                }
            ],
            "path": [
                "createFoo"
            ],
            "extensions": {
                "message": "Cannot access a disposed object.\r\nObject name: 'IServiceProvider'.",
                "stackTrace": "   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException()\r\n   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)\r\n   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)\r\n   at FairyBread.DefaultValidatorProvider.GetValidators(IMiddlewareContext context, IInputField argument)+MoveNext()\r\n   at FairyBread.InputValidationMiddleware.InvokeAsync(IMiddlewareContext context)\r\n   at HotChocolate.Utilities.MiddlewareCompiler`1.ExpressionHelper.AwaitTaskHelper(Task task)\r\n   at HotChocolate.Execution.Processing.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)\r\n   at HotChocolate.Execution.Processing.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)"
            }
        }
    ]
}