Mengembalikan file ke Lihat / Unduh di ASP.NET MVC

304

Saya mengalami masalah pengiriman file yang disimpan dalam database kembali ke pengguna di ASP.NET MVC. Yang saya inginkan adalah tampilan yang mencantumkan dua tautan, satu untuk melihat file dan membiarkan mimetype yang dikirim ke browser menentukan cara penanganannya, dan yang lainnya untuk memaksa unduhan.

Jika saya memilih untuk melihat file yang dipanggil SomeRandomFile.bakdan browser tidak memiliki program yang terkait untuk membuka file jenis ini, maka saya tidak punya masalah dengan itu default ke perilaku unduhan. Namun, jika saya memilih untuk melihat file yang dipanggil SomeRandomFile.pdfatau SomeRandomFile.jpgsaya ingin file tersebut terbuka. Tapi saya juga ingin menjaga tautan unduhan agar tidak bisa memaksa permintaan unduhan terlepas dari jenis file. Apakah ini masuk akal?

Saya telah mencoba FileStreamResultdan berfungsi untuk sebagian besar file, konstruktornya tidak menerima nama file secara default, jadi file yang tidak dikenal diberi nama file berdasarkan URL (yang tidak tahu ekstensi untuk diberikan berdasarkan tipe konten). Jika saya memaksakan nama file dengan menetapkannya, saya kehilangan kemampuan browser untuk membuka file secara langsung dan saya mendapatkan prompt unduhan. Adakah orang lain yang mengalami ini?

Ini adalah contoh dari apa yang saya coba sejauh ini.

//Gives me a download prompt.
return File(document.Data, document.ContentType, document.Name);

//Opens if it is a known extension type, downloads otherwise (download has bogus name and missing extension)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType);

//Gives me a download prompt (lose the ability to open by default if known type)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType) {FileDownloadName = document.Name};

Ada saran?


UPDATE: Pertanyaan-pertanyaan ini tampaknya cocok dengan banyak orang, jadi saya pikir saya akan memposting pembaruan. Peringatan pada jawaban yang diterima di bawah ini yang ditambahkan oleh Oskar mengenai karakter internasional benar-benar valid, dan saya telah memukulnya beberapa kali karena menggunakan ContentDispositionkelas. Saya telah memperbarui implementasi saya untuk memperbaikinya. Sementara kode di bawah ini berasal dari inkarnasi terbaru saya tentang masalah ini dalam aplikasi ASP.NET Core (Kerangka Penuh), itu harus bekerja dengan perubahan minimal dalam aplikasi MVC lama juga karena saya menggunakan System.Net.Http.Headers.ContentDispositionHeaderValuekelas.

using System.Net.Http.Headers;

public IActionResult Download()
{
    Document document = ... //Obtain document from database context

    //"attachment" means always prompt the user to download
    //"inline" means let the browser try and handle it
    var cd = new ContentDispositionHeaderValue("attachment")
    {
        FileNameStar = document.FileName
    };
    Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());

    return File(document.Data, document.ContentType);
}

// an entity class for the document in my database 
public class Document
{
    public string FileName { get; set; }
    public string ContentType { get; set; }
    public byte[] Data { get; set; }
    //Other properties left out for brevity
}
Nick Albrecht
sumber

Jawaban:

430
public ActionResult Download()
{
    var document = ...
    var cd = new System.Net.Mime.ContentDisposition
    {
        // for example foo.bak
        FileName = document.FileName, 

        // always prompt the user for downloading, set to true if you want 
        // the browser to try to show the file inline
        Inline = false, 
    };
    Response.AppendHeader("Content-Disposition", cd.ToString());
    return File(document.Data, document.ContentType);
}

CATATAN: Contoh kode di atas gagal untuk menjelaskan karakter internasional dengan benar dalam nama file. Lihat RFC6266 untuk standardisasi yang relevan. Saya percaya versi terbaru dari File()metode ASP.Net MVC dan ContentDispositionHeaderValuekelas dengan benar bertanggung jawab untuk ini. - Oskar 2016-02-25

Darin Dimitrov
sumber
7
Jika saya ingat dengan benar, ini bisa tidak dikutip selama nama file tidak memiliki spasi (saya menjalankan tambang melalui HttpUtility.UrlEncode () untuk mencapai ini).
Keith Williams
22
Catatan: Jika Anda menggunakan ini dan atur, Inline = truepastikan TIDAK untuk menggunakan kelebihan 3-param File()yang menjadikan nama-file sebagai param ke-3. Ini akan berfungsi di IE, tetapi Chrome akan melaporkan header duplikat dan menolak untuk menyajikan gambar.
Faust
74
Apa jenis var document = ...?
TTT
7
@ user1103990, ini model domain Anda.
Darin Dimitrov
3
Menggunakan MVC 5, tidak perlu lagi header konten-disposisi, karena sudah menjadi bagian dari header respons. Tapi saya hanya mendapatkan dialog unduhan di FF, tidak ada dialog di chrome dan IE
Legends
124

