2

I created a new .NET Core project and installed the packages GraphQL, GraphiQL and GraphQL.SystemTextJson.

When running the application this is what I get

enter image description here

Besides the exception message GraphiQL wasn't able to find a schema documentation.

First of all I have two entities, users and tasks.

public sealed class User
{
    public Guid Id { get; set; }
}

public sealed class Task
{
    public Guid Id { get; set; }
}

and both of them have their representing graph type

public sealed class UserType : ObjectGraphType<User>
{
    public UserType()
    {
        Name = nameof(User);
        Field(user => user.Id).Description("The user id.");
    }
}

public sealed class TaskType : ObjectGraphType<Task>
{
    public TaskType()
    {
        Name = nameof(Task);
        Field(task => task.Id).Description("The task id.");
    }
}

I created the query holding all the "actions" the client can execute

public sealed class GraphQLQuery : ObjectGraphType
{
    private readonly List<User> _users = new List<User>();
    private readonly List<Task> _tasks = new List<Task>();

    public GraphQLQuery()
    {
        Field<ListGraphType<UserType>>(
            "users",
            "Returns a collection of users.",
            resolve: context => _users);

        Field<ListGraphType<TaskType>>(
            "tasks",
            "Returns a collection of tasks.",
            resolve: context => _tasks);
    }
}

and register that query for the schema

public sealed class GraphQLSchema : GraphQL.Types.Schema
{
    public GraphQLSchema(GraphQLQuery graphQLQuery, IServiceProvider serviceProvider) : base(serviceProvider)
    {
        Query = graphQLQuery;
    }
}

In the startup file in the ConfigureServices I added this code to register all the required services before calling services.AddControllers()

serviceCollection
        .AddSingleton<IDocumentExecuter, DocumentExecuter>()
        .AddSingleton<IDocumentWriter, DocumentWriter>()
        .AddSingleton<ISchema, GraphQLSchema>()
        .AddSingleton<GraphQLQuery>()

and in the Configure method I call app.UseGraphiQl() at first.

The corresponding GraphQL request DTO

public sealed class GraphQLRequest
{
    public string OperationName { get; set; }
    public string Query { get; set; }

    [JsonConverter(typeof(ObjectDictionaryConverter))]
    public Dictionary<string, object> Variables { get; set; }
}

Lastly I implemented the API controller

[ApiController]
[Route("[controller]")]
public sealed class GraphQLController : Controller
{
    private readonly ISchema _schema;
    private readonly IDocumentExecuter _documentExecuter;

    public GraphQLController(ISchema schema, IDocumentExecuter documentExecuter)
    {
        _schema = schema;
        _documentExecuter = documentExecuter;
    }

    public async Task<IActionResult> Post([FromBody] GraphQLRequest graphQlRequest)
    {
        ExecutionOptions executionOptions = new ExecutionOptions()
        {
            Schema = _schema,
            Query = graphQlRequest.Query,
            Inputs = graphQlRequest.Variables?.ToInputs()
        };

        ExecutionResult executionResult = await _documentExecuter.ExecuteAsync(executionOptions);

        if (executionResult.Errors != null)
            return BadRequest(executionResult);

        return Ok(executionResult);
    }
}

Does someone know what's wrong here? I can't see problems like circular dependency etc.


When running the application the graphQlRequest contains the following values

  • OperationName: IntrospectionQuery
  • Query:

.

query IntrospectionQuery {
  __schema {
    queryType { name }
    mutationType { name }
    subscriptionType { name }
    types {
      ...FullType
    }
    directives {
      name
      description
      locations
      args {
        ...InputValue
      }
    }
  }
}

fragment FullType on __Type {
  kind
  name
  description
  fields(includeDeprecated: true) {
    name
    description
    args {
      ...InputValue
    }
    type {
      ...TypeRef
    }
    isDeprecated
    deprecationReason
  }
  inputFields {
    ...InputValue
  }
  interfaces {
    ...TypeRef
  }
  enumValues(includeDeprecated: true) {
    name
    description
    isDeprecated
    deprecationReason
  }
  possibleTypes {
    ...TypeRef
  }
}

fragment InputValue on __InputValue {
  name
  description
  type { ...TypeRef }
  defaultValue
}

fragment TypeRef on __Type {
  kind
  name
  ofType {
    kind
    name
    ofType {
      kind
      name
      ofType {
        kind
        name
        ofType {
          kind
          name
          ofType {
            kind
            name
            ofType {
              kind
              name
              ofType {
                kind
                name
              }
            }
          }
        }
      }
    }
  }
}

I migrated to .NET 5 now and get this error instead

enter image description here


I added a reproduction repository

https://github.com/olaf-svenson/graphql-net-reproduction

2 Answers2

0

It's a classic for Json resolvers. if you have navigation properties or properties that reference eachother.

Often this can be fixed by either returning a mapped result, or by adjusting the Json Serializer settings.

I'm unsure whether this is fixed in .Net Core 3.1 but you can to the startup.cs add

install Newtonsoft Jsonif you haven't already.

services
  .AddControllers()
  .AddNewtonsoftJson(options =>
  {
      options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
  });

hope this resolves it.

here's some article about the issue.

https://dotnetcoretutorials.com/2020/03/15/fixing-json-self-referencing-loop-exceptions/

.net core 3 not having ReferenceLoopHandling in AddJsonOptions

