提问



我正在使用ASP.NET MVC的新WebAPI处理Web服务,该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"));
}


有一个更好的方法吗?

最佳参考


尝试使用简单的HttpResponseMessage,其Content属性设置为StreamContent:[25] [26]


// 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的读/列/执行访问权限。[27]


其它参考1


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


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"/>

其它参考2


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



  • 在请求中,设置标题Accept:application/octet-stream。

  • 服务器端,添加媒体类型格式化程序以支持此mime类型。



不幸的是,WebApi不包含application/octet-stream的任何格式化程序。 GitHub上有一个实现:BinaryMediaTypeFormatter(有一些小的改编使它适用于webapi 2,方法签名改变了。)[28]


您可以将此格式化程序添加到全局配置中:


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


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


我更喜欢这个解决方案,因为返回byte [[]]的动作控制器更容易测试。但是,如果您想要返回另一种内容类型而不是application/octet-stream(例如image/gif),则另一种解决方案允许您更多地控制。

其它参考3


您正在使用的重载设置了序列化格式化程序的枚举。您需要明确指定内容类型,如:


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

其它参考4


对于在使用接受的答案中的方法下载相当大的文件时多次调用API问题的任何人,请将响应缓冲设置为true
    System.Web.HttpContext.Current.Response.Buffer=true;


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

其它参考5


你可以试试


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