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

使用HttpClient与IFormFile同时上传文件并发送数据的400错误问题

解决HttpClient同时上传文件和Form绑定DTO时的400错误

问题原因

你的API控制器Post方法用[FromForm]标记EmailIncomingDTO,意味着它期望从multipart/form-data的表单字段中绑定DTO的属性。但你在测试时错误地将DTO以JSON格式发送,导致ASP.NET Core的模型绑定无法解析请求内容,触发400(Bad Request)错误。单独传文件或DTO时,请求格式符合预期,所以能正常返回200。

解决方案

需要正确构建multipart/form-data格式的请求,将DTO的属性作为表单字段添加,同时包含文件内容。以下是两种可行的实现方式:

方式一:逐个添加DTO属性到表单(推荐)

这种方式符合ASP.NET Core默认的[FromForm]模型绑定规则,无需修改控制器代码。

控制器代码(保持原有写法)

[ApiController]
[Route("api/emails")]
public class EmailsController : ControllerBase
{
    [HttpPost]
    public IActionResult Post([FromForm] EmailIncomingDTO dto, IFormFile attachment)
    {
        // 业务逻辑处理
        return Ok();
    }
}

public class EmailIncomingDTO
{
    public string To { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
    // 其他属性
}

XUnit测试中的HttpClient调用代码

[Fact]
public async Task Post_WithFileAndFormDto_ReturnsOk()
{
    var client = new HttpClient();
    var multipartContent = new MultipartFormDataContent();

    // 构建DTO并添加每个属性为表单字段
    var testDto = new EmailIncomingDTO
    {
        To = "recipient@example.com",
        Subject = "Test Upload",
        Body = "This is a test email content"
    };
    multipartContent.Add(new StringContent(testDto.To), nameof(EmailIncomingDTO.To));
    multipartContent.Add(new StringContent(testDto.Subject), nameof(EmailIncomingDTO.Subject));
    multipartContent.Add(new StringContent(testDto.Body), nameof(EmailIncomingDTO.Body));

    // 添加文件内容
    using var fileStream = File.OpenRead("sample-attachment.txt");
    var fileContent = new StreamContent(fileStream);
    fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("text/plain");
    // 第三个参数是文件名,第二个参数要和控制器的IFormFile参数名一致(attachment)
    multipartContent.Add(fileContent, "attachment", Path.GetFileName(fileStream.Name));

    // 发送请求
    var response = await client.PostAsync("https://your-api-endpoint/api/emails", multipartContent);
    response.EnsureSuccessStatusCode(); // 验证返回200
}

方式二:将DTO序列化为JSON作为单个表单字段(适用于复杂DTO)

如果DTO结构复杂,不想逐个添加属性,可以将DTO序列化为JSON字符串,作为单个表单字段传递,然后在控制器中手动反序列化。

修改后的控制器代码

[HttpPost]
public IActionResult Post(string dtoJson, IFormFile attachment)
{
    var dto = JsonSerializer.Deserialize<EmailIncomingDTO>(dtoJson);
    if (dto == null)
    {
        return BadRequest("Invalid DTO format");
    }
    // 业务逻辑处理
    return Ok();
}

对应的HttpClient调用代码

[Fact]
public async Task Post_WithFileAndJsonDto_ReturnsOk()
{
    var client = new HttpClient();
    var multipartContent = new MultipartFormDataContent();

    var testDto = new EmailIncomingDTO
    {
        To = "recipient@example.com",
        Subject = "Test Upload",
        Body = "This is a test email content"
    };
    // 序列化为JSON字符串
    var dtoJson = JsonSerializer.Serialize(testDto);
    // 添加为表单字段,参数名要和控制器的dtoJson参数一致
    multipartContent.Add(new StringContent(dtoJson, Encoding.UTF8, "application/json"), "dtoJson");

    // 添加文件(同方式一)
    using var fileStream = File.OpenRead("sample-attachment.txt");
    var fileContent = new StreamContent(fileStream);
    fileContent.Headers.ContentType = MediaTypeHeaderValue.Parse("text/plain");
    multipartContent.Add(fileContent, "attachment", Path.GetFileName(fileStream.Name));

    var response = await client.PostAsync("https://your-api-endpoint/api/emails", multipartContent);
    response.EnsureSuccessStatusCode();
}

关键注意事项

  • 表单字段的名称必须和控制器中DTO的属性名(或参数名)完全一致,否则模型绑定会失败。
  • 添加文件时,表单字段名要和控制器的IFormFile参数名一致(示例中是attachment)。
  • 确保文件内容的ContentType设置正确,匹配文件类型。

内容的提问来源于stack exchange,提问作者Spider

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 04:10:34