.net 核心 GraphQL、GraphQL.SystemTextJson:不支持“System.Type”实例的序列化和反序列化

Posted

技术标签:

【中文标题】.net 核心 GraphQL、GraphQL.SystemTextJson:不支持“System.Type”实例的序列化和反序列化【英文标题】:.net core GraphQL, GraphQL.SystemTextJson: Serialization and deserialization of 'System.Type' instances are not supported 【发布时间】:2021-06-29 08:18:35 【问题描述】:

在 ASP.NET core 5 应用程序中,我将 GraphQL 与 GraphQL.SystemTextJson 一起使用。 当我尝试返回结果时,我收到 System.NotSupportedException 说“'System.Type' 实例的序列化和反序列化不受支持,应该避免,因为它们可能导致安全问题。”。

我怀疑 DocumentWriter 的配置中缺少某些内容。

在ConfigureServices中是这样配置的:

    public void ConfigureServices(IServiceCollection services)
    

        services.AddControllers();

        ...

        services.AddScoped<IDocumentWriter, DocumentWriter>();

有什么建议吗?

更新:

为了完整起见,正如@AndrewSilver 所要求的,我报告了整个代码(改编自https://www.red-gate.com/simple-talk/dotnet/net-development/building-and-consuming-graphql-api-in-asp-net-core-3-1/ 并移植到.net core 5.0)。

    public class Startup

    public Startup(IConfiguration configuration)
    
        Configuration = configuration;
    

    public IConfiguration Configuration  get; 

    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    

        services.AddControllers();
        services.AddSwaggerGen(c =>
        
            c.SwaggerDoc("v1", new OpenApiInfo  Title = "GraphQlExperiments", Version = "v1" );
        );

        services.AddScoped<IDocumentExecuter, DocumentExecuter>();
        services.AddScoped<IDocumentWriter, DocumentWriter>();
        services.AddScoped<AuthorService>();
        services.AddScoped<AuthorRepository>();
        services.AddScoped<AuthorQuery>();
        services.AddScoped<AuthorType>();
        services.AddScoped<BlogPostType>();
        services.AddScoped<ISchema, GraphQLDemoSchema>();
        services.AddControllers();
    

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    
        if (env.IsDevelopment())
        
            app.UseDeveloperExceptionPage();
            app.UseSwagger();
            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "GraphQlExperiments v1"));
        


        // See: https://github.com/JosephWoodward/graphiql-dotnet
        app.UseGraphiQl("/graphiql", "/api/v1/graphql");


        app.UseHttpsRedirection();

        app.UseRouting();

        app.UseAuthorization();

        app.UseEndpoints(endpoints =>
        
            endpoints.MapControllers();
        );
    


public class Author

    public int Id  get; set; 
    public string FirstName  get; set; 
    public string LastName  get; set; 


public class BlogPost

    public int Id  get; set; 
    public string Title  get; set; 
    public string Content  get; set; 
    public Author Author  get; set; 


public class AuthorType : ObjectGraphType<Author>

    public AuthorType()
    
        Name = "Author";
        Field(_ => _.Id).Description("Author's Id.");
        Field(_ => _.FirstName).Description("First name of the author");
        Field(_ => _.LastName).Description("Last name of the author");
    


public class BlogPostType : ObjectGraphType<BlogPost>

    public BlogPostType()
    
        Name = "BlogPost";
        Field(_ => _.Id, type:
        typeof(IdGraphType)).Description("The Id of the Blog post.");
        Field(_ => _.Title).Description("The title of the blog post.");
        Field(_ => _.Content).Description("The content of the blog post.");
    


