【问题标题】:ASP.NET Core Web API - Get file along with [FromBody] JSON modelASP.NET Core Web API - 获取文件以及 [FromBody] JSON 模型
【发布时间】:2021-08-15 21:07:18
【问题描述】:

我有一个从客户端接收数据的现有 API 端点。我想添加在该端点上接收文件以及数据的功能。这是我的模型:

public class TestModel
{
    public IList<IFormFile> File { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
    public string Hobbies { get; set; }
}

这是我的终点:

[HttpPost]
[Route("upload")]
public async Task<ActionResult> UploadTest([FromBody] TestModel test)
{
    return Ok();
}

我目前正在使用 Postman 测试我的 API。我知道在 Postman 中可以选择以 form-data 作为正文发送文件,但在这种情况下,我的客户需要使用 form-data 而我使用 json 作为数据(使用 FromBody)。

我找不到如何做到这一点的方法 - 有可能吗?如果是这样,我如何使用 Postman 发送文件视图raw json?

【问题讨论】:

  • 您不能同时结合 json 响应和文件响应(即 1 个请求的 2 个响应)。您需要有 2 个端点,一个用于 json,一个用于文件。
  • 可能是错误的,但是,如果您打算一次性完成,我认为最好的方法是对二进制文件客户端进行编码(例如,使用 base64),然后在何时取消编码你反序列化。见:stackoverflow.com/questions/1443158/…
  • @Neil 谢谢。在这种情况下,确保完整交易的最佳做法是什么?这意味着客户端确定服务器已上传数据和文件。假设数据已上传,现在我调用上传文件,但文件上传失败 - 数据应该被删除,因为没有文件它是不相关的。仅通过表单数据?
  • 没有自动跟踪的方法。你必须想出一个适合你的方案。 @Jonathan 的想法可能更好,但这取决于您的文件有多大。我在一个需要最多 60GB 上传的系统上工作,但他的建议并不实用。
  • 你现在如何保存数据,你使用ORM吗?数据库交互是直接发生在端点中还是在其他地方有这种逻辑?

标签: c# asp.net asp.net-core asp.net-web-api


【解决方案1】:
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Test.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class TestController : ControllerBase
    {
        [HttpPost]
        [Route("upload")]
        public async Task<ActionResult> UploadTest([FromForm] TestModel test)
        {
            return Ok();
        }
    }
    public class TestModel
    {
        public IList<IFormFile> File { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public string Hobbies { get; set; }
    }
}

这是邮递员发出的请求的样子。

这是我们得到文件的证据。

【讨论】:

    【解决方案2】:

    上传文件时,如果您选择使用 JSON,那么您就是将客户端置于泡菜中。您将强制客户端必须对所有文件进行 Base64 编码。对于客户端来说,这可能是一项繁琐的任务,特别是如果它是浏览器 JS 代码。更不用说你增加了 33% 的有效载荷。

    表单数据?

    发布文件时,您希望使用 HTML 表单。这是浏览器非常擅长的。

    话虽如此,正确的做法是将端点设置为如下所示:

    // NOTE: This is the same as you have it in your question
    public class TestModel
    {
        public IList<IFormFile> File { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public string Hobbies { get; set; }
    }
    
    [HttpPost, Route("testme")]
    public IActionResult TestMe([FromForm] TestModel testModel)
    {
        return Ok();
    }
    

    注意[FromForm] 参数。

    然后将邮递员设置成如下所示:

    1. 将其设置为Post
    2. 转到Body 部分并将其设置为Form-Data
    3. 添加参数。您可以通过多次添加相同的参数名称来测试多个文件。
    4. 点击Send

    当端点被命中时,它应该是这样的:

    JSON?

    假设您绝对必须使用 JSON 作为发布数据。然后您的模型将如下所示:

    public class TestModel
    {
        public IList<byte[]> File { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public string Hobbies { get; set; }
    }
    

    您需要将端点从 [FromForm] 更改为 [FromBody],然后您必须说服前端开发人员他们必须使用 Base64 对所有文件进行编码并构建一个 JSON 对象来发送。我认为您不会因此而获得超级成功,因为它没有经过优化,也不是常态。它们可能会给您带来奇怪的外观。

    让我们来看看这个场景:

    1. 用户选择了 3 个文件。每个都是 100 MB。
    2. 用户点击“提交”,JS 现在将 300 MB 读入内存。
    3. JS 在浏览器中将 300 MB 的文件编码为 Base64。内存使用量已增加到 400 MB。
    4. JS 客户端构建 JSON 负载 -- 内存再次增加,因为需要基于 400 MB 字符串创建新字符串。
    5. 网络流量开始。
    6. 后端获取帖子数据。由于它是 JSON,ASP.NET Core 将反序列化为您的模型。框架必须分配 400 MB 的内存来从网络读取 JSON。
    7. ASP.NET 会反序列化,这也需要解码 400 MB 的数据,这不是一项轻松的任务。
    8. 您的端点被调用时使用了一个约 300MB 的模型在内存中

    您有时会遇到 OutOfMemory 异常。几乎可以保证。

    表单数据在 ASP.NET Core 中得到了极大的优化。它会将内存用于较小的文件,然后转储到磁盘以获取较大的文件。使用表单数据时不可能遇到内存问题。

    让我们运行一个表单场景:

    1. 用户选择了 3 个文件。每个都是 100 MB。
    2. 用户点击“提交”。浏览器现在将向后端发起请求,并将文件读入小块内存并逐块发送。一次一个文件。这些块的大小根据环境进行了优化。不会超过有效传输数据所需的量。内存使用量将降至最低。
    3. ASP.NET 开始读取数据。如果IFormFile 数据超过 5 MB(我相信这是限制),它会将其从内存流移动到磁盘流。
    4. ASP.NET 使用对源自磁盘的流的引用来调用您的端点。内存中模型的大小无关紧要,因为它只是保存对磁盘流的引用。

    邮递员?如果不编写脚本,PostMan 就无法做到这一点,因为这不正常。我敢肯定,如果你用谷歌搜索这个主题,你可能会找到一些东西,但它充其量只是很笨拙。

    【讨论】:

    • 感谢您指出我的错误。您不需要创建 2 个文件密钥。每个文件可以接收多个数据。
    • @DamilolaAdegunwa -- 必须是新功能 :) 感谢您的提醒。
    猜你喜欢
    • 2021-08-09
    • 1970-01-01
    • 2018-11-06
    • 1970-01-01
    • 2017-05-13
    • 2021-11-12
    • 2020-01-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多