返回二进制文件从控制器在ASP。NET Web API

我正在使用ASP开发一个web服务。NET MVC的新WebAPI将提供二进制文件,主要是.cab.exe文件。

下面的控制器方法似乎可以工作,这意味着它返回一个文件,但它将内容类型设置为application/json:

public HttpResponseMessage<Stream> Post(string version, string environment, string filetype)
{
var path = @"C:\Temp\test.exe";
var stream = new FileStream(path, FileMode.Open);
return new HttpResponseMessage<Stream>(stream, new MediaTypeHeaderValue("application/octet-stream"));
}

还有更好的办法吗?

318874 次浏览

您正在使用的重载设置序列化格式化程序的枚举。你需要像这样显式地指定内容类型:

httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

尝试使用简单的HttpResponseMessage,将其Content属性设置为StreamContent:

// using System.IO;
// using System.Net.Http;
// using System.Net.Http.Headers;


public HttpResponseMessage Post(string version, string environment,
string filetype)
{
var path = @"C:\Temp\test.exe";
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType =
new MediaTypeHeaderValue("application/octet-stream");
return result;
}

关于使用的stream,有几件事需要注意:

  • 你不能调用stream.Dispose(),因为Web API在处理控制器方法的result以将数据发送回客户端时仍然需要能够访问它。因此,不要使用using (var stream = …)块。Web API将为您处理流。

  • 确保流的当前位置设置为0(即流数据的开始位置)。在上面的例子中,这是给定的,因为您刚刚打开文件。然而,在其他情况下(例如当你第一次将一些二进制数据写入MemoryStream时),请确保stream.Seek(0, SeekOrigin.Begin);或设置stream.Position = 0;

  • 对于文件流,显式指定FileAccess.Read权限可以帮助防止web服务器上的访问权限问题;IIS应用程序池帐户通常只授予wwwroot的读/列表/执行访问权限。

你可以试试

httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");

对于Web API 2,可以实现IHttpActionResult。这是我的:

using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.Http;


class FileResult : IHttpActionResult
{
private readonly string _filePath;
private readonly string _contentType;


public FileResult(string filePath, string contentType = null)
{
if (filePath == null) throw new ArgumentNullException("filePath");


_filePath = filePath;
_contentType = contentType;
}


public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
var response = new HttpResponseMessage(HttpStatusCode.OK)
{
Content = new StreamContent(File.OpenRead(_filePath))
};


var contentType = _contentType ?? MimeMapping.GetMimeMapping(Path.GetExtension(_filePath));
response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);


return Task.FromResult(response);
}
}

然后在你的控制器中像这样:

[Route("Images/{*imagePath}")]
public IHttpActionResult GetImage(string imagePath)
{
var serverPath = Path.Combine(_rootPath, imagePath);
var fileInfo = new FileInfo(serverPath);


return !fileInfo.Exists
? (IHttpActionResult) NotFound()
: new FileResult(fileInfo.FullName);
}

这里有一种方法,你可以告诉IIS忽略带有扩展名的请求,这样请求就会到达控制器:

<!-- web.config -->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>

虽然建议的解决方案工作得很好,但还有另一种方法可以从控制器返回一个字节数组,并正确格式化响应流:

  • 在请求中,设置报头“Accept: application/octet-stream”。
  • 服务器端,添加一个媒体类型格式化器来支持此mime类型。

不幸的是,WebApi不包括任何用于“应用程序/八字节流”的格式化程序。在GitHub上有一个实现:BinaryMediaTypeFormatter(有一些小的调整,使其适用于webapi 2,方法签名更改)。

你可以在全局配置中添加这个格式化器:

HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));

如果请求指定了正确的Accept报头,WebApi现在应该使用BinaryMediaTypeFormatter

我更喜欢这个解决方案,因为动作控制器返回字节[]更适合测试。不过,如果你想返回“application/octet-stream”以外的其他内容类型(例如“image/gif”),另一种解决方案允许你进行更多的控制。

对于任何在使用接受的答案中的方法下载一个相当大的文件时,API被多次调用的问题,请将响应缓冲设置为true

. httpcontext . current . response . buffer = true

这确保在将整个二进制内容发送到客户端之前,在服务器端对其进行了缓冲。否则,您将看到多个请求被发送到控制器,如果您没有正确处理它,文件将被损坏。

对于使用。net Core的用户:

你可以在API控制器方法中使用IActionResult接口,就像这样。

[HttpGet("GetReportData/{year}")]
public async Task<IActionResult> GetReportData(int year)
{
// Render Excel document in memory and return as Byte[]
Byte[] file = await this._reportDao.RenderReportAsExcel(year);


return File(file, "application/vnd.openxmlformats", "fileName.xlsx");
}

这个例子很简单,但应该能让人理解。在。net Core中,这个过程所以比以前版本的。net简单得多——即不设置响应类型、内容、报头等。

当然,文件和扩展名的MIME类型也取决于个人需求。

SO Post Answer by @NKosi

您可以尝试下面的代码片段

httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");

希望它对你有用。