【问题标题】:How to return a file (FileContentResult) in ASP.NET WebAPI如何在 ASP.NET WebAPI 中返回文件 (FileContentResult)
【发布时间】:2014-11-20 05:49:33
【问题描述】:

在常规的 MVC 控制器中,我们可以输出带有 FileContentResult 的 pdf。

public FileContentResult Test(TestViewModel vm)
{
    var stream = new MemoryStream();
    //... add content to the stream.

    return File(stream.GetBuffer(), "application/pdf", "test.pdf");
}

但是我们怎样才能把它改成ApiController呢?

[HttpPost]
public IHttpActionResult Test(TestViewModel vm)
{
     //...
     return Ok(pdfOutput);
}

这是我尝试过的方法,但似乎不起作用。

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();
    //...
    var content = new StreamContent(stream);
    content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    content.Headers.ContentLength = stream.GetBuffer().Length;
    return Ok(content);            
}

在浏览器中显示的返回结果是:

{"Headers":[{"Key":"Content-Type","Value":["application/pdf"]},{"Key":"Content-Length","Value":["152844"]}]}

在 SO 上有一个类似的帖子:Returning binary file from controller in ASP.NET Web API 。它谈论输出现有文件。但我无法让它与流一起使用。

有什么建议吗?

【问题讨论】:

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


【解决方案1】:

This 问题帮助了我。

那么,试试这个:

控制器代码:

[HttpGet]
public HttpResponseMessage Test()
{
    var path = System.Web.HttpContext.Current.Server.MapPath("~/Content/test.docx");;
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    var stream = new FileStream(path, FileMode.Open);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
    result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(path);
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    result.Content.Headers.ContentLength = stream.Length;
    return result;          
}

查看 Html 标记(带有点击事件和简单的 url):

<script type="text/javascript">
    $(document).ready(function () {
        $("#btn").click(function () {
            // httproute = "" - using this to construct proper web api links.
            window.location.href = "@Url.Action("GetFile", "Data", new { httproute = "" })";
        });
    });
</script>


<button id="btn">
    Button text
</button>

<a href=" @Url.Action("GetFile", "Data", new { httproute = "" }) ">Data</a>

【讨论】:

  • 这里您使用FileStream 处理服务器上的现有文件。它与MemoryStream 有点不同。但感谢您的意见。
  • 如果您确实从 Web 服务器上的文件读取,请务必使用 FileShare.Read 的重载,否则您可能会遇到文件正在使用异常。
  • 如果换成内存流不行吗?
  • @JeremyBell 这只是一个简化的例子,这里没有人谈论生产和故障安全版本。
  • @Blaise 请参阅下文,了解为什么此代码适用于FileStream,但无法使用MemoryStream。这基本上与Stream的Position有关。
【解决方案2】:

我可以使它与ByteArrayContent 一起工作,而不是将StreamContent 作为Content 返回。

[HttpGet]
public HttpResponseMessage Generate()
{
    var stream = new MemoryStream();
    // processing the stream.

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.ToArray())
    };
    result.Content.Headers.ContentDisposition =
        new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "CertificationCard.pdf"
    };
    result.Content.Headers.ContentType =
        new MediaTypeHeaderValue("application/octet-stream");

    return result;
}

【讨论】:

  • 如果上半部分回答了您的问题,请仅将其作为答案发布。后半部分似乎是一个不同的问题 - 为此发布一个新问题。
  • 嗨,谢谢分享,有一个简单的问题(我猜)。我有一个接收 httpresponsemessage 的 C# 前端。如何提取流内容并使其可用,以便用户可以将其保存到磁盘或其他东西(并且我可以获得实际文件)?谢谢!
  • 我试图下载一个自生成的 excel 文件。使用 stream.GetBuffer() 总是返回损坏的 excel。相反,如果我使用 stream.ToArray() 则文件生成没有问题。希望这对某人有所帮助。
  • @AlexandrePires 那是因为MemoryStream.GetBuffer() 实际上返回了 MemoryStream 的缓冲区,该缓冲区通常大于流的内容(以提高插入效率)。 MemoryStream.ToArray() 返回截断到内容大小的缓冲区。
  • 请停止这样做。这种对 MemoryStream 的滥用导致代码不可扩展,并且完全忽略了 Streams 的目的。想一想:为什么不把所有东西都暴露为byte[] 缓冲区呢?您的用户可以轻松地在内存不足的情况下运行您的应用程序。
【解决方案3】:

我不确定应该归咎于哪一部分,但这就是为什么 MemoryStream 不适合你:

当您写信给MemoryStream 时,它会增加它的Position 属性。 StreamContent 的构造函数考虑了流的当前Position。所以如果你写入流,然后将它传递给StreamContent,响应将从流末尾的虚无开始。

