You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>

问题排查与修复方案

核心问题点

  1. FormData文件添加错误:循环里const uploadedFile = file是把整个状态对象赋值,而非当前遍历的单个文件,正确写法是const uploadedFile = file[name],且要判断文件不为null再添加。
  2. 参数名不匹配:后端接收参数名为file,但前端FormData里的键是quote/po等,后端无法匹配到对应参数。
  3. Axios请求头错误:手动设置"Content-Type": "multipart/form-data"会丢失boundary标识,导致后端无法解析FormData,应该让Axios自动处理请求头。
  4. 重复发送请求:当前代码每遍历一个文件就发一次请求,导致多次调用接口且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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 12:05:15