6

I'm trying to build a mutation within relay that includes a file. As soon as I implement the getFiles() method referenced here: https://facebook.github.io/relay/docs/api-reference-relay-mutation.html#getfiles

Relay sends a multipart request causing a 415 error from ASP.NET Core MVC.

I'm looking for a working example, similar to "How would you do file uploads in a React-Relay app?" with the graphql-dotnet library.

Joe McBride
  • 3,584
  • 2
  • 30
  • 36
Jon Lebensold
  • 241
  • 1
  • 10

2 Answers2

4

The GraphQL Endpoint wasn't accepting the multi-part form mimetype because it wasn't JSON. I was able to work with the files once I got them into graphql-dotnet via the RootObject and the context that's available in the mutation. What was throwing me off was MVC.

So I wrote a simple Filter:

public class RelayResourceFilter : IResourceFilter
{
    private readonly string jsonMediaType = "application/json";

    public void OnResourceExecuted(ResourceExecutedContext context)
    {
    }

    public void OnResourceExecuting(ResourceExecutingContext context)
    {


        if (!string.Equals(MediaTypeHeaderValue.Parse(context.HttpContext.Request.ContentType).MediaType,
            this.jsonMediaType, StringComparison.OrdinalIgnoreCase))
        {
            var encoder = JavaScriptEncoder.Create();
            var variables = encoder.Encode(context.HttpContext.Request.Form["variables"]);
            var query = encoder.Encode(context.HttpContext.Request.Form["query"]);
            var body = $"{{\"query\":\"{query}\", \"variables\":\"{variables}\"}}";

            byte[] requestData = Encoding.UTF8.GetBytes(body);
            context.HttpContext.Request.Body = new MemoryStream(requestData);
            context.HttpContext.Request.ContentType = this.jsonMediaType;
        }
    }
}

registered it:

services.AddScoped<RelayResourceFilter>();

and then applied it like so in the Controller:

[ServiceFilter(typeof(RelayResourceFilter))]
    public async Task<ExecutionResult> Post([FromBody]GraphQLQuery query, bool? useErrorCode)
{
var files = this.Request.HasFormContentType ? this.Request.Form.Files : null;
// ... assignment to Root Object
}
Jon Lebensold
  • 241
  • 1
  • 10
3

Are you just having trouble figuring out how to access the files in your resolver on the server? You can pass the files as your rootObject or userContext.

// GraphQLController
var files = Request.Form.Files;
var userContext = files;

var result = await executer.ExecuteAsync(
    schema,
    rootObject,
    query,
    operationName,
    inputs,
    userContext).ConfigureAwait(false);

// Mutation type
Field<StringGraphType>(
  "uploadFile",
  arguments: new QueryArguments(new QueryArgument<NonNullGraphType<StringGraphType>> {Name = "fileName"}),
  resolve: context =>
  {
      var userContext = context.UserContext.As<IFormFileCollection>();

      // process files

      // return data
      return "success";
  });
Joe McBride
  • 3,584
  • 2
  • 30
  • 36
  • 1
    Thanks for the response Joe. I actually ended up writing a small ResourceFilter for the GraphQL endpoint: – Jon Lebensold Nov 11 '16 at 18:09
  • How I can read query from his? In general I am reading like this `var request = JsonConvert.DeserializeObject(body);`. Then `request` is object which contains query. If I am uploading file content is not `application/json`. – kat1330 Sep 06 '17 at 17:47
  • @kat1330 I would look at the accepted answer. You need to check the media type. See also https://github.com/graphql-dotnet/relay/blob/master/src/GraphQL.Relay/Http/Deserializer.cs – Joe McBride Sep 06 '17 at 18:22
  • @JoeMcBride Thanks. I prefer to use is middleware. Do you know which type is most suitable for mutation `QueryArgument` for file upload? – kat1330 Sep 06 '17 at 23:08
  • @kat1330 I'm not sure I fully understand your question (perhaps add an issue on the GraphQL .NET project with more info?) - but I think you're asking how you pass files as Query Arguments. Short answer is you can't. You'll have to access them via the User Context. – Joe McBride Sep 07 '17 at 16:18