0

I am trying to upload a file from a react front end to a C# backend. I am using drop zone to get the file and then I call an api helper to post the file but I am getting different errors when I try different things. I am unsure exactly what the headers should be and exactly what I should send but I get two distinct errors. If I do not set the content-type I get 415 (Unsupported Media Type) error. If I do specify content type as multipart/form-data I get a 500 internal server error. I get the same error when the content-type is application/json. The url is being past in and I am certain it is correct. I am unsure if the file should be appended as file[0][0] as I have done or as file[0] as it is an array but I believe it should be the first. Any suggestions welcome :) Here is my api post helper code:

export const uploadAdminFile = (file, path, method = 'POST', resource = 
config.defaultResource) => {
const url = createUrl(resource, path);

const data = new FormData();


data.append('file', file[0][0]);
data.append('filename', file[0][0].name);

const request = accessToken =>
fetch(
  url,
  {
    method,
    mode: 'cors',
    withCredentials: true,
    processData: false,
    headers: {
      Accept: 'application/json',
      'Content-Type': 'application/json', //'multipart/form-data',
      Authorization: `Bearer ${accessToken}`,
    },
    body: data,
  })
  .then(res => res.json())
  .then(success => console.log('API HELPER: file upload success: ', success)
    .catch(err => console.log('API HELPER: error during file upload: ', err)));


return sendRequest(request, resource);

};