有两种方法可以正确解决此问题:

  1. 构建内容,写入流

     [HttpGet]
     public HttpResponseMessage Test()
     {
         var stream = new MemoryStream();
         var response = Request.CreateResponse(HttpStatusCode.OK);
         response.Content = new StreamContent(stream);
         // ...
         // stream.Write(...);
         // ...
         return response;
     }
    
  2. 写入流,重置位置,构造内容

     [HttpGet]
     public HttpResponseMessage Test()
     {
         var stream = new MemoryStream();
         // ...
         // stream.Write(...);
         // ...
         stream.Position = 0;
    
         var response = Request.CreateResponse(HttpStatusCode.OK);
         response.Content = new StreamContent(stream);
         return response;
     }
    
  3. 如果你有一个新的 Stream,看起来会好一些,1) 如果你的 Stream 不是从 0 开始,那么会更简单

【讨论】:

  • 这段代码实际上并没有提供任何问题的解决方案,因为它使用了与问题中提到的相同的方法。问题已经表明这不起作用,我可以确认。 return Ok(new StreamContent(stream)) 返回 StreamContent 的 JSON 表示。
  • 更新了代码。这个答案实际上回答了“为什么简单的解决方案适用于 FileStream 而不是 MemoryStream”这个更微妙的问题,而不是如何在 WebApi 中返回文件。
【解决方案4】:

如果你想返回IHttpActionResult,你可以这样做:

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.GetBuffer())
    };
    result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "test.pdf"
    };
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

    var response = ResponseMessage(result);

    return response;
}

【讨论】:

  • 很好的更新以显示 IHttpActionResult 返回类型。此代码的重构是移动调用自定义 IHttpActionResult,例如在以下位置列出的那个:stackoverflow.com/questions/23768596/…
  • 这篇文章展示了一个整洁的单次使用实现。就我而言,上面链接中列出的辅助方法被证明更有帮助
  • 如果您投反对票,请说明原因,否则很难改进答案。
  • 如果你使用ByteArrayContent(Byte[], Int32, Int32)构造函数指定实际大小,我认为你只能使用stream.GetBuffer()new ByteArrayContent(stream.GetBuffer(), 0, checked((int)stream.Length))。内部缓冲区数组可能比实际内容长,因此需要指定内容长度。
【解决方案5】:

对我来说,这就是

var response = Request.CreateResponse(HttpStatusCode.OK, new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

第一个是返回 StringContent 的 JSON 表示: {"Headers":[{"Key":"Content-Type","Value":["application/octet-stream; charset=utf-8"] }]}

当第二个正在正确返回文件时。

似乎 Request.CreateResponse 有一个将字符串作为第二个参数的重载,这似乎是导致 StringContent 对象本身呈现为字符串而不是实际内容的原因。

【讨论】:

    【解决方案6】:

    这是一个将文件内容流式传输出来而不缓冲它的实现(如果文件是大文件,缓冲在 byte[] / MemoryStream 等可能是服务器问题)。

    public class FileResult : IHttpActionResult
    {
        public FileResult(string filePath)
        {
            if (filePath == null)
                throw new ArgumentNullException(nameof(filePath));
    
            FilePath = filePath;
        }
    
        public string FilePath { get; }
    
        public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
        {
            var response = new HttpResponseMessage(HttpStatusCode.OK);
            response.Content = new StreamContent(File.OpenRead(FilePath));
            var contentType = MimeMapping.GetMimeMapping(Path.GetExtension(FilePath));
            response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
            return Task.FromResult(response);
        }
    }
    

    可以这样简单地使用:

    public class MyController : ApiController
    {
        public IHttpActionResult Get()
        {
            string filePath = GetSomeValidFilePath();
            return new FileResult(filePath);
        }
    }
    

    【讨论】:

    • 下载完成后如何删除文件?下载完成时是否有任何钩子需要通知?
    • 好的,答案似乎是实现一个动作过滤器属性,并在 OnActionExecuted 方法中删除文件。
    • 找到这篇帖子 Risord 的回答:stackoverflow.com/questions/2041717/…。可以使用这一行 var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose); 而不是 File.OpenRead(FilePath)
    【解决方案7】:
     var memoryStream = new MemoryStream();
                    await cloudFile.DownloadToStreamAsync(memoryStream);
                    responseMessage.result = "Success";
    
                    var contentType = "application/octet-stream";
                
                    **using (var stream = new MemoryStream())
                    {                    
                        return File(memoryStream.GetBuffer(), contentType, "Cartage.pdf");
                    }**
    

    【讨论】:

    • 请添加更多详细信息以扩展您的答案,例如工作代码或文档引用。
    猜你喜欢
    • 2021-11-26
    • 1970-01-01
    • 2012-10-27
    • 1970-01-01
    • 2016-04-06
    • 2015-11-26
    • 1970-01-01
    • 2018-06-11
    相关资源
    最近更新 更多