React与.NET Core文件上传问题:后端接收文件为Null
问题:React + .NET Core 文件上传时后端IFormFile始终为Null
我用React和.NET Core实现文件上传功能,后端接收的IFormFile一直是Null。前端控制台和网络请求Payload里能看到文件数据,但FormData没被正确解析传递到后端。
后端代码
[HttpPost("{projectId}/upload")] //[Authorize] [ProducesResponseType(204)] [ProducesResponseType(400)] public async Task<IActionResult> UploadDocument(int projectId, [FromForm] IFormFile file) { _logger.LogDebug("Inside UploadDocument endpoint"); // Check if project exists var project = await _context.Projects.FirstOrDefaultAsync(p => p.ProjectId == projectId); if (project == null) { _logger.LogError($"An item in Project could not be found with ID: {projectId}"); throw new NotFoundException($"An item in Project could not be found with ID: {projectId}"); } if (file != null && file.Length > 0) { // Get project folder path //var repositoryPath = @"G:\Shares\GT Repository"; var repositoryPath = @"D:\Uploads"; var projectFolderPath = Path.Combine(repositoryPath, project.ProjectNumber); //var projectFolderPath = Path.Combine(repositoryPath); // Creates sub folders >>> future implementation if (!Directory.Exists(projectFolderPath)) { Directory.CreateDirectory(projectFolderPath); // Create subfolders for document types var documentFolders = new List<string> { "Quote", "Customer PO", "Invoice", "Job Card", "Delivery Note", "Costing Sheet" }; foreach (var folderName in documentFolders) { var documentFolderPath = Path.Combine(projectFolderPath, folderName); if (!Directory.Exists(documentFolderPath)) { Directory.CreateDirectory(documentFolderPath); } } } // Generate a unique file name var uniqueFileName = project.ProjectNumber + "_" + Guid.NewGuid().ToString() + "_" + file.FileName; //var uniqueFileName = Guid.NewGuid().ToString() + "_" + file.FileName; var filePath = Path.Combine(projectFolderPath, uniqueFileName); using(var stream = new FileStream(filePath, FileMode.Create)) { await file.CopyToAsync(stream); } return NoContent(); } _logger.LogError("No file provided for upload."); throw new BadRequestException("No file provided for upload."); }
前端代码
const [file, setFile] = useState({ quote: null, po: null, invoice: null, jobCard: null, deliveryNote: null, costingSheet: null }) const handleFileChange = (e) => { const { name, files } = e.target; setFile({ ...file, [name]: files[0], }); }; if(file){ const formData = new FormData(); for (const name in file) { const uploadedFile = file; if (uploadedFile) { formData.append(name, uploadedFile); console.log(uploadedFile) try{ const result = await axios.post(`https://localhost:7281/api/Project/${values.projectId}/upload`, formData, { headers: { "Content-Type": "multipart/form-data", Accept: "application/json", }, }) console.log(result); }catch(error){ console.log("Error occurred: ",error); } } } } <input className={`w-full md:w-full px-2 md:mb-0 block uppercase tracking-wide text-gray-700 text-xs font-bold mt-2 mb-2 ${fileStyles.customFileInput}`} id="quote" name="quote" type="file" accept="application/pdf" title="Attach Quote" onChange={handleFileChange} /> <input className={`w-full md:w-full px-2 md:mb-0 block uppercase tracking-wide text-gray-700 text-xs font-bold mt-1 mb-2 ${fileStyles.customFileInput}`} id="po" name="po" type="file" accept="application/pdf" title="Attach PO" onChange={handleFileChange} /> <div className="flex justify-evenly mt-3"> <div> <label className="ml-2" htmlFor="jobCard">Attach Job Card</label> <input id="jobCard" name="jobCard" className={`w-full md:w-full px-2 md:mb-0 block uppercase tracking-wide text-gray-700 text-xs font-bold mt-2 ${fileStyles.customFileInput}`} type="file" accept="application/pdf" title="Attach Job Card" onChange={handleFileChange} /> </div> <div> <label className="ml-2" htmlFor="deliveryNote">Attach Delivery Note</label> <input id="deliveryNote" name="deliveryNote" className={`w-full md:w-full px-2 md:mb-0 block uppercase tracking-wide text-gray-700 text-xs font-bold mt-2 ${fileStyles.customFileInput}`} type="file" accept="application/pdf" title="Attach Delivery Note" onChange={handleFileChange} /> </div> <div> <label className="ml-2" htmlFor="costingSheet">Attach Costing Sheet</label> <input id="costingSheet" name="costingSheet" className={`w-full md:w-full px-2 md:mb-0 block uppercase tracking-wide text-gray-700 text-xs font-bold mt-2 ${fileStyles.customFileInput}`} type="file" accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" title="Attach Costing Sheet" onChange={handleFileChange} /> </div> </div>
问题排查与修复方案
核心问题点
- FormData文件添加错误:循环里
const uploadedFile = file是把整个状态对象赋值,而非当前遍历的单个文件,正确写法是const uploadedFile = file[name],且要判断文件不为null再添加。 - 参数名不匹配:后端接收参数名为
file,但前端FormData里的键是quote/po等,后端无法匹配到对应参数。 - Axios请求头错误:手动设置
"Content-Type": "multipart/form-data"会丢失boundary标识,导致后端无法解析FormData,应该让Axios自动处理请求头。 - 重复发送请求:当前代码每遍历一个文件就发一次请求,导致多次调用接口且FormData内容不完整。
修复后的前端代码
// 绑定到提交按钮的点击事件 const handleUpload = async () => { const formData = new FormData(); // 遍历所有文件,用后端期望的参数名"file"添加到FormData for (const name in file) { const uploadedFile = file[name]; if (uploadedFile) { formData.append("file", uploadedFile); // 如果需要区分文件类型,可额外添加字段:formData.append("fileType", name); } } // 确认有文件再发送请求 if (formData.has("file")) { try { const result = await axios.post( `https://localhost:7281/api/Project/${values.projectId}/upload`, formData, { headers: { Accept: "application/json", // 不要手动设置Content-Type,Axios会自动处理multipart/form-data并添加boundary }, } ); console.log(result); } catch (error) { console.log("Error occurred: ", error); } } else { console.log("No files selected to upload"); } };
后端可选调整(支持多文件上传)
如果需要一次上传多个文件,可修改后端接口参数:
public async Task<IActionResult> UploadDocument(int projectId, [FromForm] List<IFormFile> files) { foreach (var file in files) { if (file != null && file.Length > 0) { // 原有文件保存逻辑,可根据需求区分文件类型 // ... } } return NoContent(); }
其他注意事项
- 确保.NET Core项目已配置
AddControllersWithViews()或AddRazorPages(),这些服务默认包含FormData解析支持。 - 若存在跨域请求,需在后端配置CORS,允许
multipart/form-data类型的请求。
内容的提问来源于stack exchange,提问作者Gary
相关产品推荐
相关产品推荐

