如何在 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)的主要内容,如果未能解决你的问题,请参考以下文章
iText 7 从 Asp.Net WebApi 返回 Pdf