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


173

在常规MVC控制器中,我们可以使用来输出pdf FileContentResult

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上也有类似的文章:从ASP.NET Web API中的控制器返回二进制文件 。它讨论输出现有文件。但是我无法使其与流一起工作。

有什么建议?


1
这篇文章对我有帮助:stackoverflow.com/a/23768883/585552
Greg

Answers:


199

与其返回StreamContentas Content,不如让它使用ByteArrayContent

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

2
如果上半部分回答您的问题,请仅将其发布为答案。下半年似乎是一个不同的问题-为此发布一个新问题。
gunr2171 2014年

3
嗨,感谢分享,我有一个简单的问题。我有一个C#前端,可以接收httpresponsemessage。如何提取流内容并使其可用,以便用户可以将其保存到磁盘或其他内容(并且可以获取实际文件)?谢谢!
罗纳德2015年

7
我正在尝试下载自行生成的Excel文件。使用stream.GetBuffer()总是返回损坏的excel。相反,如果我使用stream.ToArray(),则文件生成没有问题。希望这对某人有帮助。
afnpires'Aug

4
@AlexandrePires这是因为MemoryStream.GetBuffer()实际上返回MemoryStream的缓冲区,该缓冲区通常大于流的内容(以提高插入效率)。MemoryStream.ToArray()返回截断为内容大小的缓冲区。
M.Stramm '16

19
请停止这样做。这种对MemoryStream的滥用会导致无法伸缩的代码,并且完全忽略Streams的目的。想一想:为什么不将所有内容都公开为byte[]缓冲区?您的用户可以轻松地在内存不足的情况下运行您的应用程序。
makhdumi

97

如果您想退货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;
}

3
良好的更新以显示IHttpActionResult返回类型。此代码的重构将是移动调用自定义IHttpActionResult,例如在以下列表中列出的那个:stackoverflow.com/questions/23768596/…–
Josh,

这篇文章演示了一个很好的整洁的一次性使用实现。就我而言,上面链接中列出的辅助方法被证明更有用
hanzolo

45

这个问题帮助了我。

因此,请尝试以下操作:

控制器代码:

[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标记(带有点击事件和简单网址):

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

1
在这里,您正在使用FileStream服务器上的现有文件。与有点不同MemoryStream。但是,谢谢您的投入。
布莱斯(Blaise)2014年

4
如果确实从Web服务器上的文件读取,请确保对FileShare.Read使用重载,否则可能会遇到文件使用异常。
杰里米·贝尔

如果用内存流替换它,将无法正常工作?
aleha 2014年

@JeremyBell只是一个简化的示例,这里没有人谈论生产和故障安全版本。
aleha 2014年

1
@Blaise请参见以下内容,了解为什么此代码可以使用,FileStream但不能使用MemoryStream。它基本上与Stream的有关Position
M.Stramm '16

9

这是一种无需缓冲就可以流式传输文件内容的实现(如果是大文件,则在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方法中删除该文件。
哥斯达黎加'18

5
找到了此帖Risord的答案:stackoverflow.com/questions/2041717/…。一个人可以用这条线var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose);代替File.OpenRead(FilePath)
costa

7

我不确定要归咎于哪一部分,但是这就是为什么MemoryStream对您不起作用的原因:

当您写入时MemoryStream,它会递增Position属性。的构造函数StreamContent考虑了流的current 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;
}

2)如果您有新的Stream,看起来会更好一点; 1)如果您的stream不是从0开始,则会更简单


该代码实际上没有提供任何解决方案,因为它使用的是问题中提到的相同方法。问题已经表明这是行不通的,我可以确认这一点。返回Ok(new StreamContent(stream))返回StreamContent的JSON表示形式。
Dmytro Zakharov,

更新了代码。这个答案实际上回答了一个更微妙的问题:“为什么简单的解决方案对FileStream起作用而不对MemoryStream起作用”,而不是如何在WebApi中返回文件。
M.Stramm '16

3

对我来说,这是

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对象本身呈现为字符串而不是实际内容的原因。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.