Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
    }
}
like image 770
Starnuto di topo Avatar asked Apr 02 '21 12:04

Starnuto di topo


2 Answers

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());
            });
like image 80
Starnuto di topo Avatar answered Oct 02 '22 15:10

Starnuto di topo


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);
  }
}
like image 36
codemeat Avatar answered Oct 02 '22 14:10

codemeat