The "normal" way of accepting file uploads in Web API is to examine the the Request.Content. However, I'd like to have the file data be an actual parameter of the method, so that tools like NSwag are able to generate proper TypeScript clients.
So instead of stuff like this:
[HttpPost]
public async Task UploadFile()
{
var provider = new MultipartFormDataStreamProvider(Path.GetTempPath());
var result = await Request.Content.ReadAsMultipartAsync(provider);
var formData = result.FormData.GetValues("Path");
...
}
I just want something like this:
public async Task UploadFileInfo(Models.FileInfo fileInfo)
{
//FileInfo contains a string of the binary data, decode it here
}
I have this partly working except that I can't reliably encode the binary file data as a string in JavaScript and then decode it for saving in C#/.NET.
In JavaScript, I've tried FileReader.readAsArrayBuffer, FileReader.readAsBinaryString, FileReader.readAsDataURL, FileReader.readAsText, but none of these have produced a string of binary data that I can reliably decode. In C#, I've tried System.Text.Encoding.UTF8.GetBytes and/or Convert.FromBase64String.
For reference, the relevant code NSwag is generating to communicate with the Web API is as follows:
const content_ = JSON.stringify(fileInfo);
let options_ : any = {
body: content_,
observe: "response",
responseType: "blob",
headers: new HttpHeaders({
"Content-Type": "application/json",
})
};
return this.http.request("post", url_, options_)
where this.http is an Angular HttpClient.
FileDto
[DataContract]
public class RestFileDataDto
{
[DataMember(Name = "data", IsRequired = true)]
[Required]
public string Data { get; set; }
[DataMember(Name = "filename")]
public string Filename { get; set; }
}
Base64-Persistence
public void SaveFile(string data, string filePath)
{
var dir = Path.GetDirectoryName(filePath);
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
using (FileStream outputStream = File.Create(filePath))
{
using (Stream inputStream = new MemoryStream(new Base64Converter().ConvertToByteArray(data)))
{
inputStream.Seek(0, SeekOrigin.Current);
inputStream.CopyTo(outputStream);
}
}
}
Controller
[HttpPut]
[Route("route")]
public HttpResponseMessage Upload([FromBody] RestFileDataDto fileImage)
{
//// call persistence
}
Base64Converter().ConvertToByteArray
public byte[] ConvertToByteArray(string base64String)
{
var splittedString = base64String.Split(',');
return Convert.FromBase64String(splittedString[splittedString.Length - 1]);
}
Related
For example I have this code, that sends data to the asp.net core 6 web api controller.
const formData = new FormData();
formData.append("Id", 1);
formData.append("PhotoNames", ["name1", "name2", "name3"]);
formData.append("Photos", [file1, file2, file3)];
axios.post("/api/MyController", formData);
file1, file2, file3 are the photos, which I received form <input type="file"/>.
I handled the same request, but without arrays, by using this code in MyController.cs:
public class Item
{
public int Id { get; set; }
public string PhotoName{ get; set; }
public IFormFile Photo { get; set; }
}
[HttpPost]
public IActionResult Post([FromForm] ImageModel file)
{
//Using all the data from the request here
}
The request looked like:
const formData = new FormData();
formData.append("Id", 1);
formData.append("PhotoName", "name1");
formData.append("Photo", file1);
axios.post("/api/MyController", formData);
How can I handle post request with arrays in it? What kind of class and function I need to use in MyController.cs?
Before coding, you need know the following knowledge:
The formData key name should match the model property name.
The model property should be a List or an Array.
formData.append("PhotoNames", ["name1", "name2", "name3"]); post one item to the List by FormData instead of list item. You need post like below:
formData.append("PhotoName", "name1");
formData.append("PhotoName", "name2");
formData.append("PhotoName", "name3");
Here is a whole working demo you could follow:
Model:
public class ImageModel
{
public int Id { get; set; }
public string[] PhotoName { get; set; }
public IFormFile[] Photo { get; set; }
}
View:
<input type="file" multiple name="FileList" />
#section Scripts
{
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
$("input[name='FileList']").change(function () {
const formData = new FormData();
$("input[name='FileList']").each(function () {
var ReadyToUpload = $(this)[0].files;
if (ReadyToUpload.length > 0) {
$.each(ReadyToUpload, function (i, file) {
formData.append("Photo", file);
});
}
});
formData.append("Id", 1);
formData.append("PhotoName", "name1");
formData.append("PhotoName", "name2");
formData.append("PhotoName", "name3");
//formData.append("Photos", [file1, file2, file3)];
axios.post("https://localhost:44397/api/WeatherForecast", formData);
})
</script>
}
Api:
[ApiController]
[Route("api/[controller]")]
public class WeatherForecastController : ControllerBase
{
[HttpPost]
public void Post([FromForm] ImageModel file)
{
//Using all the data from the request here
}
}
Result:
try making PhotoNames nad Photos form data an array
const formData = new FormData();
formData.append("Id", 1);
formData.append("PhotoNames[]", ["name1", "name2", "name3"]);
formData.append("Photos[]", [file1, file2, file3)];
axios.post("/api/MyController", formData);
Goal
To protect the web app from malicious spam bot crawlers and similar malicious actors my goal is to use reCAPTCHA v3 to analyze the user visiting the site and if the Captcha v3 score is good enough (let's say 0.5 or better) use the Fetch API to POST the token and so verify it and if the score is good enough as mentioned previously return the E-Mail address within some HTML. For simplicity sake, the function loadContactbubble() gets executed when you click a button.
Problems
I am not sure where to implement the if (response.score => 0.5) check.
Frontend semi-works in that regard that in the network browser debug tools it gives an response but in the console it prints out the response as undefined
Is my implementation secure enough? Can't the secret key somehow siphoned or similar?
I get a lot of CSP warnings in the browser, might this be an issue in production?
Code
I was following this guide: https://dev.to/spencer741/google-recaptcha-v3-server-side-validation-using-asp-net-core-5-0-3hfb (that means that the code is like 80-90% from this article)
My appsettings.json contains the secret key and the siteverify Link (API link).
GHttpModels.cs:
using System;
using System.Runtime.Serialization;
namespace _projectname.Tooling
{
public class GRequestModel
{
public string path { get; set; }
public string secret { get; set; }
public string response { get; set; }
public string remoteip { get; set; }
public GRequestModel(string res, string remip)
{
response = res;
remoteip = remip;
secret = Startup.Configuration["GoogleRecaptchaV3:Secret"];
path = Startup.Configuration["GoogleRecaptchaV3:ApiUrl"];
if (String.IsNullOrWhiteSpace(secret) || String.IsNullOrWhiteSpace(path))
{
//Invoke logger
throw new Exception("Invalid 'Secret' or 'Path' properties in appsettings.json. Parent: GoogleRecaptchaV3.");
}
}
}
//Google's response property naming is
//embarrassingly inconsistent, that's why we have to
//use DataContract and DataMember attributes,
//so we can bind the class from properties that have
//naming where a C# variable by that name would be
//against the language specifications... (i.e., '-').
[DataContract]
public class GResponseModel
{
[DataMember]
public bool success { get; set; }
[DataMember]
public string challenge_ts { get; set; }
[DataMember]
public string hostname { get; set; }
//Could create a child object for
//error-codes
[DataMember(Name = "error-codes")]
public string[] error_codes { get; set; }
}
}
GoogleReCaptchaV3Service.cs:
using System;
using System.Threading.Tasks;
using System.Text.Json;
using System.Web;
using System.Net.Http;
using System.IO;
using System.Text;
using System.Runtime.Serialization.Json;
namespace _projectname.Tooling
{
public class CaptchaRequestException : Exception
{
public CaptchaRequestException()
{
}
public CaptchaRequestException(string message)
: base(message)
{
}
public CaptchaRequestException(string message, Exception inner)
: base(message, inner)
{
}
}
public interface IGoogleRecaptchaV3Service
{
HttpClient _httpClient { get; set; }
GRequestModel Request { get; set; }
GResponseModel Response { get; set; }
void InitializeRequest(GRequestModel request);
Task<bool> Execute();
}
public class GoogleRecaptchaV3Service : IGoogleRecaptchaV3Service
{
public HttpClient _httpClient { get; set; }
public GRequestModel Request { get; set; }
public GResponseModel Response { get; set; }
public HttpRequestException HttpReqException { get; set; }
public Exception GeneralException { get; set; }
public GoogleRecaptchaV3Service(HttpClient httpClient)
{
_httpClient = httpClient;
}
public void InitializeRequest(GRequestModel request)
{
Request = request;
}
public async Task<bool> Execute()
{
// Notes on error handling:
// Google will pass back a 200 Status Ok response if no network or server errors occur.
// If there are errors in on the "business" level, they will be coded in an array;
// CaptchaRequestException is for these types of errors.
// CaptchaRequestException and multiple catches are used to help seperate the concerns of
// a) an HttpRequest 400+ status code
// b) an error at the "business" level
// c) an unpredicted error that can only be handled generically.
// It might be worthwhile to implement a "user error message" property in this class so the
// calling procedure can decide what, if anything besides a server error, to return to the
// client and any client handling from there on.
try
{
//Don't to forget to invoke any loggers in the logic below.
//formulate request
string builtURL = Request.path + '?' + HttpUtility.UrlPathEncode($"secret={Request.secret}&response={Request.response}&remoteip={Request.remoteip}");
StringContent content = new StringContent(builtURL);
Console.WriteLine($"Sent Request {builtURL}");
//send request, await.
HttpResponseMessage response = await _httpClient.PostAsync(builtURL, null);
response.EnsureSuccessStatusCode();
//read response
byte[] res = await response.Content.ReadAsByteArrayAsync();
string logres = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Retrieved Response: {logres}");
//Serialize into GReponse type
using (MemoryStream ms = new MemoryStream(res))
{
DataContractJsonSerializer serializer = new DataContractJsonSerializer(typeof(GResponseModel));
Response = (GResponseModel)serializer.ReadObject(ms);
}
//check if business success
if (!Response.success)
{
throw new CaptchaRequestException();
}
//return bool.
return true; //response.IsSuccessStatusCode; <- don't need this. EnsureSuccessStatusCode is now in play.
}
catch (HttpRequestException hre)
{
//handle http error code.
HttpReqException = hre;
//invoke logger accordingly
//only returning bool. It is ultimately up to the calling procedure
//to decide what data it wants from the Service.
return false;
}
catch (CaptchaRequestException ex)
{
//Business-level error... values are accessible in error-codes array.
//this catch block mainly serves for logging purposes.
/* Here are the possible "business" level codes:
missing-input-secret The secret parameter is missing.
invalid-input-secret The secret parameter is invalid or malformed.
missing-input-response The response parameter is missing.
invalid-input-response The response parameter is invalid or malformed.
bad-request The request is invalid or malformed.
timeout-or-duplicate The response is no longer valid: either is too old or has been used previously.
*/
//invoke logger accordingly
//only returning bool. It is ultimately up to the calling procedure
//to decide what data it wants from the Service.
return false;
}
catch (Exception ex)
{
// Generic unpredictable error
GeneralException = ex;
// invoke logger accordingly
//only returning bool. It is ultimately up to the calling procedure
//to decide what data it wants from the Service.
return false;
}
}
}
}
Startup.cs:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Http;
//from captchav3
using _projectname.Tooling;
namespace _projectname
{
public class CookieCheckMiddleware
{
private readonly RequestDelegate _next;
public CookieCheckMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task Invoke(HttpContext httpContext)
{
if (httpContext.Request.Cookies["ModalShown"] == null && httpContext.Request.Path != "/Cookies")
{
httpContext.Response.Redirect("/Cookies?q="+ httpContext.Request.Path);
}
await _next(httpContext); // calling next middleware
}
}
// Extension method used to add the middleware to the HTTP request pipeline.
public static class CookieCheckMiddlewareExtensions
{
public static IApplicationBuilder UseCookieCheckMiddleware(this IApplicationBuilder builder)
{
return builder.UseMiddleware<CookieCheckMiddleware>();
}
}
public class Startup
{
internal static IConfiguration Configuration { get; private set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
//public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// Captcha v3
services.AddHttpClient<IGoogleRecaptchaV3Service, GoogleRecaptchaV3Service>();
services.AddTransient<IGoogleRecaptchaV3Service, GoogleRecaptchaV3Service>();
services.AddControllers();
//Register dependencies
services.AddRazorPages();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
//app.Use(async (ctx, next) =>
//{
// await next();
// if (ctx.Response.StatusCode == 404 && !ctx.Response.HasStarted)
// {
// //Re-execute the request so the user gets the error page
// string originalPath = ctx.Request.Path.Value;
// ctx.Items["originalPath"] = originalPath;
// ctx.Request.Path = "/Cloud";
// await next();
// }
//});
// orig
//app.UseExceptionHandler("/Errors/{0}");
app.UseStatusCodePagesWithRedirects("/Errors/{0}");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// TEST
app.UseCookieCheckMiddleware();
app.UseAuthentication();
app.UseAuthorization();
var cookiePolicyOptions = new CookiePolicyOptions
{
MinimumSameSitePolicy = SameSiteMode.Strict,
};
app.UseCookiePolicy(cookiePolicyOptions);
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
// Experimental
endpoints.MapControllers();
});
}
}
}
_Layout.cshtml:
<button onclick="loadContactbubble();">Load contacts</button>
site.js (only the function for brevity):
function loadContactbubble() {
grecaptcha.execute('sitekeyhere', { action: 'onclick' }).then(function (token) {
console.log(token);
fetch("/load/contactbubble?RecaptchaToken=" + token, {
method: "POST",
body: token,
})
}).then((response) => {
console.log(response);
if (!response.ok) {
const errorBuild = {
type: "Error",
message: response.message || "Something went wrong",
data: response.data || "",
code: response.code || "",
};
}
}
//addText("Error: " + JSON.stringify(errorBuild));
//toggleLoader(2, "hidden");
//return;
)
}
ApiController.cs:
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using _projectname.Tooling;
using System.Threading.Tasks;
namespace _projectname.Controllers
{
public class SignUpModel
{
public string RecaptchaToken { get; set; }
}
[ApiController]
[Route("load/contactbubble")]
public class SignUp : ControllerBase
{
IGoogleRecaptchaV3Service _gService { get; set; }
public SignUp(IGoogleRecaptchaV3Service gService)
{
_gService = gService;
}
[HttpPost]
public async Task<IActionResult> Post([FromQuery] SignUpModel SignUpData)
{
GRequestModel rm = new GRequestModel(SignUpData.RecaptchaToken,
HttpContext.Connection.RemoteIpAddress.ToString());
_gService.InitializeRequest(rm);
if (!await _gService.Execute())
{
//return error codes string.
return Ok(_gService.Response.error_codes);
}
//call Business layer
//return result
return base.Content("<div>Welcome human! Here is our secret e-mail: test#test.com</div>", "text/html");
}
}
}
Errors
If I click the button the following gets printed out in the console:
Uncaught (in promise) TypeError: can't access property "ok", response is undefined
The response contains proper HTML in the network tab, which is weird.
How do I fix this?
Your function
function (token) {
console.log(token);
fetch("/load/contactbubble?RecaptchaToken=" + token, {
method: "POST",
body: token,
});
}
does not return anything, hence why the argument passed to the next .then((response) => ... is undefined.
Make this function return the fetched data, and it should hopefully work:
function (token) {
console.log(token);
return fetch("/load/contactbubble?RecaptchaToken=" + token, {
method: "POST",
body: token
});
}
(Well, it should then at least forward the fetch result to the next .then((response) => .... I have not looked for other errors in your code, so "it should hopefully work" is to be understood with respect to the one problem i explained here...)
I want use moment to handle dates, but when i send request to web api url the date arrives in another format.
For example, from JS i send '2021-01-02' but when request arrive backend, the date transform to '02-01-2021' and i need handle the YYY-MM-DD format for backend
example request:
get(): Observable<any>{
let strDate = '2021-01-02';
const payoad = {
... //other properties,
datesearch: moment(strDate).format('YYYY-MM-DD') // --> 2021-01-02
}
return this.http.post<any>('https://localhost:33421/api/orders', payload)
}
My c# class request:
public class RequestDto
{
public string Category { get; set; }
public string Product { get; set; }
public DateTime Datesearch { get; set; }
}
My web api controller:
[HttpPost]
public async Task<IActionResult> Search(RequestDto request)
{
var datecheck = request.Datesearch; // -> this get '02-01-2021'
....
....
return Ok(response);
}
What I can be doing wrong?
UPDATE:
Solved it as follow: as I am working with net core, I had to set the culture info inside my SatrtUp.cs class -> Configure method
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILoggerFactory loggerFactory)
{
CultureInfo customCulture = new CultureInfo("en-US", true);
customCulture.DateTimeFormat.ShortDatePattern = "yyyy-MM-dd";
app.UseRequestLocalization(new RequestLocalizationOptions
{
DefaultRequestCulture = new RequestCulture(customCulture),
SupportedCultures = new List<CultureInfo>
{
customCulture
},
SupportedUICultures = new List<CultureInfo>
{
customCulture
}
});
......
......
}
The best thing to do is to change
'YYYY-MM-DD'
to
'YYYY-MMM-DD'
That way it avoids ambiguity and c# DateTime can parse it easily.
ie. 05-06-20 could be 6-May-2020 or 5-June-2020
It's easier to use letters for the month
I have tried different methods that have been posted on this site, but nothing seems to work.
I want to create a clothing site (a personal project). The products on the site have their own class, that is built like this:
public class Product
{
public string ProductName { get; set; }
public string ProductPrice { get; set; }
public int Quantity { get; set; }
}
The shopping cart is another class that will contain a list of Product objects and this one is built like this:
public class ShoppingCart
{
[Key]
public int Id { get; set; }
List<Product> ProductList { get; set; }
public string ClientName { get; set; }
public string ClientAddress { get; set; }
public string ClientMail { get; set; }
}
I created an API Controller class and thought that would solve the problem. It looks like this:
[Route("api/Shopping")]
[ApiController]
public class ShoppingCartController : ControllerBase
{
[HttpPost]
public ShoppingCart Save([FromBody] ShoppingCart s)
{
return s;
}
}
In my JavaScript code I create my JSON object and try to post it like this:
var orderB = document.getElementById("orderB");
orderB.addEventListener("click", function () {
var inputName = document.getElementById("inputName").value;
var inputAddress = document.getElementById("inputAddress").value;
var inputMail = document.getElementById("inputMail").value;
var auxArray = [];
for (var i = 0; i < productsAux.length; i++) {
auxArray[i] = { "productName": productsAux[i].titlu, "productPrice": productsAux[i].pret, "quantity": localStorage.getItem(productsAux[i].titlu)};
}
var shoppingCart = {
productList: auxArray,
clientName: inputName,
clientAddress: inputAddress,
clientMail: inputMail
};
$.ajax({
type: "POST",
data: JSON.stringify(shoppingCart),
url: "api/shopping/save",
contentType: "application/json charset=utf-8",
}).done(function (res) {
alert(res);
});
After I push the order button on my page I expect to see the alert pop-up with the callback result which I suppose is the ShoppingCart object that is created using the JSON that I send.
For those coming on later, I would suggest checking that your types are correct on both ends. For instance, if your JS is posting a byte array and C# tries to convert it to an int, the whole object (not just that prop) will be null.
This has caught me many a time.
I opened the Network tab and I got this: I got a 404 (kind of
expected that) , the name of the method 'save' , a type of 'xhr' and a
size of 45B.
The 404 error obviously means the url/routing is wrong. Here to solve it ,you have two ways to achieve.
First way:
You can change url to "api/shopping" in ajax as follow:
$.ajax({
type: "POST",
data: JSON.stringify(shoppingCart),
url: "api/shopping",
contentType: "application/json charset=utf-8",
}).done(function (res) {
alert(res);
})
Second way:
You can change the path name
of Save action by Attribute routing with Http verb attributes as follow:
[Route("api/Shopping")]
[ApiController]
public class ShoppingCartController : ControllerBase
{
[HttpPost("Save")]
public ShoppingCart Save([FromBody] ShoppingCart s)
{
return s;
}
}
Update
According to your comment, in addition to the above updates, you also need to modify the routing settings as follows:
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
Debug result:
I need send data with file in a javascript object to asp.net core but
when it arrives at the asp.net core method, arrives with null values (List<Upload> listUpload)
I did a test removing the File property from the javascript object and it does not appear anymore null, the problem is with the File property that it can not map with the model property called File of type IFormFile.
javascript code:
jQuery('#tblDocuments > tbody > tr').each(function () {
checkBox = jQuery(this).find('td').eq(0).children();
inputFile = jQuery(this).find('td').eq(2).children()[0].files[0];
let Upload = {
File: inputFile,
CodigoVendaArquivo: res,
CodigoClienteArquivo: cliente,
Checkbox: checkBox[0].id
};
listUpload.push(Upload);
});
I'm trying to send with fetch:
fetch('../../upload', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(listUpload)
}).then(res => res.json())
.then(res => {
if (res == 1) {
// success
}
});
Here's my asp.net core method:
[Route("upload")]
[HttpPost]
public JsonResult Upload([FromBody] List<Upload> listUpload)
{
// something
}
Here's my Model:
public class Upload
{
public IFormFile File { get; set; }
public string CodigoVendaArquivo { get; set; }
public string CodigoClienteArquivo { get; set; }
public string Checkbox { get; set; }
}
Avoid embedding an IFormFile within the Upload class. When uploading something like IList<Upload> where its list item has a property of IFormFile, you might encounter an bug that results in a crazy memory leak (It ate me about 6.7G memory until I address it). For more information, see
ASP.NET Core #4802
ASP.NET MVC #8782
The easiest way to walk around it, as WahidBitar comments on GitHub, is to create a wrapper to receive the payload.
As for your question itself, you should avoid embedding an IFormFile within the Upload class.
Here's a working sample :
public class UploadPayload{
public IList<Upload> Uploads{get;set;}
public class IFormFileWrapper {
public IFormFile File { get; set; }
}
public class Upload
{
// See https://github.com/aspnet/Mvc/issues/8782
public IFormFile File { get; set; }
public IFormFileWrapper CodigoFile { get; set; }
public string CodigoVendaArquivo { get; set; }
public string CodigoClienteArquivo { get; set; }
public string Checkbox { get; set; }
}
}
And also change your action method as below:
public JsonResult Upload([FromBody] List listUpload)
public IActionResult Upload(UploadPayload listUpload)
{
...
}
Client Side :
To construct a formData, create a getFormData() function :
function getFormData(listUpload){
var formData = new FormData();
function getFieldName(index,name){
return "Uploads[" + index + "]." + name ;
};
function getFileName(index,name){
return "Uploads[" + index + "].CodigoFile." + name ;
};
for(var i =0 ;i <listUpload.length; i++){
var upload = listUpload[i];
formData.append(getFieldName(i, 'CodigoVendaArquivo'), upload.CodigoVendaArquivo);
formData.append(getFieldName(i, 'CodigoClienteArquivo'), upload.CodigoClienteArquivo);
formData.append(getFieldName(i, 'Checkbox'),upload.Checkbox)
formData.append(getFileName(i, 'File'), upload.File);
}
return formData;
}
Now we could send the formData in the following way:
jQuery('#tblDocuments > tbody > tr').each(function () {
...
listUpload.push(Upload);
}
var formData = getFormData(listUpload);
fetch('../../upload', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(listUpload),
body:formData,
})
.then(res => res.json())
.then(res => {
if (res == 1) {
// success
}
});