public class AuthorQuery : ObjectGraphType

    public AuthorQuery(AuthorService authorService)
    
        int id = 0;
        Field<ListGraphType<AuthorType>>(
            name: "authors",
            resolve: context =>
            
                return authorService.GetAllAuthors();
            );
        Field<AuthorType>(
            name: "author",
            arguments: new QueryArguments(new QueryArgument<IntGraphType>  Name = "id" ),
            resolve: context =>
            
                id = context.GetArgument<int>("id");
                return authorService.GetAuthorById(id);
            
        );
        Field<ListGraphType<BlogPostType>>(
            name: "blogs",
            arguments: new QueryArguments(new QueryArgument<IntGraphType>  Name = "id" ),
            resolve: context =>
            
                return authorService.GetPostsByAuthor(id);
            
        );
    


public class GraphQLQueryDTO

    public string OperationName  get; set; 
    public string NamedQuery  get; set; 
    public string Query  get; set; 
    public string Variables  get; set; 


public class GraphQLDemoSchema : Schema, ISchema

    public GraphQLDemoSchema(IServiceProvider resolver) : base(resolver)
    
        Query = resolver.GetService<AuthorQuery>();
    


public class AuthorService

    private readonly AuthorRepository _authorRepository;

    public AuthorService(AuthorRepository
            authorRepository)
    
        _authorRepository = authorRepository;
    
    public List<Author> GetAllAuthors()
    
        return _authorRepository.GetAllAuthors();
    
    public Author GetAuthorById(int id)
    
        return _authorRepository.GetAuthorById(id);
    
    public List<BlogPost> GetPostsByAuthor(int id)
    
        return _authorRepository.GetPostsByAuthor(id);
    


public class AuthorRepository

    private readonly List<Author> authors = new List<Author>();
    private readonly List<BlogPost> posts = new List<BlogPost>();

    public AuthorRepository()
    
        Author author1 = new Author
        
            Id = 1,
            FirstName = "Joydip",
            LastName = "Kanjilal"
        ;
        Author author2 = new Author
        
            Id = 2,
            FirstName = "Steve",
            LastName = "Smith"
        ;
        BlogPost csharp = new BlogPost
        
            Id = 1,
            Title = "Mastering C#",
            Content = "This is a series of articles on C#.",
            Author = author1
        ;
        BlogPost java = new BlogPost
        
            Id = 2,
            Title = "Mastering Java",
            Content = "This is a series of articles on Java",
            Author = author1
        ;
        posts.Add(csharp);
        posts.Add(java);
        authors.Add(author1);
        authors.Add(author2);
    
    public List<Author> GetAllAuthors()
    
        return this.authors;
    
    public Author GetAuthorById(int id)
    
        return authors.Where(author => author.Id == id).FirstOrDefault<Author>();
    
    public List<BlogPost> GetPostsByAuthor(int id)
    
        return posts.Where(post => post.Author.Id == id).ToList<BlogPost>();
    


    [Route("/api/v1/graphql")]
public class GraphQLController : Controller

    private readonly ISchema _schema;
    private readonly IDocumentExecuter _executer;
    public GraphQLController(
        ISchema schema,
        IDocumentExecuter executer
        )
    
        _schema = schema;
        _executer = executer;
    

    [HttpPost]
    public async Task<IActionResult> Post([FromBody] GraphQLQueryDTO query)
    
        var result = await _executer.ExecuteAsync(_ =>
        
            _.Schema = _schema;
            _.Query = query.Query;
            _.Inputs = query.Variables?.ToInputs();
        );
        if (result.Errors?.Count > 0)
        
            return BadRequest();
        
        return Ok(result.Data);
    

这是一个触发错误的示例请求:

query 
  author (id: 1)
    id
    firstName
    lastName
  
  blogs
    
      id
      title
      content
    

【问题讨论】:

您的代码中有DataSet 或DataTable 吗?您可以查看此问题github.com/dotnet/runtime/issues/41920。也许这就是原因。无论如何,我认为没有更多细节很难说具体的事情 @AndrewSilver,我既不使用 DataSet,也不使用 DataTable。为了完整起见,我更新了问题,报告了整个代码。 【参考方案1】:

我解决了创建自定义 JsonConverter 的问题:

