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

Bla*_*ise 158 c# asp.net asp.net-mvc asp.net-web-api

在常规的MVC控制器中,我们可以用a输出pdf FileContentResult.

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

    return File(stream.GetBuffer(), "application/pdf", "test.pdf");
}
Run Code Online (Sandbox Code Playgroud)

但是我们怎样才能把它变成一个ApiController

[HttpPost]
public IHttpActionResult Test(TestViewModel vm)
{
     //...
     return Ok(pdfOutput);
}
Run Code Online (Sandbox Code Playgroud)

这是我尝试过但它似乎不起作用.

[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);            
}
Run Code Online (Sandbox Code Playgroud)

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

{"Headers":[{"Key":"Content-Type","Value":["application/pdf"]},{"Key":"Content-Length","Value":["152844"]}]}
Run Code Online (Sandbox Code Playgroud)

在SO上有类似的帖子:从ASP.NET Web API中的控制器返回二进制文件 .它讨论输出现有文件.但我无法使用流.

有什么建议?

Bla*_*ise 187

而不是返回的StreamContent作为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;
}
Run Code Online (Sandbox Code Playgroud)

  • 请停止这样做.这种滥用MemoryStream导致代码不可扩展,完全忽略了Streams的目的.想一想:为什么不是所有的东西都只是作为`byte []`缓冲区而被暴露?您的用户可以轻松地运行应用程序内存不足. (15认同)
  • 我试图下载自己生成的excel文件.使用stream.GetBuffer()始终返回损坏的excel.相反,如果我使用stream.ToArray()生成文件没有问题.希望这有助于某人. (7认同)
  • @AlexandrePires这是因为`MemoryStream.GetBuffer()`实际上返回了MemoryStream的缓冲区,它通常大于流的内容(使插入更有效).`MemoryStream.ToArray()`返回截断到内容大小的缓冲区. (4认同)
  • 嗨,谢谢分享,得到一个简单的问题(我猜).我有一个接收httpresponsemessage的C#前端.如何提取流内容并使其可用,以便用户可以将其保存到磁盘或其他东西(我可以获取实际文件)?谢谢! (3认同)
  • 如果上半部分回答了您的问题,请仅将其作为答案发布.下半部分似乎是一个不同的问题 - 发布了一个新问题. (2认同)
  • @makhdumi MemoryStream 的替代品是什么? (2认同)
  • @IanWarburton Streams 应该使用小的(通常为 ~4 KB)缓冲区流式传输到另一个。.NET 方便地为此提供了一种方法,[Stream.CopyTo](https://docs.microsoft.com/en-us/dotnet/api/system.io.stream.copyto?view=netframework-4.7.2) (2认同)

Ogg*_*las 86

如果你想返回,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;
}
Run Code Online (Sandbox Code Playgroud)

  • 显示IHttpActionResult返回类型的好更新.此代码的重构将是调用自定义IHttpActionResult,例如列出的自定义IHttpActionResult:http://stackoverflow.com/questions/23768596/why-is-my-file-not-being-returned-by-a-得到请求从 - 我的Web-API函数 (3认同)

ale*_*eha 41

这个问题对我有帮助.

所以,试试这个:

控制器代码:

[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;          
}
Run Code Online (Sandbox Code Playgroud)

查看Html标记(使用click事件和简单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>
Run Code Online (Sandbox Code Playgroud)

  • 如果您确实从Web服务器上的文件中读取,请务必使用FileShare.Read的重载,否则您可能会遇到正在使用的文件异常. (4认同)

M.S*_*amm 6

我不确定应该责怪哪一部分,但这就是为什么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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

2)如果你有一个新的流看起来好一点,1)如果你的流不是从0开始就更简单


End*_*gin 6

对我来说,这是之间的区别

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

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

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

而第二个正在正确返回文件。

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


Sim*_*ier 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

它可以像这样简单地使用:

public class MyController : ApiController
{
    public IHttpActionResult Get()
    {
        string filePath = GetSomeValidFilePath();
        return new FileResult(filePath);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 找到了此帖Risord的答案:/sf/ask/142920221/。可以使用这一行`var fs = new FileStream(FilePath,FileMode.Open,FileAccess.Read,FileShare.None,4096,FileOptions.DeleteOnClose);`代替`File.OpenRead(FilePath)` (3认同)