【问题标题】:GraphQL models struggling with System.Text.Json.JsonException与 System.Text.Json.JsonException 苦苦挣扎的 GraphQL 模型
【发布时间】:2021-04-07 02:36:51
【问题描述】:

我创建了一个新的 .NET Core 项目并安装了包 GraphQL、GraphiQL 和 GraphQL.SystemTextJson。

当运行应用程序时,这就是我得到的

除了异常消息 GraphiQL 无法找到架构文档。

首先我有两个实体,用户和任务。

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

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

它们都有自己的表示图类型

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.");
    }
}

我创建了包含客户端可以执行的所有“操作”的查询

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);
    }
}

并为架构注册该查询

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

ConfigureServices的启动文件中我添加了这段代码来注册所有需要的服务,然后再调用services.AddControllers()

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

Configure 方法中,我首先调用app.UseGraphiQl()

对应的GraphQL请求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; }
}

最后我实现了 API 控制器

[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);
    }
}

有人知道这里出了什么问题吗?我看不到循环依赖等问题。


运行应用程序时,graphQlRequest 包含以下值

  • 操作名称:IntrospectionQuery
  • 查询:

.

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
              }
            }
          }
        }
      }
    }
  }
}

我现在迁移到 .NET 5 却得到了这个错误


我添加了一个复制库

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

【问题讨论】:

  • graphQlRequest 的值是多少?您可以将其值的 json 表示添加到您的问题中。
  • OfirD 我为我的问题添加了价值:)
  • 你能上传完整的 silution 到 Github 吗?如果人们可以克隆并使用您的存储库,那么提供帮助会更容易
  • 当然,等一下
  • @Artur 你可以在这里找到它github.com/olaf-svenson/graphql-net-reproduction

标签: c# graphql graphql-dotnet


【解决方案1】:

.Net 5 中的错误与未注册的图形类型有关。如果您在调试设置中启用所有异常并禁用“仅我的代码”,您将看到此错误

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()

UserTypeTaskType 添加到DI 容器可以解决此错误。

现在,您的原始问题:您应该使用IDocumentWriter 来编写响应,您不能通过返回Ok(executionResult) 来序列化executionResult。 使用此代码编写响应(从官方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);
}

更新后的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);
    }
}

【讨论】:

  • 我建议使用中间件作为使用库的更简单和更清洁的方式。请参阅此示例:github.com/graphql-dotnet/examples/tree/master/src/AspNetCore
  • 是的,错误消失了!但是 GraphiQL 无法加载架构 .. 我该如何添加它?
  • Emm,在注册缺失类型并使用IDocumentWriter 写入响应后,我实际上可以在 Docs 中看到架构。我会用控制器代码更新答案
【解决方案2】:

这是 Json 解析器的经典之作。如果您有导航属性或相互引用的属性。

通常可以通过返回映射结果或调整 Json 序列化器设置来解决此问题。

我不确定这是否在 .Net Core 3.1 中得到修复 但是你可以在startup.cs中添加

如果你还没有安装Newtonsoft Json

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

希望这能解决它。

这里有一些关于这个问题的文章。

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

.net core 3 not having ReferenceLoopHandling in AddJsonOptions

【讨论】:

  • 嘿,谢谢你的回答。不幸的是,我没有使用 Newtonsoft。由于 Microsoft 提供了它自己的 JSON 库,所以我现在安装了 GraphQL.SystemTextJson 包,如这里的文档中所述 github.com/graphql-dotnet/graphql-dotnet#installation
  • 在 system.text.json 由于引用循环处理错误而不够成熟之前,这是一个已知问题。强烈建议您要么更新到 .net core 5,要么将 system.text.json 换成 newtonsofts。 :) 顺便说一句:这是微软自己的文档的链接,他们的 json 序列化器:docs.microsoft.com/en-us/dotnet/standard/serialization/… 即使在 3.1 中似乎仍然不支持引用循环错误
  • 嗯,我迁移到了 .NET 5,但不幸的是仍然出现错误..(更新了我的问题)我了解您使用 Newtonsoft 的原因,但我想避免使用外部软件包作为解决方法.. :S
  • 所以它不再是参考循环处理错误。欢呼!不幸的是,现在您在 system.text.json 中遇到了另一个不受支持的功能,请查看这个已注册的问题,例如说明 system.text.json 引发此异常的相同事实。 github.com/dotnet/runtime/issues/41920 我最强烈的建议仍然是切换到已经存在多年并且可以正常工作的 newtonsoft。
  • 嗯:/谢谢。但是我认为我的代码是错误的,因为其他人也使用了这个包,或者至少有人在发布之前尝试过它?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-08-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多