3

I editted the whole question because it was very long and confusing.

Clear, concise new question

I was sending all data from my forms as a stringified JSON, but now I need to append a file, so I can no longer do this. How do I send my form data from React to an ASP.NET Core API in a format that allows appending files?

Old question

I have several forms working perfectly: I send the data using fetch from React and receive it correctly as body in my ASP.NET Core API.

However, I need to send files now, and I don't know how to append them since I am just sending all of my content in a strinfified JSON.

fetch("localhost/api/test", {
    method: 'POST',
    headers: {
        'Accept': 'application/json',
        'Content-Type': 'application/json'
    },
    body: JSON.stringify(body)
}).then(result => result.json()).then(
    (result) => {
        console.log(result);
    }
);

I tried sending a FormData object built like this instead of the JSON.stringify(body).

let formData = new FormData();
for (var property in body) {
    formData.append(property, body[property]);
}

But when I send this object instead of the stringified JSON I always get null for all the values in ASP.NET Core.

I also tried sending this:

URLSearchParams(data)

And this:

let formBody = [];
for (var property in details) {
    var encodedKey = encodeURIComponent(property);
    var encodedValue = encodeURIComponent(details[property]);
    formBody.push(encodedKey + "=" + encodedValue);
}
formBody = formBody.join("&");

And I tried different combinations of headers with every type of data encoding:

  • No headers
  • 'Content-Type': 'multipart/formdata'
  • 'Content-Type': 'application/json'
  • 'Content-Type': 'application/x-www-form-urlencoded'

I also tried getting the data from ASP.NET with both [FromBody] and [FromForm].

I think I have tried every possible combination of all the options I have explained above, with no result. I always get null values in my API.

Edit:

Right now, I am not even trying to send a file. I am trying to successfully send common data in the proper format before trying to attach a file. I don't know if I should change the title of the question.

This is my API code:

[HttpPost]
[Route("login")]
public object Login([FromBody] Credentials cred)
{
    // check credentials
    return CustomResult.Ok;
}

The class Credentials:

public class Credentials
{
    public string Username { get; set; }
    public string Password { get; set; }
}

The object body from React looks like this:

{
    username: "user",
    password: "pass"
}
5
  • Could you please share your API and React code for how you are uploading the image file? So that it can be resolved easily. Commented Jun 6, 2022 at 7:06
  • I don't even have a file input. I am just trying to send my data in a different way because I cannot send files over a stringified JSON, or can I? Commented Jun 6, 2022 at 7:13
  • 1
    You have two ways, Either IFormFile Image or image as base64 where the image will be served as string Commented Jun 6, 2022 at 7:15
  • It took me quite some time to work this through myself. Remove any uncertainty as to whether its front end or back end by using Postman. You can craft the request using form-data etc and then ensure the api is working. Once that is fine use the debugging tools on the browser to see what request your client code is producing. Commented Jun 7, 2022 at 7:34
  • @NAJ To use postman was the best option. Backend was fine, the problem was the for loop I was using to append the values to the formData. I checked the object after the loop and it seemed ok but I decided to try change it to Object.keys(data).forEach and it worked. I still don't understand why but it works Commented Jun 7, 2022 at 10:55

3 Answers 3

6

My whole question was a mess and, in my case, the problem was the for loop. However, I will try to explain clearly what I needed because there is a lot of missleading information online about how to submit a form to an API with React.

Solution

Backend

In the backend, accept POST petitions and get the data with FromForm. If you need to use credentials, it is probably better to pass them through headers instead of putting them as hidden inputs inside every single form.

[HttpPost]
[Route("test")]
public object Test([FromHeader] string token, [FromForm] MyDataClass data)
{
    // check token
    // do something with data
    return CustomResult.Ok;
}

If you bind an object, the properties must have the same name as the ones you are sending from the frontend and must have a public setter.

public class MyDataClass
{
    public string SomeInfo { get; set; }
    public string SomeOtherInfo { get; set; }
}

Frontend

Send the data as FormData. To do this, you can follow this tutorial. That way, you won't need to worry about handling input changes and formatting your data before submitting it.

However, if your data is already in a plain JavaScript object, you can transform it in a FormData as shown below.

let formData = new FormData();
Object.keys(data).forEach(function (key) {
    formData.append(key, data[key]);
});

Nonetheless, this is very basic and if you have a complex object (with arrays, nested objects, etc) you will need to handle it differently.

