ASP.NET WebAPIでファイル(FileContentResult)を返す方法


173

通常のMVCコントローラーでは、pdfをで出力できますFileContentResult

public FileContentResult Test(TestViewModel vm)
{
    var stream = new MemoryStream();
    //... add content to the stream.

    return File(stream.GetBuffer(), "application/pdf", "test.pdf");
}

しかし、どうすればそれをに変更できApiControllerますか?

[HttpPost]
public IHttpActionResult Test(TestViewModel vm)
{
     //...
     return Ok(pdfOutput);
}

これが私が試したものですが、うまくいかないようです。

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();
    //...
    var content = new StreamContent(stream);
    content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
    content.Headers.ContentLength = stream.GetBuffer().Length;
    return Ok(content);            
}

ブラウザに表示される返される結果は次のとおりです。

{"Headers":[{"Key":"Content-Type","Value":["application/pdf"]},{"Key":"Content-Length","Value":["152844"]}]}

また、SO:ASP.NET Web APIのコントローラーからバイナリファイルを返すという同様の投稿があります 。既存のファイルの出力について話します。しかし、ストリームで動作させることはできませんでした。

助言がありますか?


1
この投稿が役に立ち
Greg

回答:


199

代わりに返すStreamContentようContentに、私はそれをして動作させることができますByteArrayContent

[HttpGet]
public HttpResponseMessage Generate()
{
    var stream = new MemoryStream();
    // processing the stream.

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.ToArray())
    };
    result.Content.Headers.ContentDisposition =
        new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "CertificationCard.pdf"
    };
    result.Content.Headers.ContentType =
        new MediaTypeHeaderValue("application/octet-stream");

    return result;
}

2
上半分が質問に答える場合は、それだけを回答として投稿してください。後半は別の質問のようです-そのための新しい質問を投稿してください。
gunr2171 2014

3
こんにちは、共有してくれてありがとう、簡単な質問をした(多分)。httpresponsemessageを受信するC#フロントエンドがあります。streamcontentを抽出して利用できるようにするには、ユーザーがそれをディスクなどに保存できるようにします(実際のファイルを取得できます)。ありがとう!
ロナルド

7
自己生成されたExcelファイルをダウンロードしようとしました。stream.GetBuffer()を使用すると、常に破損したExcelが返されました。代わりにstream.ToArray()を使用すると、ファイルは問題なく生成されます。これが誰かを助けることを願っています。
afnpires

4
@AlexandrePiresこれは、MemoryStream.GetBuffer()実際にはストリームのコンテンツよりも大きいMemoryStreamのバッファを実際に返すためです(挿入を効率的にするため)。MemoryStream.ToArray()コンテンツサイズに切り捨てられたバッファを返します。
M.Stramm

19
これをやめてください。このようなMemoryStreamの乱用は、スケーラブルでないコードを引き起こし、Streamsの目的を完全に無視します。考えてみてください。なぜすべてがbyte[]代わりにバッファとして公開されないのですか?ユーザーはメモリ不足でアプリケーションを簡単に実行できます。
makhdumi 2017

97

返品したい場合IHttpActionResultは、次のようにしてください。

[HttpGet]
public IHttpActionResult Test()
{
    var stream = new MemoryStream();

    var result = new HttpResponseMessage(HttpStatusCode.OK)
    {
        Content = new ByteArrayContent(stream.GetBuffer())
    };
    result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
    {
        FileName = "test.pdf"
    };
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");

    var response = ResponseMessage(result);

    return response;
}

3
IHttpActionResultの戻り値の型を表示するための適切な更新。:このコードのリファクタリングは、このような時に記載されている一つとして、カスタムIHttpActionResultを呼び出すに移動するだろうstackoverflow.com/questions/23768596/...
ジョシュ

この投稿は、きちんと整理された使い捨ての実装を示しています。私の場合、上記のリンクに記載されているヘルパーメソッドの方が便利
でした

45

この質問は私を助けました。

だから、これを試してください:

コントローラーコード:

[HttpGet]
public HttpResponseMessage Test()
{
    var path = System.Web.HttpContext.Current.Server.MapPath("~/Content/test.docx");;
    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    var stream = new FileStream(path, FileMode.Open);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
    result.Content.Headers.ContentDisposition.FileName = Path.GetFileName(path);
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
    result.Content.Headers.ContentLength = stream.Length;
    return result;          
}

HTMLマークアップの表示(クリックイベントと単純なURLを使用):

<script type="text/javascript">
    $(document).ready(function () {
        $("#btn").click(function () {
            // httproute = "" - using this to construct proper web api links.
            window.location.href = "@Url.Action("GetFile", "Data", new { httproute = "" })";
        });
    });
</script>


<button id="btn">
    Button text
</button>

