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

React调用C# API上传FormData时fetch返回400错误的求助

问题分析与解决方案

1. 前端请求的Content-Type错误(400错误核心原因)

你的React代码里手动设置了错误的请求头:'multi-part/formdata',存在两个关键问题:

  • 拼写错误:正确的格式是multipart/form-data(连字符位置错误,且应为form-data而非formdata)
  • 手动设置该请求头会导致浏览器无法自动添加boundary分隔符,而multipart类型的表单请求必须依赖boundary才能让后端正确解析数据,这直接导致后端无法识别请求内容,返回400错误。

修复前端代码

直接移除手动设置的Content-Type请求头,浏览器会自动为FormData生成正确的multipart/form-data请求头,并附带合适的boundary:

const sendRequest = () => {
    var base = process.env.REACT_APP_API_URL;
    var data = new FormData();
    data.append('cover', cover);
    data.append('file', file);
    data.append('title', title);
    data.append('metaData', meta);
    data.append('description', description);

    // 移除手动设置的Content-Type,由浏览器自动处理
    fetch(base + "/Book", { method: 'POST', body: data })
        .then(success => { 
            console.log(success); 
            console.log(data); 
            window.location.reload() 
        })
        .catch(e => {
            console.log(e); 
            console.log(data);
        });
}

2. 后端API的数据流复用问题(潜在功能bug)

你的后端代码中,将同一个MemoryStream同时传给了文档和封面的S3上传对象:在执行await file.CopyToAsync(stream)后,流的位置指针已经移到末尾,上传封面时这个流是空的,会导致封面文件上传失败。

修复后端代码

为文档和封面分别创建独立的MemoryStream,并重置流指针到开头:

[HttpPost]
public async Task<IActionResult> CreateBook([FromForm]IFormFile cover, [FromForm]IFormFile file, [FromForm]string title, [FromForm]string metaData, [FromForm]string description, CancellationToken cancellationToken)
{
    if (cover == null || file == null)
    {
        return BadRequest();
    }

    // 处理文档文件流
    await using var docStream = new MemoryStream();
    await file.CopyToAsync(docStream);
    docStream.Position = 0; // 重置指针到流开头,确保上传时能读取完整内容

    // 处理封面文件流
    await using var coverStream = new MemoryStream();
    await cover.CopyToAsync(coverStream);
    coverStream.Position = 0;

    var id = Guid.NewGuid();
    var fileExt = Path.GetExtension(file.FileName);
    var coverExt = Path.GetExtension(cover.FileName);
    var docName = $"{id}{fileExt}";
    var coverName = $"{id}{coverExt}";

    var bookDoc = new S3Obj()
    {
        BucketName = Constants.bucketName,
        InputStream = docStream,
        Name = Constants.bookPath + docName
    };
    var bookCover = new S3Obj()
    {
        BucketName = Constants.bucketName,
        InputStream = coverStream,
        Name = Constants.bookCoverPath + coverName // 修正:原代码误用了docName,改为coverName
    };
    var cred = new Credentials()
    {
        AccessKey = Constants.accessKey,
        SecretKey = Constants.secretKey
    };
    var docResponse = await StorageService.Singleton.UploadFileAsync(bookDoc, cred);
    var coverResponse = await StorageService.Singleton.UploadFileAsync(bookCover, cred);
    Console.WriteLine(docResponse.Message);
    Book book = new Book() { Id = id, Title = title, MetaData = metaData, Description = description, Cover = coverName, DocumentPath = docName };
    return Ok(await Mediator.Send(new CreateBook.Command() { Book = book }, cancellationToken));
}

注:同时修正了封面路径的命名错误,原代码中封面路径使用了文档的文件名,会导致文件覆盖或路径错误。

3. 额外排查点

如果修复后仍有问题,可检查以下内容:

  • 确认React端的cover和file变量是有效的File对象(可在append前通过console.log(cover, file)验证)
  • 检查后端是否有请求大小限制,可在Program.cs中配置调整:
builder.Services.Configure<FormOptions>(options =>
{
    options.MultipartBodyLengthLimit = 10 * 1024 * 1024; // 设置10MB的请求大小限制,可按需调整
});

内容的提问来源于stack exchange,提问作者Újfalusi Ábel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 09:45:01