web-dev-qa-db-ja.com

ASP.NET Web APIでコントローラからバイナリファイルを返す

私はバイナリファイル、主に.cab.exeファイルを提供するASP.NET MVCの新しいWebAPIを使ったWebサービスに取り組んでいます。

次のコントローラメソッドはうまくいくようです。つまり、ファイルを返しますが、コンテンツタイプを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"));
}

これを行うより良い方法はありますか?

295
Josh Earl

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に関して注意すべきことがいくつかあります。

  • Web APIは、クライアントにデータを送り返すためにコントローラメソッドのresultを処理するときにアクセスできる必要があるため、stream.Dispose()を呼び出してはいけません。したがって、using (var stream = …)ブロックを使用しないでください。 Web APIはあなたに代わってストリームを処理します。

  • ストリームの現在位置が0(ストリームのデータの先頭)に設定されていることを確認してください。上記の例では、ファイルを開いたばかりなのでこれは与えられています。ただし、他のシナリオ(最初にバイナリデータをMemoryStreamに書き込むときなど)では、必ずstream.Seek(0, SeekOrigin.Begin);を設定するかstream.Position = 0;を設定してください。

  • ファイルストリームでは、明示的に FileAccess.Read permissionを指定すると、Webサーバーでのアクセス権の問題を防ぐのに役立ちます。 IISアプリケーションプールアカウントには、多くの場合、wwwrootに対する読み取り/一覧表示/実行アクセス権のみが付与されています。

479
carlosfigueira

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"/>
128
Ronnie Overby

提案された解決策はうまくいくが、応答ストリームが適切にフォーマットされた状態で、コントローラからバイト配列を返す別の方法がある。

  • リクエストでは、ヘッダー「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 []を返すほうがテストしやすいので、この解決策をお勧めします。ただし、もう1つのソリューションでは、 "application/octet-stream"以外の他のコンテンツタイプを返す場合は、より細かく制御できます(たとえば "image/gif")。

8
Eric Boumendil

承認された回答のメソッドを使用して非常に大きいファイルをダウンロードする際にAPIが複数回呼び出される問題がある場合は、応答のバッファリングをtrueに設定してください。System.Web.HttpContext.Current.Response.Buffer = true;

これにより、バイナリコンテンツ全体がクライアントに送信される前にサーバー側で確実にバッファリングされます。それ以外の場合は、コントローラに複数の要求が送信されていることがわかります。正しく処理しないと、ファイルが破損します。

7
JBA

あなたが使っているオーバーロードは、シリアライゼーションフォーマッタの列挙を設定します。次のようにコンテンツタイプを明示的に指定する必要があります。

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

あなたが試すことができます

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

.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では、このプロセスは so 以前のバージョンの.NETよりもはるかに簡単です。つまり、応答タイプ、コンテンツ、ヘッダーなどの設定はありません。

また、もちろんファイルのMIMEタイプと拡張子は個々のニーズによって異なります。

参照: SO回答の投稿 @NKosiさん

2
KMJungersen