HttpClient 流上传不适用于查询参数
HttpClient Stream Upload not working with queryparams
我想通过流将文件上传到我的 ASP.NET 核心 API 服务器,一切正常,直到我将查询字符串添加到我的 POST,在这种情况下我不没有出现异常,但文件是在服务器上创建的,但没有字节流入。我正在使用 Streams,因为我有非常大的文件 (20mb-8gb)
ASP.NET API:
[Route("api/[controller]")]
[ApiController]
public class UploadTestController : ControllerBase
{
// POST api/<UploadTestController>
[DisableRequestSizeLimit]
[HttpPost]
public async Task<IActionResult> Post([FromQuery]string username)
{
using (Stream stream = Request.Body)
{
try
{
using (var fstream = System.IO.File.OpenWrite(@"F:\TMP\file..."))
{
await stream.CopyToAsync(fstream);
}
return Ok("test");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return BadRequest();
}
}
}
}
客户:
class Program
{
static void Main(string[] args)
{
Upload().Wait();
}
public static async Task Upload()
{
FileStream stream = new FileStream(@"C:\TEMP\file...", FileMode.Open);
ThrottledStream throttledStream = new ThrottledStream(stream,900000);
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromMinutes(10);
var content = new MultipartFormDataContent();
content.Add(new StreamContent(throttledStream), "file...");
try
{
HttpResponseMessage response =
await client.PostAsync("https://localhost:5001/api/uploadtest?username=test", content);
var cont = await response.Content.ReadAsStringAsync();
Console.WriteLine(cont);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.ReadKey();
}
}
}
因此,如果我删除查询,一切正常。我无法向自己解释,因为查询不应该打扰它,对吗?
请考虑处理大文件(假设大于数百 MB)涉及一些安全和可用性问题(恢复、溢出等)。在这方面有趣的阅读是 this.
另一个考虑因素是 .Net 如何处理多部分请求以及上传文件的不同选项:
- Multipart/form-data
- Multipart/related
- 具有 Application/json 内容类型
的 Base64 编码文件
- 使用 MIME 类型的正文请求
还有RFC multipart specification is very interesting because you can learn how the boundaries in a request are defined. I think the best way to handle this requirements is by using libraries that allow you to resume files or retry.
在您的情况下,最简单的解决方案是将变量作为多部分内容的一部分发送,而不是在 url 请求中进行硬编码,并为流内容命名,这样您就可以让“ WebApi 背后的 magic" 正确地映射了这一点:
content.Add(new StreamContent(stream), name: "files", "file...");
var userName = new StringContent("test");
content.Add(userName, "username");
下一步是告诉控制器使用具有以相同方式命名的参数的内容[FromForm] IFormFileCollection files:
public async Task<IActionResult> Upload([FromForm] IFormFileCollection files)
然后你可以像这样读取所有其他参数:
IFormCollection form = await HttpContext.Request.ReadFormAsync();
form.TryGetValue("username", out StringValues user);
也就是说,这只是一种方法(既不是最好的也不是最坏的),只是对这种情况的快速修复。最好考虑让 DTO 代表整个表单(请求中包含的数据),并处理上述 Microsoft blog post 中解释的安全问题,其中:
- 配置框架限制
- 禁用自动绑定
- 处理取消
- 进度报告
我想通过流将文件上传到我的 ASP.NET 核心 API 服务器,一切正常,直到我将查询字符串添加到我的 POST,在这种情况下我不没有出现异常,但文件是在服务器上创建的,但没有字节流入。我正在使用 Streams,因为我有非常大的文件 (20mb-8gb)
ASP.NET API:
[Route("api/[controller]")]
[ApiController]
public class UploadTestController : ControllerBase
{
// POST api/<UploadTestController>
[DisableRequestSizeLimit]
[HttpPost]
public async Task<IActionResult> Post([FromQuery]string username)
{
using (Stream stream = Request.Body)
{
try
{
using (var fstream = System.IO.File.OpenWrite(@"F:\TMP\file..."))
{
await stream.CopyToAsync(fstream);
}
return Ok("test");
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
return BadRequest();
}
}
}
}
客户:
class Program
{
static void Main(string[] args)
{
Upload().Wait();
}
public static async Task Upload()
{
FileStream stream = new FileStream(@"C:\TEMP\file...", FileMode.Open);
ThrottledStream throttledStream = new ThrottledStream(stream,900000);
using (var client = new HttpClient())
{
client.Timeout = TimeSpan.FromMinutes(10);
var content = new MultipartFormDataContent();
content.Add(new StreamContent(throttledStream), "file...");
try
{
HttpResponseMessage response =
await client.PostAsync("https://localhost:5001/api/uploadtest?username=test", content);
var cont = await response.Content.ReadAsStringAsync();
Console.WriteLine(cont);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
Console.ReadKey();
}
}
}
因此,如果我删除查询,一切正常。我无法向自己解释,因为查询不应该打扰它,对吗?
请考虑处理大文件(假设大于数百 MB)涉及一些安全和可用性问题(恢复、溢出等)。在这方面有趣的阅读是 this.
另一个考虑因素是 .Net 如何处理多部分请求以及上传文件的不同选项:
- Multipart/form-data
- Multipart/related
- 具有 Application/json 内容类型 的 Base64 编码文件
- 使用 MIME 类型的正文请求
还有RFC multipart specification is very interesting because you can learn how the boundaries in a request are defined. I think the best way to handle this requirements is by using libraries that allow you to resume files or retry.
在您的情况下,最简单的解决方案是将变量作为多部分内容的一部分发送,而不是在 url 请求中进行硬编码,并为流内容命名,这样您就可以让“ WebApi 背后的 magic" 正确地映射了这一点:
content.Add(new StreamContent(stream), name: "files", "file...");
var userName = new StringContent("test");
content.Add(userName, "username");
下一步是告诉控制器使用具有以相同方式命名的参数的内容[FromForm] IFormFileCollection files:
public async Task<IActionResult> Upload([FromForm] IFormFileCollection files)
然后你可以像这样读取所有其他参数:
IFormCollection form = await HttpContext.Request.ReadFormAsync();
form.TryGetValue("username", out StringValues user);
也就是说,这只是一种方法(既不是最好的也不是最坏的),只是对这种情况的快速修复。最好考虑让 DTO 代表整个表单(请求中包含的数据),并处理上述 Microsoft blog post 中解释的安全问题,其中:
- 配置框架限制
- 禁用自动绑定
- 处理取消
- 进度报告