0

I am trying to upload an image file with graphql-dotnet, but it is never successful.

I am taking file object in my GraphQLController:

var files = this.Request.Form.Files;

var executionOptions = new ExecutionOptions 
{
    Schema = _schema,
    Query = queryToExecute,
    Inputs = inputs,
    UserContext = files,
    OperationName = query.OperationName
};

And here my Mutation:

Field<UserGraphType>(
    "uploadUserAvatar",
    Description="Kullanıcı resmi yükleme.",
    arguments: new QueryArguments(
        new QueryArgument<NonNullGraphType<IntGraphType>> { Name = "Id", Description = "Identity Alanı" }
    ),
    resolve: context => {
        var file = context.UserContext.As<IFormCollection>();
        var model = userService.UploadAvatar(context.GetArgument<int>("Id"),file);
        return true;
    }
);

I think it is accepting the only JSON. It is not accepting the request as a file type.

Also I am using React & apollo-client at the client-side. It has an error in the console:

Failed to load http://localhost:5000/graphql: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access. The response had HTTP status code 500. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

I am trying to send the query like this:

const { selectedFile,id } = this.state

this.props.uploadAvatar({
    variables: {id},
    file:selectedFile
}).then(result => {
    console.log(result);
});

What can I do to achieve this?

fubar
  • 16,918
  • 4
  • 37
  • 43
tcetin
  • 979
  • 1
  • 21
  • 48

1 Answers1

0

Failed to load http://localhost:5000/graphql: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8080' is therefore not allowed access.

This error means you need to enable CORS.

See these docs: https://learn.microsoft.com/en-us/aspnet/core/security/cors?view=aspnetcore-2.1

Essentially you need these two things:

services.AddCors();

app.UseCors(builder =>
   builder.WithOrigins("http://example.com"));

I would also suggest to look at this Deserializer helper function in the GraphQL.Relay project. It helps your server handle a multipart/form-data request. You can then use the parsed query information and files and pass it to the DocumentExecutor.

https://github.com/graphql-dotnet/relay/blob/master/src/GraphQL.Relay/Http/Deserializer.cs

public static class Deserializer
{
    public static async Task<RelayRequest> Deserialize(Stream body, string contentType)
    {
        RelayRequest queries;

        switch (contentType)
        {
            case "multipart/form-data":
                queries = DeserializeFormData(body);
                break;
            case "application/json":
                var stream = new StreamReader(body);
                queries = DeserializeJson(await stream.ReadToEndAsync());
                break;
            default:
                throw new ArgumentOutOfRangeException($"Unknown media type: {contentType}. Cannot deserialize the Http request");
        }

        return queries;
    }


    private static RelayRequest DeserializeJson(string stringContent)
    {
        if (stringContent[0] == '[')
            return new RelayRequest(
                JsonConvert.DeserializeObject<RelayQuery[]>(stringContent),
                isBatched: true
            );

        if (stringContent[0] == '{')
            return new RelayRequest() {
                JsonConvert.DeserializeObject<RelayQuery>(stringContent)
            };

        throw new Exception("Unrecognized request json. GraphQL queries requests should be a single object, or an array of objects");
    }

    private static RelayRequest DeserializeFormData(Stream body)
    {
        var form = new MultipartFormDataParser(body);

        var req = new RelayRequest()
        {
            Files = form.Files.Select(f => new HttpFile {
                ContentDisposition = f.ContentDisposition,
                ContentType = f.ContentType,
                Data = f.Data,
                FileName = f.FileName,
                Name = f.Name
            })
        };

        req.Add(new RelayQuery {
            Query = form.Parameters.Find(p => p.Name == "query").Data,
            Variables = form.Parameters.Find(p => p.Name == "variables").Data.ToInputs(),
        });

        return req;
    }
}
Joe McBride
  • 3,789
  • 2
  • 34
  • 38
  • Thanks for your suggestions. But I have an error about ContentType also. So it doesn't get the file cause of content-type in the GraphQLControlller.I am using apollo-client for client-side btw. – tcetin Jun 04 '18 at 21:19
  • Is it correct using to send the file from client side? – tcetin Jun 04 '18 at 22:04
  • You should probably be sending it as a multipart/form-data request. As far as I understand Apollo does not support that out of the box. See this article. https://medium.freecodecamp.org/how-to-manage-file-uploads-in-graphql-mutations-using-apollo-graphene-b48ed6a6498c – Joe McBride Jun 04 '18 at 22:12
  • Yes, I read this. But there is no graph type as `Upload!` in the dotnet-graphql. So I couldn't apply on the Apollo.It has mentioned about this at https://github.com/graphql-dotnet/graphql-dotnet/issues/419 – tcetin Jun 04 '18 at 22:14
  • Yes, file upload is not directly supported by Apollo or GraphQL. GraphQL is transport agnostic so you may not be running GraphQL over http. You will have to implement customizations in both Apollo and your server. – Joe McBride Jun 05 '18 at 16:16
  • So you mean we can't use dotnet-graphql on file upload? – tcetin Jun 06 '18 at 09:44
  • You _can_ use it. You have to send the request from your JavaScript (in your case Apollo) as multipart/form-data. You also have to handle that content-type in your GraphQL server code. Neither Apollo nor GraphQL for .NET provides that code for you. You have to write it yourself. – Joe McBride Jun 06 '18 at 14:37