<a href=" @Url.Action("GetFile", "Data", new { httproute = "" }) ">Data</a>

1
ここではFileStream、サーバー上の既存のファイルに使用しています。とは少し異なりMemoryStreamます。しかし、入力してくれてありがとう。
ブレーズ

4
Webサーバー上のファイルから読み取る場合は、必ずFileShare.Readのオーバーロードを使用してください。そうしないと、ファイル使用中の例外が発生する可能性があります。
ジェレミーベル

あなたがそれをメモリストリームで置き換えると、それは機能しませんか?
アレハ2014

@JeremyBellは単なる簡略化された例であり、プロダクションとフェイルセーフバージョンについては誰もここで話しません。
アレハ2014

1
@Blaiseこのコードが機能するFileStreamが失敗する理由については、以下を参照してくださいMemoryStream。基本的には、ストリームと関係がありますPosition
M.Stramm

9

以下は、バッファリングせずにファイルのコンテンツをストリーミングする実装です(byte [] / MemoryStreamなどでのバッファリングは、大きなファイルの場合、サーバーの問題になる可能性があります)。

public class FileResult : IHttpActionResult
{
    public FileResult(string filePath)
    {
        if (filePath == null)
            throw new ArgumentNullException(nameof(filePath));

        FilePath = filePath;
    }

    public string FilePath { get; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        var response = new HttpResponseMessage(HttpStatusCode.OK);
        response.Content = new StreamContent(File.OpenRead(FilePath));
        var contentType = MimeMapping.GetMimeMapping(Path.GetExtension(FilePath));
        response.Content.Headers.ContentType = new MediaTypeHeaderValue(contentType);
        return Task.FromResult(response);
    }
}

次のように簡単に使用できます。

public class MyController : ApiController
{
    public IHttpActionResult Get()
    {
        string filePath = GetSomeValidFilePath();
        return new FileResult(filePath);
    }
}

ダウンロード後にファイルをどのように削除しますか?ダウンロードが完了したときに通知されるフックはありますか?
コスタ2018年

答えは、アクションフィルター属性を実装し、OnActionExecutedメソッドでファイルを削除することです。
コスタ2018年

5
この投稿を見つけたRisordの回答:stackoverflow.com/questions/2041717/…var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose);代わりにこの行を使用できますFile.OpenRead(FilePath)
Costa

7

どの部分を非難するかは正確にはわかりMemoryStreamませんが、次の理由で動作しません。

に書き込むとMemoryStream、そのPositionプロパティがインクリメントされます。のコンストラクタはStreamContent、ストリームの現在のPosition。したがって、ストリームに書き込み、それをに渡すとStreamContent、ストリームの最後の何もないところから応答が開始されます。

これを適切に修正するには2つの方法があります。

1)コンテンツを作成し、ストリームに書き込みます

[HttpGet]
public HttpResponseMessage Test()
{
    var stream = new MemoryStream();
    var response = Request.CreateResponse(HttpStatusCode.OK);
    response.Content = new StreamContent(stream);
    // ...
    // stream.Write(...);
    // ...
    return response;
}

2)ストリームへの書き込み、位置のリセット、コンテンツの構築

[HttpGet]
public HttpResponseMessage Test()
{
    var stream = new MemoryStream();
    // ...
    // stream.Write(...);
    // ...
    stream.Position = 0;

    var response = Request.CreateResponse(HttpStatusCode.OK);
    response.Content = new StreamContent(stream);
    return response;
}

2)ストリームが新しい場合は少し良く見える、1)ストリームが0で始まらない場合はよりシンプル


このコードは、質問で述べたのと同じアプローチを使用するため、実際には問題の解決策を提供しません。質問では、これが機能しないことをすでに述べていますが、私はそれを確認できます。return Ok(new StreamContent(stream))は、StreamContentのJSON表現を返します。
Dmytro Zakharov

コードを更新しました。この答えは実際には、WebApiでファイルを返す方法ではなく、「単純なソリューションがMemoryStreamではなくFileStreamで機能する理由」というより微妙な質問に答えます。
M.Stramm 2016

3

私にとってそれは違いでした

var response = Request.CreateResponse(HttpStatusCode.OK, new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

そして

var response = Request.CreateResponse(HttpStatusCode.OK);
response.Content = new StringContent(log, System.Text.Encoding.UTF8, "application/octet-stream");

最初のものは、StringContentのJSON表現を返していました:{"Headers":[{"Key": "Content-Type"、 "Value":["application / octet-stream; charset = utf-8"]}]}

2つ目は適切なファイルを返していました。

Request.CreateResponseには、2番目のパラメーターとして文字列を受け取るオーバーロードがあり、これが実際のコンテンツではなく、StringContentオブジェクト自体を文字列としてレンダリングする原因となったようです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.