Cara mengembalikan file (FileContentResult) di ASP.NET WebAPI

173

Dalam pengontrol MVC biasa, kita dapat menampilkan pdf dengan a FileContentResult.

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

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

Tapi bagaimana kita bisa mengubahnya menjadi ApiController?

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

Inilah yang saya coba tetapi tampaknya tidak berhasil.

[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);            
}

Hasil yang dikembalikan ditampilkan di browser adalah:

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

Dan ada posting serupa di SO: Mengembalikan file biner dari pengontrol di ASP.NET Web API . Ini berbicara tentang keluaran file yang ada. Tetapi saya tidak bisa membuatnya bekerja dengan aliran.

Ada saran?

Blaise
sumber
1
Posting ini membantu saya: stackoverflow.com/a/23768883/585552
Greg

Jawaban:

199

Alih-alih kembali StreamContentsebagai Content, saya dapat membuatnya bekerja dengan 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;
}
Blaise
sumber
2
Jika bagian atas menjawab pertanyaan Anda, harap hanya posting itu sebagai jawaban. Babak kedua tampaknya menjadi pertanyaan yang berbeda - memposting pertanyaan baru untuk itu.
gunr2171
3
Hai, terima kasih sudah berbagi, ada pertanyaan sederhana (saya kira). Saya memiliki ujung depan C # yang menerima httpresponsemessage. Bagaimana cara mengekstrak streamcontent dan membuatnya tersedia sehingga pengguna dapat menyimpannya ke disk atau sesuatu (dan saya bisa mendapatkan file yang sebenarnya)? Terima kasih!
Ronald
7
Saya mencoba mengunduh file excel yang dihasilkan sendiri. Menggunakan stream.GetBuffer () selalu mengembalikan excel yang rusak. Jika sebaliknya saya menggunakan stream.ToArray () file dihasilkan tanpa masalah. Semoga ini bisa membantu seseorang.
afnpires
4
@AlexandrePires Itu karena MemoryStream.GetBuffer()sebenarnya mengembalikan buffer MemoryStream, yang biasanya lebih besar dari konten stream (untuk membuat penyisipan menjadi efisien). MemoryStream.ToArray()mengembalikan buffer yang terpotong ke ukuran konten.
M. Strtr
19
Tolong berhenti melakukan ini. Jenis penyalahgunaan MemoryStream ini menyebabkan, kode tidak dapat dihapus dan sepenuhnya mengabaikan tujuan Streams. Pikirkan: mengapa semua tidak hanya diekspos sebagai byte[]buffer saja? Pengguna Anda dapat dengan mudah menjalankan aplikasi Anda dari kehabisan memori.
makhdumi
97

Jika Anda ingin kembali, IHttpActionResultAnda dapat melakukannya seperti ini:

[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;
}
Ogglas
sumber
3
Pembaruan yang bagus untuk menunjukkan jenis pengembalian IHttpActionResult. Sebuah refactor dari kode ini adalah untuk memindahkan panggilan IHttpActionResult kustom seperti yang terdaftar di: stackoverflow.com/questions/23768596/…
Josh
Posting ini menunjukkan implementasi penggunaan tunggal yang rapi dan bagus. Dalam kasus saya, metode pembantu yang tercantum dalam tautan di atas terbukti lebih membantu
hanzolo
45

Pertanyaan ini membantu saya.

Jadi, coba ini:

Kode pengontrol:

[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;          
}

Lihat marka Html (dengan acara klik dan url sederhana):

<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>
aleha
sumber
1
Di sini Anda menggunakan FileStreamfile yang ada di server. Agak sedikit berbeda dari MemoryStream. Tapi terima kasih atas masukannya.
Blaise
4
Jika Anda membaca dari file di server web, pastikan untuk menggunakan kelebihan untuk FileShare.Baca, jika tidak, Anda dapat menemukan file yang digunakan pengecualian.
Jeremy Bell
jika Anda menggantinya dengan aliran memori itu tidak akan berfungsi?
aleha
@ JeremyBell itu hanya contoh sederhana, tidak ada yang berbicara di sini tentang versi produksi dan gagal-aman.
aleha
1
@Blaise Lihat di bawah mengapa kode ini berfungsi FileStreamtetapi gagal dengannya MemoryStream. Ini pada dasarnya ada hubungannya dengan Stream Position.
M. Strtr
9

Berikut ini adalah implementasi yang mengalirkan konten file keluar tanpa buffering (buffering dalam byte [] / MemoryStream, dll. Bisa menjadi masalah server jika itu file besar).

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);
    }
}

Ini bisa digunakan seperti ini:

public class MyController : ApiController
{
    public IHttpActionResult Get()
    {
        string filePath = GetSomeValidFilePath();
        return new FileResult(filePath);
    }
}
Simon Mourier
sumber
Bagaimana Anda menghapus file setelah pengunduhan selesai? Apakah ada kaitan yang harus diberitahukan ketika unduhan selesai?
Costa
ok, jawabannya sepertinya untuk menerapkan atribut filter tindakan dan menghapus file dalam metode OnActionExecuted.
Costa
5
Ditemukan posting ini jawaban Risord: stackoverflow.com/questions/2041717/… . Satu dapat menggunakan baris ini var fs = new FileStream(FilePath, FileMode.Open, FileAccess.Read, FileShare.None, 4096, FileOptions.DeleteOnClose);bukanFile.OpenRead(FilePath)
costa
7

Saya tidak yakin bagian mana yang harus disalahkan, tetapi inilah mengapa MemoryStreamtidak bekerja untuk Anda:

Saat Anda menulis surat MemoryStream, itu menambah Positionproperti itu. Konstruktor StreamContentmemperhitungkan aliran saat ini Position. Jadi jika Anda menulis ke aliran, maka meneruskannya keStreamContent , respons akan mulai dari ketiadaan di akhir aliran.

Ada dua cara untuk memperbaikinya dengan benar:

1) membuat konten, menulis untuk streaming

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

2) menulis untuk streaming, mengatur ulang posisi, membuat konten

[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) terlihat sedikit lebih baik jika Anda memiliki Stream baru, 1) lebih sederhana jika streaming Anda tidak dimulai pada 0

M. Stramm
sumber
Kode ini sebenarnya tidak memberikan solusi untuk masalah, karena menggunakan pendekatan yang sama yang disebutkan dalam pertanyaan. Pertanyaannya sudah menyatakan bahwa ini tidak berhasil, dan saya dapat mengonfirmasi itu. return Ok (StreamContent baru (stream)) mengembalikan representasi JSON dari StreamContent.
Dmytro Zakharov
Diperbarui kodenya. Jawaban ini sebenarnya menjawab pertanyaan yang lebih halus tentang 'mengapa solusi sederhana bekerja dengan FileStream tetapi tidak MemoryStream' daripada bagaimana mengembalikan File di WebApi.
M. Strtr
3

Bagi saya itu adalah perbedaan di antara keduanya

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

dan

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

Yang pertama adalah mengembalikan representasi JSON dari StringContent: {"Header": [{"Key": "Content-Type", "Value": ["application / octet-stream; charset = utf-8"]}]}

Sedangkan yang kedua mengembalikan file yang tepat.

Tampaknya Request.CreateResponse memiliki kelebihan yang mengambil string sebagai parameter kedua dan ini tampaknya yang menyebabkan objek StringContent itu sendiri dirender sebagai string, alih-alih konten yang sebenarnya.

EnderWiggin
sumber