【问题标题】:Returning binary file from controller in ASP.NET Web API从 ASP.NET Web API 中的控制器返回二进制文件
【发布时间】:2012-03-21 10:21:38
【问题描述】:

我正在使用 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"));
}

有没有更好的方法来做到这一点?

【问题讨论】:

  • 任何想要通过 web api 和 IHTTPActionResult 的流返回字节数组的人,然后在这里查看:nodogmablog.bryanhogan.net/2017/02/…
  • // 使用 System.IO; // 使用 System.Net.Http; // 使用 System.Net.Http.Headers; public HttpResponseMessage Post(string version, string environment, string filetype) { var path = @"C:\Temp\test.exe"; HttpResponseMessage 结果 = 新的 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");返回结果; }

标签: asp.net asp.net-mvc asp.net-web-api


【解决方案1】:

你可以试试下面的代码sn-p

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

希望它对你有用。

【讨论】:

    【解决方案2】:

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

    • 在请求中,设置标头“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”),其他解决方案可以让您进行更多控制。

    【讨论】:

      【解决方案3】:

      对于使用 .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

      【讨论】:

      • 请注意,如果它是一张图片并且您希望它可以在具有直接 URL 访问权限的浏览器中查看,则不要提供文件名。
      【解决方案4】:

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

      【讨论】:

      • 不错的答案,并不总是 SO 代码在粘贴后运行并针对不同的情况(不同的文件)。
      • @JonyAdamit 谢谢。我认为另一种选择是在方法签名上放置 async 修饰符并完全删除任务的创建:gist.github.com/ronnieoverby/ae0982c7832c531a9022
      • 只是提醒任何通过这个运行的 IIS7+ 的人。 runAllManagedModulesForAllRequests 现在可以是omitted
      • @BendEg 好像有一次我检查了源代码并且确实如此。它应该是有道理的。由于无法控制框架的来源,这个问题的任何答案都可能随着时间而改变。
      • 实际上已经有一个内置的 FileResult(甚至是 FileStreamResult)类。
      【解决方案5】:

      尝试使用简单的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.Dispose(),然后调用 HttpResponseMessage.Content.Dispose() 有效地关闭流。
      • Steve - 你是对的,我通过向 FileStream.Dispose 添加断点并运行此代码来验证。框架调用HttpResponseMessage.Dispose,后者调用StreamContent.Dispose,后者调用FileStream.Dispose。
      • 您不能真正将 using 添加到结果 (HttpResponseMessage) 或流本身,因为它们仍将在方法之外使用。正如@Dan 提到的,它们在向客户端发送响应后由框架处理。
      • @B.ClayShannon 是的,就是这样。就客户端而言,它只是 HTTP 响应内容中的一堆字节。客户端可以随意处理这些字节,包括将其保存到本地文件。
      • @carlosfigueira,你好,你知道字节发送完后如何删除文件吗?
      【解决方案6】:

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

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

      【讨论】:

      【解决方案7】:

      你可以试试

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

      【讨论】:

        【解决方案8】:

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

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

        【讨论】:

        • 感谢您的回复。我试过了,我仍然在 Fiddler 中看到Content Type: application/json。如果我在返回 httpResponseMessage 响应之前中断,则 Content Type 似乎设置正确。还有什么想法吗?
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-03-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多