我正在使用 ASP.NET MVC 的新 WebAPI 开发一个 Web 服务,它将提供二进制文件,主要是 .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"));
}
有一个更好的方法吗?
尝试使用将其 Content
属性设置为 StreamContent
的简单 HttpResponseMessage
:
// 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 在处理控制器方法的结果以将数据发送回客户端时仍然需要能够访问它。因此,不要使用 using (var stream = ...) 块。 Web API 将为您处理流。
确保流的当前位置设置为 0(即流数据的开头)。在上面的示例中,这是给定的,因为您刚刚打开了文件。但是,在其他情况下(例如当您第一次将一些二进制数据写入 MemoryStream 时),请确保 stream.Seek(0, SeekOrigin.Begin);或设置 stream.Position = 0;
对于文件流,明确指定 FileAccess.Read 权限有助于防止 Web 服务器上的访问权限问题; IIS 应用程序池帐户通常只被授予对 wwwroot 的读取/列出/执行访问权限。
对于 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"/>
async
修饰符并完全删除任务的创建:gist.github.com/ronnieoverby/ae0982c7832c531a9022
对于使用 .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 @NKosi
虽然建议的解决方案工作正常,但还有另一种方法可以从控制器返回字节数组,响应流格式正确:
在请求中,设置标头“Accept: application/octet-stream”。
服务器端,添加媒体类型格式化程序以支持此 mime 类型。
不幸的是,WebApi 不包含任何“application/octet-stream”的格式化程序。在 GitHub 上有一个实现:BinaryMediaTypeFormatter(有一些小的修改使其适用于 webapi 2,方法签名已更改)。
您可以将此格式化程序添加到您的全局配置中:
HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));
如果请求指定了正确的 Accept 标头,WebApi 现在应该使用 BinaryMediaTypeFormatter
。
我更喜欢这种解决方案,因为返回 byte[] 的动作控制器更易于测试。但是,如果您想返回“application/octet-stream”以外的其他内容类型(例如“image/gif”),则其他解决方案允许您进行更多控制。
对于在使用接受的答案中的方法下载相当大的文件时多次调用 API 的问题的任何人,请将响应缓冲设置为 true System.Web.HttpContext.Current.Response.Buffer = true;
这可以确保整个二进制内容在发送到客户端之前在服务器端进行缓冲。否则你会看到多个请求被发送到控制器,如果你没有正确处理它,文件将会损坏。
您使用的重载设置了序列化格式化程序的枚举。您需要明确指定内容类型,例如:
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
Content Type: application/json
。如果我在返回 httpResponseMessage
响应之前中断,则 Content Type
似乎设置正确。还有什么想法吗?
你可以试试
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
您可以尝试以下代码片段
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
希望它对你有用。
using
添加到结果 (HttpResponseMessage
) 或流本身,因为它们仍将在方法之外使用。正如@Dan 提到的,它们在向客户端发送响应后由框架处理。