Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing GraphQL (GraphQL for .Net) in ASP.NET Core, why is my Query:ObjectGraphType class not being registered by dependency injection?

I've setup a basic Web Api demonstrate the use of GraphQL using ASP.Net Core. I've Followed a tutorial, what feels like exactly but am getting an error I don't understand.

I'm using GraphQL for .NET v2.4.0

This is the error:

System.InvalidOperationException: No service for type 'Land.GraphQL.Queries.LandQuery' has been registered.
  at at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
  at at GraphQL.FuncDependencyResolver.Resolve(Type type)
  at at GraphQL.FuncDependencyResolver.Resolve[T]()
  at Land.GraphQL.LandSchema..ctor(IDependencyResolver resolver) in .../LandSchema.cs:11

I'd be grateful for any light shed :)

Here's the code:

I created a LandType:ObjectGraphType to define the Type:

    public class LandType : ObjectGraphType<Entities.Land>
    {
        public LandType(ILandDataAccess landDataAccess)
        {
            Name = "Land";

            Field(land => land.Id, type: typeof(IdGraphType)).Description("Land Id in LandApi context");
            Field(land => land.Apn)
                .Description(
                    "Assessor's Parcel Number (APN) is a unique number that is assigned to each tract of land in a county by the Tax Assessor.");
            Field(land => land.Address).Description("");
            Field(land => land.ZipCode).Description("");
            Field(land => land.City).Description("");
            Field(land => land.County).Description("");
            Field(land => land.State).Description("");
            Field(land => land.Country).Description("");
            Field(land => land.GisNumber).Description("");
            Field(land => land.AssessedValue).Description("");
            Field(land => land.LegalDescription).Description("");
            Field(land => land.Acreage, type: typeof(FloatGraphType)).Description("Acreage of Land");

        }
    }

I created a LandQuery:ObjectGraphType to define the Query:

public class LandQuery : ObjectGraphType
    {
        public LandQuery(ILandDataAccess dataAccess)
        {
            Field<ListGraphType<LandType>>(
                "Land", 
                resolve: context => dataAccess.GetLandsAsync());
        }
    }

I created a LandSchema:Schema to define the Schema:

public class LandSchema : Schema
    {
        public LandSchema(IDependencyResolver resolver) : base(resolver)
        {
            Query = resolver.Resolve<LandQuery>();
        }
    }

I added the service and middleware to the Startup file:

public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(
                s.GetRequiredService));

            services.AddScoped<LandSchema>();

            services.AddGraphQL(o => { o.ExposeExceptions = true; })
                .AddGraphTypes(ServiceLifetime.Scoped);
        }

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
                app.UseGraphQL<LandSchema>();
        }

Edit:

Thanks to the commenters @NateBarbettini and @TonyNgo inspired me to find the answer. Turns out .AddGraphTypes only searches the calling assembly. My GraphTypes are stored in a referenced Assembly. passing the referenced assembly fixed the problem: .AddGraphTypes(typeof(LandSchema).Assembly, ServiceLifetime.Scoped);

like image 247
teaMonkeyFruit Avatar asked Sep 18 '19 03:09

teaMonkeyFruit


2 Answers

I think you are missing this line of code in your ConfigureServices.

public void ConfigureServices (IServiceCollection services) {
    services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver (
        s.GetRequiredService));

    services.AddScoped<LandSchema>();
    services.AddGraphQL(x => {
            x.ExposeExceptions = true; //set true only in development mode. make it switchable.
        })
        .AddGraphTypes (ServiceLifetime.Scoped);
}

If that is not the case you can read my blog here

like image 67
Tony Ngo Avatar answered Oct 25 '22 17:10

Tony Ngo


GraphQL.NET requires you to explicitly register a lot of things. Quick answer: you need to register LandQuery.

You have:

services.AddScoped<IDependencyResolver>(s => new FuncDependencyResolver(s.GetRequiredService));

The IDependencyResolver acts as a "glue" between the GraphQL.NET system (which needs to request a lot of service types) and ASP.NET Core's service collection (which contains the service types).

The object graph that you're giving to GraphQL.NET starts with LandSchema, which is correctly registered:

services.AddScoped<LandSchema>();

But look at what LandSchema is doing!

Query = resolver.Resolve<LandQuery>();

This line asks the IDependencyResolver for a LandQuery service type. Your FuncDependencyResolver is pointing at the ASP.NET Core service collection, so this will succeed if LandQuery is registered. It isn't, which is why you are getting the error

No service for type 'Land.GraphQL.Queries.LandQuery' has been registered.

To fix it, add a few new lines to ConfigureServices:

services.AddScoped<LandQuery>();
services.AddScoped<LandType>();

Any type or service (including ILandDataAccess) required by one of the GraphQL types will need to be registered in ConfigureServices. If you don't want to hand-register every single GraphQL.NET type, use Scrutor to auto-register types that derive from GraphType:

// Add all classes that represent graph types
services.Scan(scan => scan
    .FromAssemblyOf<LandQuery>()
    .AddClasses(classes => classes.AssignableTo<GraphQL.Types.GraphType>())
        .AsSelf()
        .WithSingletonLifetime());
like image 34
Nate Barbettini Avatar answered Oct 25 '22 18:10

Nate Barbettini