在ASP.NET Web API中从控制器返回二进制文件
我正在使用ASP.NET MVC的新WebAPI开发一个web服务,它将提供二进制文件,主要是在ASP.NET Web API中从控制器返回二进制文件,asp.net,asp.net-mvc,asp.net-web-api,Asp.net,Asp.net Mvc,Asp.net Web Api,我正在使用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";
.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"));
}
公共HttpResponseMessage帖子(字符串版本、字符串环境、字符串文件类型)
{
var path=@“C:\Temp\test.exe”;
var stream=新文件流(路径,FileMode.Open);
返回新的HttpResponseMessage(流,新的MediaTypeHeaderValue(“应用程序/八位字节流”);
}
有更好的方法吗?您正在使用的重载设置序列化格式化程序的枚举。您需要明确指定内容类型,如:
httpResponseMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
尝试使用一个将其内容属性设置为:
关于所使用的流
需要注意的几点:
- 您不能调用
stream.Dispose()
,因为Web API在处理控制器方法的结果时仍然需要能够访问它才能将数据发送回客户端。因此,不要使用using(var stream=…)
块。Web API将为您处理流
- 确保流的当前位置设置为0(即流数据的开始)。在上面的示例中,这是给定的,因为您刚刚打开了文件。但是,在其他情况下(例如,当您第一次将一些二进制数据写入
MemoryStream
),请确保stream.Seek(0,SeekOrigin.Begin)代码>或设置stream.Position=0代码>
- 对于文件流,明确指定权限有助于防止web服务器上的访问权限问题;IIS应用程序池帐户通常只被授予对wwwroot的读取/列出/执行访问权限
你可以试试
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
对于WebAPI2,您可以实现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);
}
}
这里有一种方法可以告诉IIS忽略带有扩展名的请求,这样请求就会发送到控制器:
[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);
}
<!-- web.config -->
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
虽然建议的解决方案工作正常,但还有另一种方法可以从控制器返回字节数组,并正确格式化响应流:
- 在请求中,设置标题“Accept:application/octet stream”
- 服务器端,添加媒体类型格式化程序以支持此mime类型
不幸的是,WebApi不包括任何“应用程序/八位字节流”的格式化程序。GitHub上有一个实现:(为了使它适用于webapi 2,进行了一些小的修改,方法签名发生了变化)
您可以将此格式化程序添加到全局配置中:
HttpConfiguration config;
// ...
config.Formatters.Add(new BinaryMediaTypeFormatter(false));
如果请求指定了正确的Accept头,WebApi现在应该使用BinaryMediaTypeFormatter
我更喜欢这种解决方案,因为返回字节[]的动作控制器更易于测试。不过,如果您想返回除“应用程序/八位字节流”(例如“图像/gif”)之外的其他内容类型,另一种解决方案允许您进行更多控制。如果任何人在使用接受答案中的方法下载相当大的文件时遇到多次调用API的问题,请将响应缓冲设置为true
System.Web.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");
}
[HttpGet(“GetReportData/{year}”)]
公共异步任务GetReportData(整年)
{
//在内存中呈现Excel文档并以字节[]返回
Byte[]file=等待此操作。_reportDao.renderreportasecel(年份);
返回文件(文件“application/vnd.openxmlformats”、“fileName.xlsx”);
}
这个例子被简化了,但应该能让人明白这一点。在.NET Core中,此过程比以前的.NET版本简单得多,即不设置响应类型、内容、标题等
当然,文件和扩展名的MIME类型将取决于个人需要
参考:通过@NKosi您可以尝试以下代码片段
httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
希望它对您有用。谢谢您的回复。我试过了,我仍然在Fiddler中看到内容类型:application/json
。如果在返回httpResponseMessage
响应之前中断,则内容类型似乎设置正确。还有什么想法吗?你知道小溪什么时候关闭吗?我假设框架最终调用了HttpResponseMessage.Dispose(),后者反过来调用HttpResponseMessage.Content.Dispose(),从而有效地关闭了stream.Steve-您是正确的,我通过向FileStream.Dispose添加断点并运行此代码进行了验证。框架调用HttpResponseMessage.Dispose,它调用StreamContent.Dispose,它调用FileStream.Dispose。您不能使用
向结果(HttpResponseMessage
)或流本身添加,因为它们仍然会在方法外部使用。正如@Dan所提到的,在框架将响应发送给客户端之后,它们将由框架处理。@B.ClayShannon是的,就是这样。就客户端而言,它只是HTTP响应内容中的一堆字节。客户端可以选择如何处理这些字节,包括将其保存到本地文件。@carlosfigueira,您好,您知道如何在发送完所有字节后删除该文件吗?回答得很好,并非总是这样,代码在粘贴后以及针对不同的情况(不同的文件)运行。@JonyAdamit谢谢。我认为另一种选择是放置httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");