当我尝试使用MultipartFormDataContent
和带有数据流的HttpClient
时,遇到了问题。
上下文
我正在尝试将大文件上传到ASP.NET Core Web API。客户端应该通过POST请求表单数据将文件发送到前端API,然后前端API应将文件转发到后端API。
由于文件可能很大,我遵循了Microsoft示例,即我不想使用IFormFile
类型,而是使用MultipartReader
读取Request.Body
。这是为了避免在服务器上将整个文件加载到内存中或将其保存在服务器硬盘上的临时文件中。
问题
后端API控制器动作如下(这几乎是直接从ASP.NET Core 5.0 示例应用程序中复制的,只进行了轻微简化):
[HttpPost]
[DisableRequestSizeLimit]
public async Task<IActionResult> ReceiveLargeFile()
{
var request = HttpContext.Request;
if (!request.HasFormContentType
|| !MediaTypeHeaderValue.TryParse(request.ContentType, out var mediaTypeHeader)
|| string.IsNullOrEmpty(mediaTypeHeader.Boundary.Value))
{
return new UnsupportedMediaTypeResult();
}
var reader = new MultipartReader(mediaTypeHeader.Boundary.Value, request.Body);
/* This throws an IOException: Unexpected end of Stream, the content may have already been read by another component. */
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
var hasContentDispositionHeader = ContentDispositionHeaderValue.TryParse(section.ContentDisposition,
out var contentDisposition);
if (hasContentDispositionHeader
&& contentDisposition!.DispositionType.Equals("form-data")
&& !string.IsNullOrEmpty(contentDisposition.FileName.Value))
{
/* Fake copy to nothing since it doesn't even get here */
await section.Body.CopyToAsync(Stream.Null);
return Ok();
}
section = await reader.ReadNextSectionAsync();
}
return BadRequest("No files data in the request.");
}
我使用
Microsoft.AspNetCore.Mvc.Testing
NuGet 包制作了一个集成测试,稍微减轻了问题。以下测试替换了前端 API ,因此不是在 Web API 中读取 Request.Body
流,而是尝试将 StreamContent
添加到 MultipartFormDataContent
中,并通过 HttpClient
将其发布到后端 API: [Fact]
public async Task Client_posting_to_Api_returns_Ok()
{
/* Arrange */
await using var stream = new MemoryStream();
await using var writer = new StreamWriter(stream);
await writer.WriteLineAsync("FILE CONTENTS");
await writer.FlushAsync();
stream.Position = 0;
using var client = _factory.CreateDefaultClient();
/* Act */
using var response =
await client.PostAsync(
"Receive",
new MultipartFormDataContent
{
{
new StreamContent(stream),
"file",
"fileName"
}
});
/* Assert */
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
}
后端 API 控制器在
await reader.ReadNextSectionAsync()
处抛出 IOException
,提示“流意外终止,内容可能已被其他组件读取”。
GitHub 代码库(完整示例)
我上传了一个包括后端 API 和测试的完整示例以解决这个问题,您可以在 GitHub 代码库 中查看。
问题
我一定做错了什么。如何将以 form-data 内容类型接收到的文件从一个服务(前端 API)转发到另一个服务(后端 API),而不会在前端 API 中加载整个文件到内存或硬盘,即只需将数据流转发到后端 API 中?
非常感谢任何帮助。
HttpClient
部分。特别是httpWebRequest.GetRequestStream()
部分(与.net 5相同)。这使您可以通过复制来转发请求流。 - Eldar