如何在 ASP.NET WebAPI 中返回文件 (FileContentResult)

Posted

技术标签:

【中文标题】如何在 ASP.NET WebAPI 中返回文件 (FileContentResult)【英文标题】:How to return a file (FileContentResult) in ASP.NET WebAPI 【发布时间】: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 。它谈论输出现有文件。但我无法让它与流一起使用。

有什么建议吗?

【问题讨论】:

这篇文章帮助了我:***.com/a/23768883/585552 【参考方案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,响应将从流末尾的虚无开始。

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

    构建内容,写入流

     [HttpGet]
     public HttpResponseMessage Test()
     
         var stream = new MemoryStream();
         var response = Request.CreateResponse(HttpStatusCode.OK);
         response.Content = new StreamContent(stream);
         // ...
         // stream.Write(...);
         // ...
         return response;
     
    

    写入流,重置位置,构造内容

     [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;
     
    

    如果你有一个新的 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,例如在以下位置列出的那个:***.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 的回答:***.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");
                **

【讨论】:

请添加更多详细信息以扩展您的答案,例如工作代码或文档引用。

以上是关于如何在 ASP.NET WebAPI 中返回文件 (FileContentResult)的主要内容,如果未能解决你的问题,请参考以下文章

如何从 ASP.net 5 web api 返回文件

ASP.NET Core WebApi返回结果统一包装实践

iText 7 从 Asp.Net WebApi 返回 Pdf

在 ASP.NET 中使用 WebAPI 或 MVC 返回 JSON

在 asp.net WebAPI 中返回数组对象

如何在本地服务结构集群中部署和访问来宾可执行文件(ASP.NET OWIN 自托管 webapi 应用程序)