Reputation: 474
I editted the whole question because it was very long and confusing.
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?
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:
'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"
}
Upvotes: 3
Views: 8248
Reputation: 1
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);
}
}
Upvotes: 0
Reputation: 474
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.
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; }
}
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);
}
);
Upvotes: 6
Reputation: 1401
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)
Upvotes: 3