Bjarke Handsdal
  • 219
  • 1
  • 6
  • hey, thanks for your answer. Unfortunately I'm not using Newtonsoft. Since Microsoft provides it's own JSON lib now I installed the `GraphQL.SystemTextJson` package as described in the docs here https://github.com/graphql-dotnet/graphql-dotnet#installation –  Jan 07 '21 at 15:17
  • it's been a known issue before that system.text.json wasn't mature enough because of referenceloop handling error. Strongly recommend that you either update to .net core 5 then or swap out system.text.json with newtonsofts. :) btw: here's a link of microsofts own documentation their json serializer: https://learn.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-migrate-from-newtonsoft-how-to?pivots=dotnet-core-3-1 which even in 3.1 still doesnt appear to be supporting the referenceloop error – Bjarke Handsdal Jan 07 '21 at 16:07
  • hmm I migrated to .NET 5 but unfortunately still get an error .. (updated my question) I understand your reasons to use Newtonsoft but I would like to avoid external packages as a workaround .... :S –  Jan 08 '21 at 08:13
  • 1
    So it's no longer the referenceloop handling error. hurray! unfortunately now you've run into another unsupported feature in the system.text.json take a look at this registered issue for instance stating the same fact that system.text.json throws this exception. https://github.com/dotnet/runtime/issues/41920 Still my strongest recommendation would be to just switch to newtonsoft which have been around for ages and does the work. – Bjarke Handsdal Jan 08 '21 at 11:36
  • hmm :/ thanks. But I think my code is wrong because other people use this package too or at least someone tried it before releasing it? –  Jan 08 '21 at 11:57
0

Your error in .Net 5 related to unregistered graph types. If you enable all exceptions and disable "Just My Code" in debug settings you will see this error

System.InvalidOperationException: 'Required service for type API.GraphTypes.UserType not found'
This exception was originally thrown at this call stack:
    GraphQL.Utilities.ServiceProviderExtensions.GetRequiredService(System.IServiceProvider, System.Type) in ServiceProviderExtensions.cs
    GraphQL.Types.Schema.CreateTypesLookup.AnonymousMethod__68_1(System.Type) in Schema.cs
    GraphQL.Types.GraphTypesLookup.Create.AnonymousMethod__0(System.Type) in GraphTypesLookup.cs
    GraphQL.Types.GraphTypesLookup.AddTypeIfNotRegistered(System.Type, GraphQL.Types.TypeCollectionContext) in GraphTypesLookup.cs
    GraphQL.Types.GraphTypesLookup.HandleField(GraphQL.Types.IComplexGraphType, GraphQL.Types.FieldType, GraphQL.Types.TypeCollectionContext, bool) in GraphTypesLookup.cs
    GraphQL.Types.GraphTypesLookup.AddType(GraphQL.Types.IGraphType, GraphQL.Types.TypeCollectionContext) in GraphTypesLookup.cs
    GraphQL.Types.GraphTypesLookup.Create(System.Collections.Generic.IEnumerable<GraphQL.Types.IGraphType>, System.Collections.Generic.IEnumerable<GraphQL.Types.DirectiveGraphType>, System.Func<System.Type, GraphQL.Types.IGraphType>, GraphQL.Conversion.INameConverter, bool) in GraphTypesLookup.cs
    GraphQL.Types.Schema.CreateTypesLookup() in Schema.cs
    System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()

Adding UserType and TaskType to DI container solves this error.

Now, your original problem: you should use IDocumentWriter to write the response, you can't just serialize executionResult by returning Ok(executionResult). Use this code to write response (stolen from official graphql-dotnet/examples repo):

private async Task WriteResponseAsync(HttpContext context, ExecutionResult result)
{
    context.Response.ContentType = "application/json";
    context.Response.StatusCode = result.Errors?.Any() == true ? (int)HttpStatusCode.BadRequest : (int)HttpStatusCode.OK;

    await _documentWriter.WriteAsync(context.Response.Body, result);
}

The updated GraphQLController.cs

[ApiController]
[Route("[controller]")]
public sealed class GraphQLController : Controller
{
    private readonly ISchema _schema;
    private readonly IDocumentExecuter _documentExecuter;
    private readonly IDocumentWriter _documentWriter;

    public GraphQLController(ISchema schema, IDocumentExecuter documentExecuter, IDocumentWriter documentWriter)
    {
        _schema = schema;
        _documentExecuter = documentExecuter;
        _documentWriter = documentWriter;
    }

    public async Task Post([FromBody] GraphQLRequest graphQlRequest)
    {
        ExecutionOptions executionOptions = new ExecutionOptions()
        {
            Schema = _schema,
            Query = graphQlRequest.Query,
            Inputs = graphQlRequest.Variables?.ToInputs()
        };

        ExecutionResult executionResult = await _documentExecuter.ExecuteAsync(executionOptions);

        await WriteResponseAsync(HttpContext, executionResult);
    }

    private async Task WriteResponseAsync(HttpContext context, ExecutionResult result)
    {
        context.Response.ContentType = "application/json";
        context.Response.StatusCode = result.Errors?.Any() == true ? (int)HttpStatusCode.BadRequest : (int)HttpStatusCode.OK;
    
        await _documentWriter.WriteAsync(context.Response.Body, result);
    }
}
Artur
  • 4,595
  • 25
  • 38
  • 1
    And I'd recommend to use middleware as much simpler and cleaner way to use library. See this example: https://github.com/graphql-dotnet/examples/tree/master/src/AspNetCore – Artur Jan 08 '21 at 12:48
  • yes, the error is gone! But GraphiQL is not able to load a schema .. how can I add it? –  Jan 08 '21 at 14:18
  • Emm, I actually can see the schema in Docs after registering missing types and writing the response to with `IDocumentWriter`. I'll update the answer with Controller code – Artur Jan 08 '21 at 14:30