首页 > 解决方案 > 使用 HttpClient 将大文件流传递给 MultipartFormDataContent

问题描述

尝试MultipartFormDataContentHttpClient数据流一起使用时遇到问题。

语境

我正在尝试将一个大文件上传到 ASP.NET Core Web API。客户端应通过 POST 请求表单数据将文件发送到前端 API,而前端 API 又应将文件转发到后端 API。

因为文件可能很大,所以我按照微软的例子,即我不想使用IFormFiletype 而是阅读Request.BodyusingMultipartReader。这是为了避免将整个文件加载到服务器的内存中,或者将其保存在服务器硬盘上的临时文件中。

问题

后端 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,因此Request.Body该测试不是在 Web API 中读取流,而是尝试将其添加StreamContent到后端 APIMultipartFormDataContent并将其发布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 控制器然后抛出一个IOExceptionat await reader.ReadNextSectionAsync(),说“意外的 Stream 结束,内容可能已经被另一个组件读取”。

GitHub 存储库(完整示例)

我上传了一个完整的问题示例(包括后端 API 和测试)一个GitHub repo

问题

我一定做错了什么。如何在不将整个文件加载到前端的内存或硬盘驱动器的情况下,将在一个服务(前端 API)中的表单数据内容类型的请求中接收到的文件转发到另一个服务(后端 API) API,即只是将数据流转发到后端API?

提前感谢您的帮助。

标签: c#asp.net-coreasp.net-core-5.0

解决方案


推荐阅读