.net core GraphQL, GraphQL.SystemTextJson: Serialization and deserialization of 'System.Type' instances are not supported
Asked Answered
M

5

24

In a ASP.NET core 5 application, I use GraphQL with GraphQL.SystemTextJson. When I attempt to return a result, I get s System.NotSupportedException saying "Serialization and deserialization of 'System.Type' instances are not supported and should be avoided since they can lead to security issues.".

I suspect something to be missing in the configuration of DocumentWriter.

It is configured like this in ConfigureServices:

    public void ConfigureServices(IServiceCollection services)
    {

        services.AddControllers();

        ...

        services.AddScoped<IDocumentWriter, DocumentWriter>();

Any suggestion?

Update:

for completeness, as asked by @AndrewSilver, I report the whole code (adapted from https://www.red-gate.com/simple-talk/dotnet/net-development/building-and-consuming-graphql-api-in-asp-net-core-3-1/ and ported to .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);
    }
}

And this is a sample request that triggers the error:

query {
  author (id: 1){
    id
    firstName
    lastName
  }
  blogs
    {
      id
      title
      content
    }
}
Midkiff answered 2/4, 2021 at 12:55 Comment(2)
Do you have DataSet or DataTable in your code? You can look into this issue github.com/dotnet/runtime/issues/41920. Maybe this is the reason. Anyway, I think it's hard to say something specific without more detailsEldred
@AndrewSilver, I don't use either DataSet, nor DataTable. for completeness, I updated the question, reporting the whole code.Midkiff
M
13

I solved creating a custom 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);
    }
}

Then, in configureServices:

        services.AddControllers()
            .AddJsonOptions(options =>
            {
                options.JsonSerializerOptions.WriteIndented = true;
                options.JsonSerializerOptions.Converters.Add(new CustomJsonConverterForType());
            });
Midkiff answered 6/4, 2021 at 6:22 Comment(3)
Worked for me thank you.Cinematography
Getting 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 on this oneDaradarach
@PriteshAcharya this is because your object may have cycles (like self-references). You need to ensure your object has not got any cycle, as they lead to infinite loops in de/serializationMidkiff
A
3

Instead of using System.Text.Json.JsonSearializer Use NewtonSoft.JsonConvert.SearializeObject

Aright answered 7/12, 2022 at 10:35 Comment(1)
This also worked for me - Newtonsoft seems to Serialze/Deserialize without any issuesWeighbridge
D
2

I fixed that problem by using the snippet shown in the docs: 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);
  }
}
Dissection answered 8/4, 2021 at 9:50 Comment(3)
Yep. This actually solves. Minor issues: the method should return Task; the snippet should be: HttpContext.Response.ContentType = "application/json"; HttpContext.Response.StatusCode = result.Errors?.Any() == true ? (int)HttpStatusCode.BadRequest : (int)HttpStatusCode.OK; var writer = new GraphQL.SystemTextJson.DocumentWriter(); and there should be no code afterwardsMidkiff
I expected DocumentWriter to be injected as defined in ConfigureServices, though.Midkiff
Well I guess you can inject it in the controller. Apparently you have to explicitely invoke DocumentWriter.Write, whereas all the documentation out there leads one to believe it's implicit in the call to DocumentExecutor.Dissection
P
0

Try install Microsoft.AspNetCore.Mvc.NewtonsoftJson and ConfigureServices like this:

builder.Services.AddControllers().AddNewtonsoftJson();
Peritonitis answered 10/12, 2023 at 6:29 Comment(0)
V
-3

In your startup.cs, in ConfigureServices

Add AddNewtonsoftJson() after AddControllers()

services.AddControllers().AddNewtonsoftJson();

Vogul answered 19/10, 2021 at 12:7 Comment(1)
This is not an actual solution as it doesn't solve OP's problem by using System.Text.Json, it just provides an alternative.Flowage

© 2022 - 2024 — McMap. All rights reserved.