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


323

私は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"));
}

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


2
:ここを参照し、Web APIとIHTTPActionResult経由でストリームを経由してバイト配列を返したい土地誰でもnodogmablog.bryanhogan.net/2017/02/...
IbrarMumtaz

回答:


516

シンプルを使用してみてください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()コントローラメソッドを処理resultしてクライアントにデータを送信するときにWeb APIがアクセスできるようにする必要があるため、を呼び出さないでください。したがって、using (var stream = …)ブロックは使用しないでください。Web APIがストリームを破棄します。

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

  • ファイルストリームでは、FileAccess.Read権限を明示的に指定することで、Webサーバーでのアクセス権の問題を防ぐことができます。IISアプリケーションプールアカウントには、wwwrootへの読み取り/一覧表示/実行アクセス権のみが与えられることがよくあります。


37
ストリームが閉じられるタイミングを知っていますか?フレームワークは最終的にHttpResponseMessage.Dispose()を呼び出し、次にHttpResponseMessage.Content.Dispose()を呼び出してストリームを効果的に閉じていると想定しています。
スティーブギディ

41
スティーブ-正解です。FileStream.Disposeにブレークポイントを追加し、このコードを実行して確認しました。フレームワークは、HttpResponseMessage.Disposeを呼び出します。HttpResponseMessage.Disposeは、StreamContent.Disposeを呼び出し、FileStream.Disposeを呼び出します。
Dan Gartner

15
メソッドの外でも使用されるためusing、結果(HttpResponseMessage)またはストリーム自体にa を実際に追加することはできません。@Danが述べたように、クライアントへの応答の送信が完了すると、フレームワークによって破棄されます。
カルロスフィゲイラ2013年

2
@ B.ClayShannonはい、それはそれについてです。クライアントに関する限り、それはHTTP応答のコンテンツの単なるバイトの集まりです。クライアントは、ローカルファイルへの保存を含め、それらのバイトを何でも選択できます。
カルロスフィゲイラ2014

5
@carlosfigueira、こんにちは、バイトがすべて送信された後にファイルを削除する方法を知っていますか?
Zach

137

以下のための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に拡張機能付きのリクエストを無視して、リクエストがコントローラーに到達するようにする方法の1つです。

<!-- web.config -->
<system.webServer>
  <modules runAllManagedModulesForAllRequests="true"/>

1
いい答えですが、貼り付け直後やさまざまなケース(異なるファイル)でSOコードが実行されるとは限りません。
Krzysztof Morcinek 2014年

1
@JonyAdamitありがとう。別のオプションはasync、メソッドシグネチャに修飾子を配置し、タスクの作成を完全に削除することだと思います。gist.github.com
Ronnie Overby

4
これを実行しているIIS7 +を使用している人の前兆です。runAllManagedModulesForAllRequestsを省略できるようになりました。
インデックス

1
@BendEgかつて私がソースをチェックしたようですが、実際にはそうでした。そして、そうすべきだというのは理にかなっています。フレームワークのソースを制御できないため、この質問に対する答えは時間とともに変化する可能性があります。
ロニーオーバーバイ

1
実際には、組み込みのFileResult(さらにはFileStreamResult)クラスが既に存在します。
BrainSlugs83

12

.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のコアでは、このプロセスがあるので、つまり無応答タイプを設定、コンテンツ、ヘッダ、など-はるかに単純.NETの以前のバージョンに比べて

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

参照:@NKosiによるSO投稿回答


1
ただ注意してください。それが画像であり、直接URLアクセスでブラウザで表示できるようにしたい場合は、ファイル名を指定しないでください。
プルート

9

提案されたソリューションは正常に機能しますが、応答ストリームが適切にフォーマットされた、コントローラーからバイト配列を返す別の方法があります。

  • リクエストで、ヘッダー「Accept:application / octet-stream」を設定します。
  • サーバー側で、このMIMEタイプをサポートするメディアタイプフォーマッタを追加します。

残念ながら、WebApiには「application / octet-stream」のフォーマッターは含まれていません。GitHub:BinaryMediaTypeFormatterに実装があります(webapi 2、メソッドシグネチャが変更された場合に機能するようにマイナーな調整があります)。

このフォーマッターをグローバル構成に追加できます。

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

BinaryMediaTypeFormatterリクエストで正しいAcceptヘッダーが指定されている場合、WebApiが使用されるようになりました。

アクションコントローラーがbyte []を返す方がテストしやすいので、このソリューションを選択します。ただし、他のソリューションでは、「application / octet-stream」以外のコンテンツタイプ(「image / gif」など)を返す場合に、より詳細な制御が可能です。


8

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

これにより、バイナリコンテンツ全体がサーバー側でバッファリングされてから、クライアントに送信されます。そうしないと、コントローラに複数のリクエストが送信され、適切に処理しないと、ファイルが破損します。


3
このBufferプロパティは非推奨になりましたBufferOutput。デフォルトはtrueです。
2017年

6

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

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

3
返信いただきありがとうございます。私はこれを試しました、そして私はまだContent Type: application/jsonフィドラーで見ています。Content Type私は返す前に壊れている場合、正しく設定されて表示されますhttpResponseMessage応答を。これ以上のアイデア?
Josh Earl

3

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

httpResponseMessage.Content.Headers.Add("Content-Type", "application/octet-stream");
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.