返回已下载文件的数据流(如果可能的话),而不将其分配到内存中。

huangapple go评论67阅读模式
英文:

Return stream of downloaded file without allocating it to memory (if possible)

问题

I have Web API endpoint that serves a file to me by first downloading it, and then returning the byte array.

This code works, but loads the entire file (no matter how big it is) into memory first.

Is there a way to simply return the same stream that I have used to download the file with the httpClient?

Simplified example of working code:

[HttpGet("file")]
public async Task<IActionResult> DownloadFile(string url)
{
    using var httpClient = new HttpClient();
    using var response = await httpClient.GetAsync(url);
    if (response.IsSuccessStatusCode)
    {
        var content = await response.Content.ReadAsByteArrayAsync();

        var cd = new System.Net.Mime.ContentDisposition
        {
             FileName = "myFile.pdf",

             // always prompt the user for downloading, set to true if you want 
             // the browser to try to show the file inline
             Inline = false, 
        };
                
        Response.Headers.Add("Content-Disposition", cd.ToString());
                
        var mediaType = response.Content.Headers.ContentType.MediaType;

        return File(content, mediaType);
    }

    return NotFound();
}

What I have tried (and kind of expected to work):

using var httpClient = new HttpClient();
using var response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
   using (var content = await response.Content.ReadAsStreamAsync())
   {
      content.Position = 0;

      var cd = new System.Net.Mime.ContentDisposition
      {
         FileName = "myFile.pdf",

         // always prompt the user for downloading, set to true if you want 
         // the browser to try to show the file inline
         Inline = false,
      };

      Response.Headers.Add("Content-Disposition", cd.ToString());

      var mediaType = response.Content.Headers.ContentType.MediaType;

      return File(content, mediaType);
}

but this gives me the ObjectDisposedException: cannot access a closed stream.

Is there any way of accomplishing what I'm intending to do please?

英文:

I have Web API endpoint that serves a file to me by first downloading it, and then returning the byte array.

This code works, but loads the entire file (no matter how big it is) into memory first.

Is there a way to simply return the same stream that I have used to download the file with the httpClient?

Simplified example of working code:

[HttpGet(&quot;file&quot;)]
public async Task&lt;IActionResult&gt; DownloadFile(string url)
{
    using var httpClient = new HttpClient();
    using var response = await httpClient.GetAsync(url);
    if (response.IsSuccessStatusCode)
    {
        var content = await response.Content.ReadAsByteArrayAsync();

        var cd = new System.Net.Mime.ContentDisposition
        {
             FileName = &quot;myFile.pdf&quot;,

             // always prompt the user for downloading, set to true if you want 
             // the browser to try to show the file inline
             Inline = false, 
        };
                
        Response.Headers.Add(&quot;Content-Disposition&quot;, cd.ToString());
                
        var mediaType = response.Content.Headers.ContentType.MediaType;

        return File(content, mediaType);
    }

    return NotFound();
}

What I have tried (and kind of expected to work):

using var httpClient = new HttpClient();
using var response = await httpClient.GetAsync(url);
if (response.IsSuccessStatusCode)
{
   using (var content = await response.Content.ReadAsStreamAsync())
   {
      content.Position = 0;

      var cd = new System.Net.Mime.ContentDisposition
      {
         FileName = &quot;myFile.pdf&quot;,

         // always prompt the user for downloading, set to true if you want 
         // the browser to try to show the file inline
         Inline = false,
      };

      Response.Headers.Add(&quot;Content-Disposition&quot;, cd.ToString());

      var mediaType = response.Content.Headers.ContentType.MediaType;

      return File(content, mediaType);
}

but this gives me the ObjectDisposedException: cannot access a closed stream.

Is there any way of accomplishing what I'm intending to do please?

答案1

得分: 3

为了同时处理多个请求,我在控制器类上添加了[ApiController]属性,并在DownloadFile操作方法上添加了[Produces("application/pdf")]属性。这允许同时高效地处理多个请求。

为了防止在多个请求时覆盖文件内容,我为每个请求生成了一个唯一的文件名,使用GUID并将其附加到文件名中。这确保每个请求都有其自己的唯一文件名,文件内容不会被覆盖。

生成的唯一文件名仅用于设置Content-Disposition标头中的文件名,该标头告诉用户的浏览器如何处理下载的文件。

因此,文件实际上没有写入磁盘,而是直接流式传输到响应流中,并以唯一文件名作为可下载附件返回给用户。

EmptyResult类是ASP.NET Core中的内置类,表示具有200状态代码的空响应。它在操作不需要返回任何数据但仍需要指示操作已成功完成的情况下使用。

[ApiController]
public class FileController : ControllerBase
{
    [HttpGet("file")]
    [Produces("application/pdf")]
    public async Task<IActionResult> DownloadFile(string url)
    {
        using var httpClient = new HttpClient();
        var response = await httpClient.GetAsync(url);

        if (response.IsSuccessStatusCode)
        {
            var fileName = $"myFile_{Guid.NewGuid()}.pdf";
            var cd = new System.Net.Mime.ContentDisposition
            {
                FileName = fileName,
                Inline = false,
            };

            Response.Headers.Add("Content-Disposition", cd.ToString());
            var mediaType = response.Content.Headers.ContentType.MediaType;

            await response.Content.CopyToAsync(Response.Body);

            return new EmptyResult();
        }

        return NotFound();
    }
}
英文:

To handle multiple requests at the same time, I added the [ApiController] attribute on the controller class and the [Produces(&quot;application/pdf&quot;)] attribute to the DownloadFile action method. This allows multiple requests to be handled simultaneously and efficiently.

To prevent the file content from being overwritten when multiple requests are made, I generated a unique file name for each request using a GUID and appended it to the file name. This ensures that each request gets its own unique file name and the file content won't be overwritten.

The unique file name that is generated is used only to set the file name in the Content-Disposition header, which tells the user's browser how to handle the downloaded file.

So, the file is not actually written to the disk, but it is streamed directly to the response stream and returned to the user as a downloadable attachment with a unique file name.

The EmptyResult class is a built-in class in ASP.NET Core that represents an empty response with a 200 status code. It is used in cases where the action does not need to return any data, but still needs to indicate that the action has completed successfully.

[ApiController]
public class FileController : ControllerBase
{
    [HttpGet(&quot;file&quot;)]
    [Produces(&quot;application/pdf&quot;)]
    public async Task&lt;IActionResult&gt; DownloadFile(string url)
    {
        using var httpClient = new HttpClient();
        var response = await httpClient.GetAsync(url);

        if (response.IsSuccessStatusCode)
        {
            var fileName = $&quot;myFile_{Guid.NewGuid()}.pdf&quot;;
            var cd = new System.Net.Mime.ContentDisposition
            {
                FileName = fileName,
                Inline = false,
            };

            Response.Headers.Add(&quot;Content-Disposition&quot;, cd.ToString());
            var mediaType = response.Content.Headers.ContentType.MediaType;

            await response.Content.CopyToAsync(Response.Body);

            return new EmptyResult();
        }

        return NotFound();
    }
}

huangapple
  • 本文由 发表于 2023年4月13日 18:58:33
  • 转载请务必保留本文链接:https://go.coder-hub.com/76004620.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定