public class CustomJsonConverterForType : JsonConverter<Type>

    public override Type Read(
        ref Utf8JsonReader reader,
        Type typeToConvert,
        JsonSerializerOptions options
        )
    
        // Caution: Deserialization of type instances like this 
        // is not recommended and should be avoided
        // since it can lead to potential security issues.

        // If you really want this supported (for instance if the JSON input is trusted):
        // string assemblyQualifiedName = reader.GetString();
        // return Type.GetType(assemblyQualifiedName);
        throw new NotSupportedException();
    

    public override void Write(
        Utf8JsonWriter writer,
        Type value,
        JsonSerializerOptions options
        )
    
        string assemblyQualifiedName = value.AssemblyQualifiedName;
        // Use this with caution, since you are disclosing type information.
        writer.WriteStringValue(assemblyQualifiedName);
    

然后,在 configureServices 中:

        services.AddControllers()
            .AddJsonOptions(options =>
            
                options.JsonSerializerOptions.WriteIndented = true;
                options.JsonSerializerOptions.Converters.Add(new CustomJsonConverterForType());
            );

【讨论】:

为我工作谢谢。 在这个上获得System.Text.Json.JsonException: A possible object cycle was detected. This can either be due to a cycle or if the object depth is larger than the maximum allowed depth of 32 @PriteshAcharya 这是因为您的对象可能有循环(如自引用)。您需要确保您的对象没有任何循环,因为它们会导致反序列化中的无限循环【参考方案2】:

我使用文档中显示的 sn-p 解决了这个问题:https://graphql-dotnet.github.io/docs/migrations/migration3

[HttpPost]
public async Task<IActionResult> Post([FromBody] GraphQLQueryDTO query)

  var result = await _executer.ExecuteAsync(_ =>
  
     _.Schema = _schema;
     _.Query = query.Query;
     _.Inputs = query.Variables?.ToInputs();
  );

  /* ----------- Added this ---------------------------------*/
  HttpContext.Response.ContentType = "application/json";
  HttpContext.Response.StatusCode = 200; // OK
  var writer = new GraphQL.SystemTextJson.DocumentWriter();
  await writer.WriteAsync(HttpContext.Response.Body, result);*
  /* ------------------------------------------------------*/

  if (result.Errors?.Count > 0)
  
    return BadRequest();
  
    return Ok(result.Data);
  

【讨论】:

是的。这实际上解决了。小问题:方法应该返回任务; sn-p 应该是: HttpContext.Response.ContentType = "application/json"; HttpContext.Response.StatusCode = result.Errors?.Any() == true ? (int)HttpStatusCode.BadRequest : (int)HttpStatusCode.OK; var writer = new GraphQL.SystemTextJson.DocumentWriter();之后应该没有代码 不过,我希望 DocumentWriter 能够按照 ConfigureServices 中的定义进行注入。 好吧,我想你可以将它注入控制器。显然你必须显式调用 DocumentWriter.Write,而所有的文档都让人相信它隐含在对 DocumentExecutor 的调用中。【参考方案3】:

在您的 startup.cs 中,在 ConfigureServices 中

在 AddControllers() 之后添加 AddNewtonsoftJson()

services.AddControllers().AddNewtonsoftJson();

【讨论】:

这不是一个实际的解决方案,因为它没有通过使用 System.Text.Json 解决 OP 的问题,它只是提供了一种替代方案。

以上是关于.net 核心 GraphQL、GraphQL.SystemTextJson:不支持“System.Type”实例的序列化和反序列化的主要内容,如果未能解决你的问题,请参考以下文章

2021.NET Conf China上的GraphQL

TypeError:无法读取未定义的属性“类型”

.Net Core 上的 GraphQL、MongoDB - 中间件?

.NET 遇上 GraphQL使用 Hot Chocolate 构建 GraphQL 服务

如何使用 GraphQL.Net 和 c# 将集合传递给 GraphQL 突变?

GraphQL in ASP.NET Core 系列视频 - GraphQL简介