Saya mengalami masalah dengan jawaban yang diterima karena tidak ada tipe yang mengisyaratkan pada variabel "dokumen": var document = ...Jadi saya memposting apa yang berhasil bagi saya sebagai alternatif jika ada orang lain yang mengalami masalah.

public ActionResult DownloadFile()
{
    string filename = "File.pdf";
    string filepath = AppDomain.CurrentDomain.BaseDirectory + "/Path/To/File/" + filename;
    byte[] filedata = System.IO.File.ReadAllBytes(filepath);
    string contentType = MimeMapping.GetMimeMapping(filepath);

    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = filename,
        Inline = true,
    };

    Response.AppendHeader("Content-Disposition", cd.ToString());

    return File(filedata, contentType);
}
ooXei1sh
sumber
4
Variabel dokumen hanya kelas (POCO) yang mewakili informasi tentang dokumen yang ingin Anda kembalikan. Itu ditanyakan pada jawaban yang diterima juga. Itu bisa berasal dari ORM, dari kueri SQL yang dibuat secara manual, dari sistem file (seperti milik Anda menarik informasi), atau penyimpanan data lainnya. Itu tidak relevan untuk pertanyaan asli dari mana tipe byte / nama file / mime dokumen Anda berasal sehingga ditinggalkan untuk tidak memperkeruh kode. Terima kasih telah memberikan contoh menggunakan sistem file saja.
Nick Albrecht
1
Solusi ini tidak berfungsi dengan benar ketika nama file berisi karakter internasional di luar US-ASCII.
Oskar Berggren
1
Terima kasih, Selamatkan hari saya :)
Dipesh
1
Alternatif lain AppDomain.CurrentDomain.BaseDirectoryadalah System.Web.HttpContext.Current.Server.MapPath("~"), ini dapat bekerja lebih baik pada server nyata dibandingkan dengan mesin lokal.
Chris Thompson
15

Jawaban Darin Dimitrov benar. Hanya tambahan:

Response.AppendHeader("Content-Disposition", cd.ToString());dapat menyebabkan browser gagal membuat file jika respons Anda sudah berisi tajuk "Content-Disposition". Dalam hal ini, Anda mungkin ingin menggunakan:

Response.Headers.Add("Content-Disposition", cd.ToString());
Leo
sumber
Saya menemukan bahwa tipe konten juga memiliki pengaruh, untuk pdffile, jika saya menetapkan tipe konten sebagai System.Net.Mime.MediaTypeNames.Application.Octet, itu akan memaksa unduhan bahkan ketika saya atur Inline = true, tetapi jika saya atur Response.ContentType = MimeMapping.GetMimeMapping(filePath), yaitu application/pdf, ia dapat membuka dengan benar daripada mengunduh
yu yang Jian
Response.Headers.Addmembutuhkan mode pipa terintegrasi IIS. Selain itu, bahkan jika kumpulan aplikasi ditetapkan sebagai terintegrasi, itu akan menimbulkan pengecualian. Larutan. Gunakan Response.AddHeader. Lihat utas SO: stackoverflow.com/questions/22313167/…
roland
12

Untuk melihat file (txt misalnya):

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain);

Untuk mengunduh file (txt misalnya):

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain, "TextFile.txt");

Catatan: untuk mengunduh file kita harus melewati argumen fileDownloadName

Ashot Muradian
sumber
Satu-satunya masalah dengan pendekatan ini adalah bahwa nama file hanya disediakan jika Anda menggunakan metode yang memaksa unduhan. Itu tidak memungkinkan saya untuk mengirim nama file yang benar ketika saya ingin membiarkan browser menentukan cara membuka file. Jadi aplikasi eksternal akan mencoba menggunakan URL saya sebagai nama file. Yang biasanya akan menjadi semacam id untuk dokumen dalam database, biasanya tanpa ekstensi file. Ini membuat nama yang kurang bagus tidak cocok dengan URL yang digunakan untuk mengakses file, karena skenarionya adalah jika Anda tidak menyediakannya.
Nick Albrecht
Menggunakan Inlineproperti pada Content-Disposition memungkinkan saya untuk memisahkan kemampuan pengaturan nama file dari perilaku memaksa unduhan atau tidak.
Nick Albrecht
4