Finally, I used fetch to call my API. If you use it, it is important to NOT set the Content-Type header, because you will be overwritting the one that fetch automatically chooses for you, which will be the right one in most cases.

fetch("localhost/api/test", {
    method: 'POST',
    headers: {
        'Token': "dummy-token"
        // DON'T overwrite Content-Type header
    },
    body: formData
}).then(result => result.json()).then(
    (result) => {
        console.log(result);
    }
);
Sign up to request clarification or add additional context in comments.

1 Comment

I went on a similar journey with trying all the combinations of Content-Type and [FromForm] etc. Thanks for circling back and describing your solution excellently.
3

A bit more time now. (10 fish caught.) I notice from your code you had used the header "multipart/formdata", It should have a hyphen; "multipart/form-data".

On my API I am specifying that it consumes the same type: [Consumes("multipart/form-data")]

I am not clear what the .net core defaults are and whether it should automatically de-serialise the form data but specifying it should rule out any ambiguity.

With my code I am sending a file with some parameters. On the api side it looks like:

    public class FileUpload_Single
    {
        public IFormFile DataFile { get; set; }
        public string Params { get; set; }
    }

    // POST: api/Company (Create New)
    [HttpPost]
    [Authorize(PermissionItem.SimulationData, PermissionAction.Post)]
    [RequestSizeLimit(1024L * 1024L * 1024L)]  // 1 Gb
    [RequestFormLimits(MultipartBodyLengthLimit = 1024L * 1024L * 1024L)] // 1 Gb
    [Consumes("multipart/form-data")]
    public async virtual Task<IActionResult> Post([FromForm] FileUpload_Single data)

.... Then on the client side it looks like:

  let formData = new FormData();
  formData.append("DataFile", file);
  formData.append("Params", JSON.stringify(params));
  fetch(apicall, {
    method: "POST",
    mode: "cors",
    headers: {
      Authorization:
        "Bearer " + (localStorage.getItem("token") || "").replace(/['"]+/g, ""),
      Accept: "multipart/form-data",
    },
    body: formData,
  })

With the file coming from

  const changeHandler = (event) => {
    setSelectedFile(event.target.files[0]);
    setIsSelected(true);
  };

(from my React component)

Comments

0

This work for my, thanks for det help NAJ, ///Client part////

uploadImage: (uploadImage: any): AppThunkAction<KnownAction> => (dispatch, getState) => {
        const appState = getState();
        const formData = new FormData();
        formData.append('formFile', uploadImage);
        formData.append("fileName", uploadImage.name);
            fetch(`api/File/Upload`, {
                method: 'POST',
                headers: authHeader.authHeaderAddAccept(),
                body: formData
            })
                .then(response => response.json() as Promise<UploadFileResponse>)
                .then(data => {
                    console.log('UploadFiles');
                    console.info(data);
                    if (appState && appState.fileUpload) {
                        let selectedFileUpload: FilesUpload = { ...appState.fileUpload.selectedFileUpload };
                        selectedFileUpload.id = data.id;
                        selectedFileUpload.fileUrl = data.fileUrl;
                        selectedFileUpload.filePath = data.filePath;
                        selectedFileUpload.fileName = data.fileName;
                        dispatch({ type: 'SET_FILE_UPLOAD_ACTION', selectedFileUpload: selectedFileUpload });
                        dispatch({ type: 'CREATE_PDF_RECEIVE', enablePdf: false });
                    }
                })
                .catch(error => {
                    console.error(error);
                });
        dispatch({ type: 'CREATE_PDF_REQUEST' });
    }

/////API Controller////

[HttpPost("Upload")]
        [ProducesResponseType(StatusCodes.Status200OK)]
        [ProducesResponseType(StatusCodes.Status500InternalServerError)]
        [Consumes("multipart/form-data")]
        public async Task<ActionResult<UploadFileResponse>> UploadAsync([FromForm] UploadFileRequest uploadFileRequest)
        {
            try
            {
                UploadFileResponse uploadFileResponse = await _fileAgent.UploadAsync(uploadFileRequest);
                return Ok(uploadFileResponse);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex.Message, ex);
                return StatusCode(StatusCodes.Status500InternalServerError);
            }
        }

2 Comments

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
Hello, please don't post code only and add an explantation as to why you think that this is the optimal solution. People are supposed to learn from your answer, which might not occur if they just copy paste code without knowing why it should be used.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.