cbutler
  • 1,111
  • 5
  • 16
  • 32
  • "I get a 500 internal server error."...and did you investigate the cause of that error?It means the server-side code crashed somehow. You need to debug the server code, examine logs etc to find out what the actual fault was. – ADyson Sep 04 '18 at 09:11
  • Thank you, I understand that. I do not have access to do that at the moment so I am asking if someone can help me validate / verify, confirm or deny that what I am doing is "correct" / if this should work on the front end or if there is something I am clearly doing wrong – cbutler Sep 04 '18 at 09:19
  • if you're trying to upload a file then certainly the content type would not be JSON. – ADyson Sep 04 '18 at 09:21
  • However, a bigger problem seems to be that the `init` property in the fetch function is not expecting most of the property names you've used there. See https://stackoverflow.com/questions/52162745/upload-excel-file-from-react-to-c-sharp-asp-net-core-backend?noredirect=1#comment91275281_52162745 . Are you getting it confused with jQuery's $.ajax (http://api.jquery.com/jquery.ajax/) - they look like the jQuery style options to me. Also `method` should be `method: "POST"` or something. A value on its own is useless without a name. – ADyson Sep 04 '18 at 09:22
  • They just use default values so I don't have to explicitly send them in. And with ES^ syntax you can shorten the key: value down to just one thing if the names are the same so that is the same as method : method, – cbutler Sep 04 '18 at 09:32
  • You're not using the default values for processData or withCredentials (although that's not directly a root option anyway so it's wrong, look it up) or method (jquery style options), nor for mode (fetch style option). You're including quite a few non-default settings there, but you're mixing and matching options from two different libraries, so some just aren't recognised by the fetch function and won't be applied to the generated HTTP request. Decide which library you want to use and then set the required options correctly according to the appropriate documentation. – ADyson Sep 04 '18 at 09:36
  • If you're not sure what options to set, consult the maintainers of the API. If they don't have explicit documentation, they should at least be able to tell you how they expect the HTTP request to be structured and then you can work out what options would cause an appropriate request to be generated. – ADyson Sep 04 '18 at 09:36
  • What part of the code is jQuery? – cbutler Sep 04 '18 at 09:43
  • None is literally jquery _syntax_. What I'm saying is that "method", "withCredentials: true", and "processData: false" are not valid options for the `fetch()` method, as can clearly be seen in the documentation. However, coincidentally, they are (almost, in the case of withCredentials) valid options for jQuery's $.ajax() method which also creates HTTP requests. So I assumed you were confused between the two functions (fetch() and $.ajax()) somehow. Did you read the two links I gave you? Where did you get the idea from to set these values in a fetch() function? – ADyson Sep 04 '18 at 09:47
  • I apologise though as well - just realised in my earlier comment I linked back to your own question when in fact I intended to link you to the fetch() docs: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch – ADyson Sep 04 '18 at 09:48
  • And my overall point is that the fact that you've supplied options which won't actually work might be contributing to your problem, if you're assuming that they would affect the way the HTTP request is created. – ADyson Sep 04 '18 at 09:50
  • Actually sorry, "method" is valid in fetch(), but the others I mentioned aren't. You might find this link helpful: https://stackoverflow.com/questions/36067767/how-do-i-upload-a-file-with-the-js-fetch-api . Of course you also need to align what you do with the requirements of the API, as I mentioned. – ADyson Sep 04 '18 at 09:59
  • Thank you. I've seen that post. I'm reading the docs now to see if anything I've done is invalid. Yes there is some code there for testing etc but from what I can see there are no options I am using that are not valid for fetch. withCredentials does work. I am not using processData (that was things I tried from other stack overflow answers. – cbutler Sep 04 '18 at 10:10
  • And yes I have seen the other answer you mentioned – cbutler Sep 04 '18 at 10:10
  • if you're not using something you should remove it. What question suggested to use it?? And there's no "withCredentials" option documented for fetch(). Are you sure this is having any effect? It shouldn't. However if you look closely at the fetch() spec there is a `credentials` option. It doesn't accept true or false values, though. See also https://stackoverflow.com/questions/40543372/set-withcredentials-to-the-new-es6-built-in-http-request-api-fetch . I don't really understand why you would keep trying values which aren't in the spec, just because you saw them in a random SO question. – ADyson Sep 04 '18 at 10:13
  • I have tried all your suggestions and removed what you identified. I have just not updated the code example here. My thought now is that the backend may be expecting a file stream and I would need to covert excel to json. – cbutler Sep 04 '18 at 10:31
  • a file stream is not JSON. Sending it in binary format using multipart encoding is closest to a "stream" (which you can't really do using HTTP in this manner). JSON is text. If you wanted to send it within a JSON object you'd have to base64-encode it. But you really need to check what the API is actually expecting instead of guessing. Either find its documentation or consult the relevant people who support it. – ADyson Sep 04 '18 at 10:41
  • I think this is part of the problem: [link](https://stanko.github.io/uploading-files-using-fetch-multipart-form-data/). The endpoint expects 'Content-Type': 'multipart/form-data' however the boundary messes things up but I need to be able to send an accessToken in the header. Maybe I should try another http request library like axios or Request or superagent. – cbutler Sep 04 '18 at 15:14
  • the boundary is in the request body, the access token is in the header. I can't see how these would conflict or how that's really relevant. It's perfectly possible to send headers with a multipart request. Maybe you should look in your browser tools to see what the final request actually looks like. What if you simply omit the contenttype header entirely? – ADyson Sep 04 '18 at 15:38
  • Have a look at the link I posted... if you omit Content-Type the browser adds it back in and also adds the boundary. Read the article and you will see why it's important. – cbutler Sep 04 '18 at 18:11
  • I understand why the boundary is important but it's nothing to do with the authorization header. I made a little demo here. http://jsfiddle.net/316txzhm/8/ It's not identical to your code but the essentials are there in terms of how the request is made. Select a sample file to upload and click the button, and then watch your request. Notice the code does not set the content-type header but it gets added back in correctly with the boundary. Notice also the auth header is ok. I also captured the HTTP request generated by the browser when I run it (using Chrome): https://imgur.com/a/PntHs4L – ADyson Sep 05 '18 at 09:32
  • AFAIK that is basically the correct approach to uploading both the file and its name in separate fields as a multi-part request. Is your endpoint expecting something else? The boundary is required for the endpoint to know where the file data ends and the filename data begins. If it isn't able to deal with that then I can't see how it's going to be able to read the required data correctly. BTW you don't really need to send the filename separately - notice that the request includes it anyway along with the file data. – ADyson Sep 05 '18 at 09:33

1 Answers1

0

Thanks for the help and suggestions, it turned out to be a backend issue but even still I learned a lot in the process. I will post my working code here in case anyone comes across this and finds it useful.

export const uploadAdminFile = (file, path, resource=config.defaultResource) => {
  const url = createUrl(resource, path);
  const formData = new FormData();

  formData.append('file', file[0][0]);
  formData.append('filename', file[0][0].name);

  const request = accessToken =>
    fetch(url,
    {
      method: 'POST',
      headers: {
        Accept: 'application/json',
        Authorization: `Bearer ${accessToken}`,
    },
    body: formData,
  });

  return sendRequest(request, resource);
};

As mentioned, the file name does not need to be sent separately and count be omitted. I am indexing the file this way because I get it from dropzone as an array and I only want a single file (the first one in the array). I hope this helps someone else out and here is a link to the mdn fetch docs (good information) and a good article on using fetch and formData.

cbutler
  • 1,111
  • 5
  • 16
  • 32