Saya yakin jawaban ini lebih bersih, (berdasarkan https://stackoverflow.com/a/3007668/550975 )

    public ActionResult GetAttachment(long id)
    {
        FileAttachment attachment;
        using (var db = new TheContext())
        {
            attachment = db.FileAttachments.FirstOrDefault(x => x.Id == id);
        }

        return File(attachment.FileData, "application/force-download", Path.GetFileName(attachment.FileName));
    }
Serj Sagan
sumber
9
Tidak direkomendasikan, Content-Disposition adalah metode yang lebih disukai untuk kejelasan dan kompatibilitas. stackoverflow.com/questions/10615797/...
Nick Albrecht
Terima kasih untuk itu. Meskipun saya mengubah tipe MIME ke application/octet-streamdan itu masih menyebabkan file untuk diunduh daripada ditampilkan, dan tampaknya kompatibel.
Serj Sagan
1
Berbohong tentang jenis konten terdengar seperti ide yang sangat buruk. Beberapa browser bergantung pada tipe konten yang benar untuk menyarankan aplikasi bagi pengguna dalam dialog "save-or-open".
Oskar Berggren
Jika maksud Anda adalah agar peramban menyarankan aplikasi maka itu tidak masalah, tetapi pertanyaan ini secara khusus tentang memaksa unduhan ...
Serj Sagan
@ SerjSagan Saya pikir ini lebih tentang menghindari perilaku browser default untuk melihat jenis file tertentu secara langsung atau menggunakan plugin, daripada menawarkan pilihan antara save / open. Tidak mencoba dengan misalnya JPEG sekarang, jadi tidak yakin tentang perilaku yang tepat.
Oskar Berggren
3

FileVirtualPath -> Research \ Global Office Review.pdf

public virtual ActionResult GetFile()
{
    return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}
Bishoy Hanna
sumber
5
Ini sudah disebutkan dalam jawaban sebelumnya dan tidak direkomendasikan. Lihat pertanyaan berikut untuk detail lebih lanjut tentang mengapa. stackoverflow.com/questions/10615797/…
Nick Albrecht
1
Dengan cara ini tidak menggunakan sumber daya di server dengan memuat file ke dalam memori di server, apakah saya benar?
Ian Jowett
1
Saya percaya begitu, karena Anda tidak perlu lagi @CrashOverride
Bishoy Hanna
1

Kode di bawah ini berfungsi untuk saya untuk mendapatkan file pdf dari layanan API dan menanggapinya ke browser - semoga membantu;

public async Task<FileResult> PrintPdfStatements(string fileName)
    {
         var fileContent = await GetFileStreamAsync(fileName);
         var fileContentBytes = ((MemoryStream)fileContent).ToArray();
         return File(fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf);
    }
Jonny Boy
sumber
2
Terima kasih atas jawabannya. Anda melewatkan bagian di mana saya mencari solusi yang mencakup kemampuan untuk menentukan nama file. Anda baru saja mengembalikan byte, sehingga namanya akan disimpulkan dari URL. Saya menggunakan PDF sebagai contoh, tetapi saya membutuhkannya untuk bekerja untuk beberapa tipe file lain juga dalam kasus saya. Saya harus menyebutkan bahwa solusi Anda akan bekerja selama Anda menggunakan .NET Framework 4.x dan MVC <= 5. Jika Anda menjalankan terhadap .NET Core, taruhan terbaik Anda adalah menggunakanMicrosoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider
Nick Albrecht
@NickAlbrecht Saya tidak menggunakan .Net Core - contoh di atas adalah untuk respon pdf pada browser. Namun jika Anda ingin mengunduh cobalah: File (fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf, "nama file Anda"). Tapi saya tidak yakin apakah Anda mendapat jawaban. tolong beri tahu saya jika ini membantu. Terima kasih atas saran .Net Core. Juga jika Anda menemukan jawaban saya bermanfaat, silakan tambahkan suara.
Jonny Boy
Saya sudah memecahkan masalah sejak lama dengan jawaban yang saya tandai telah diterima. Saya lebih menunjukkan beberapa peringatan jika Anda menemukan itu berguna. Pertanyaan asli saya adalah 2011, jadi ini sudah cukup tua sekarang.
Nick Albrecht
@NickAlbrecht Terima kasih. Saya tidak tahu Anda menyelesaikannya, saya memang melihatnya sangat lama. Saya menemukan forum ini sambil mencari beberapa jawaban terkait async, saya baru dalam proses async. saya bisa menyelesaikan masalah saya jadi hanya berbagi. Daripada Anda untuk waktu Anda.
Jonny Boy
0

Metode tindakan perlu mengembalikan FileResult dengan aliran, byte [], atau jalur virtual file. Anda juga perlu mengetahui tipe konten dari file yang sedang diunduh. Berikut adalah contoh metode utilitas (cepat / kotor). Contoh tautan video Cara mengunduh file menggunakan asp.net core

[Route("api/[controller]")]
public class DownloadController : Controller
{
    [HttpGet]
    public async Task<IActionResult> Download()
    {
        var path = @"C:\Vetrivel\winforms.png";
        var memory = new MemoryStream();
        using (var stream = new FileStream(path, FileMode.Open))
        {
            await stream.CopyToAsync(memory);
        }
        memory.Position = 0;
        var ext = Path.GetExtension(path).ToLowerInvariant();
        return File(memory, GetMimeTypes()[ext], Path.GetFileName(path));
    }

    private Dictionary<string, string> GetMimeTypes()
    {
        return new Dictionary<string, string>
        {
            {".txt", "text/plain"},
            {".pdf", "application/pdf"},
            {".doc", "application/vnd.ms-word"},
            {".docx", "application/vnd.ms-word"},
            {".png", "image/png"},
            {".jpg", "image/jpeg"},
            ...
        };
    }
}
VETRIVEL D
sumber
Menautkan ke sesuatu yang Anda berafiliasi (misalnya perpustakaan, alat, produk, tutorial, atau situs web) tanpa mengungkapkan milik Anda dianggap sebagai spam di Stack Overflow. Lihat: Apa yang menandakan promosi diri "Bagus"? , beberapa tips dan saran tentang promosi diri , Apa definisi yang tepat dari "spam" untuk Stack Overflow? , dan Apa yang membuat sesuatu menjadi spam .
Samuel Liew
0

Jika, seperti saya, Anda telah sampai ke topik ini melalui komponen Razor saat Anda mempelajari Blazor, maka Anda akan perlu berpikir lebih banyak di luar kotak untuk menyelesaikan masalah ini. Agak seperti ladang ranjau jika (juga seperti saya) Blazor adalah forray pertama Anda ke dunia tipe-MVC, karena dokumentasi tidak membantu untuk tugas-tugas 'kasar' seperti itu.

Jadi, pada saat penulisan, Anda tidak dapat mencapai ini menggunakan vanilla Blazor / Razor tanpa menyematkan pengontrol MVC untuk menangani bagian pengunduhan file, contohnya adalah sebagai berikut:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;

[Route("api/[controller]")]
[ApiController]
public class FileHandlingController : ControllerBase
{
    [HttpGet]
    public FileContentResult Download(int attachmentId)
    {
        TaskAttachment taskFile = null;

        if (attachmentId > 0)
        {
            // taskFile = <your code to get the file>
            // which assumes it's an object with relevant properties as required below

            if (taskFile != null)
            {
                var cd = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
                {
                    FileNameStar = taskFile.Filename
                };

                Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
            }
        }

        return new FileContentResult(taskFile?.FileData, taskFile?.FileContentType);
    }
}

Selanjutnya, pastikan startup aplikasi Anda (Startup.cs) dikonfigurasi untuk menggunakan MVC dengan benar dan menghadirkan baris berikut (tambahkan jika tidak):

        services.AddMvc();

.. dan akhirnya memodifikasi komponen Anda untuk ditautkan ke controller, misalnya (contoh berbasis iteratif menggunakan kelas kustom):

    <tbody>
        @foreach (var attachment in yourAttachments)
        {
        <tr>
            <td><a href="api/[email protected]" target="_blank">@attachment.Filename</a> </td>
            <td>@attachment.CreatedUser</td>
            <td>@attachment.Created?.ToString("dd MMM yyyy")</td>
            <td><ul><li class="oi oi-circle-x delete-attachment"></li></ul></td>
        </tr>
        }
        </tbody>

Semoga ini membantu siapa saja yang berjuang (seperti saya!) Untuk mendapatkan jawaban yang tepat untuk pertanyaan yang tampaknya sederhana ini di ranah Blazor ...!

VorTech
sumber
Meskipun ini bisa bermanfaat bagi orang lain yang menghadapi masalah yang sama dengan Anda, itu akan lebih mudah ditemukan bagi mereka jika Anda memposting pertanyaan Anda sendiri dengan judul yang menunjukkan itu unik untuk Blazor, jawab sendiri, dan tambahkan komentar di sini yang menyarankan siapa pun mencapai pertanyaan ini dengan masalah tentang blazor, periksa tautan Anda. Saya merasa saya telah mencapai bahkan dengan pertanyaan asli ini adalah untuk ASP.NET MVC, dan mengadaptasi jawabannya agar relevan dengan ASP.NET Core. Blazor adalah binatang yang sama sekali berbeda, seperti yang Anda temukan.
Nick Albrecht