Unduh file jenis apa pun di Asp.Net MVC menggunakan FileResult?

228

Saya sudah menyarankan kepada saya bahwa saya harus menggunakan FileResult untuk memungkinkan pengguna mengunduh file dari aplikasi Asp.Net MVC saya. Tapi satu-satunya contoh ini saya dapat temukan selalu ada hubungannya dengan file gambar (menentukan jenis konten gambar / jpeg).

Tetapi bagaimana jika saya tidak dapat mengetahui jenis file? Saya ingin pengguna dapat mengunduh hampir semua file dari file di situs saya.

Saya telah membaca satu metode untuk melakukan ini (lihat posting sebelumnya untuk kode), yang benar-benar berfungsi dengan baik, kecuali untuk satu hal: nama file yang muncul dalam dialog Save As disatukan dari path file dengan garis bawah ( folder_folder_file.ext). Juga, tampaknya orang berpikir saya harus mengembalikan FileResult daripada menggunakan kelas kustom ini yang saya temukan BinaryContentResult.

Adakah yang tahu cara "benar" melakukan pengunduhan di MVC?

EDIT: Saya mendapat jawabannya (di bawah), tetapi hanya berpikir saya harus memposting kode kerja lengkap jika orang lain tertarik:

public ActionResult Download(string filePath, string fileName)
{
    string fullName = Path.Combine(GetBaseDir(), filePath, fileName);

    byte[] fileBytes = GetFile(fullName);
    return File(
        fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}

byte[] GetFile(string s)
{
    System.IO.FileStream fs = System.IO.File.OpenRead(s);
    byte[] data = new byte[fs.Length];
    int br = fs.Read(data, 0, data.Length);
    if (br != fs.Length)
        throw new System.IO.IOException(s);
    return data;
}
Anders
sumber
12
Apa yang Anda lakukan agak berbahaya. Anda cukup banyak memungkinkan pengguna untuk mengunduh file apa pun dari server Anda yang dapat diakses oleh pengguna yang mengeksekusinya.
Paul Fleming
1
Benar - menghapus path file, dan memakukannya di tubuh actionresult akan lebih aman. Setidaknya dengan cara itu mereka hanya memiliki akses ke folder tertentu.
shubniggurath
2
Apakah ada alat yang memungkinkan Anda menemukan celah yang berpotensi berbahaya seperti ini?
David
Saya merasa nyaman untuk mengatur tipe konten sebagai Response.ContentType = MimeMapping.GetMimeMapping(filePath);, dari stackoverflow.com/a/22231074/4573839
yu yang Jian
Apa yang Anda gunakan di sisi klien?
FrenkyB

Jawaban:

425

Anda cukup menentukan jenis MIME octet-stream umum:

public FileResult Download()
{
    byte[] fileBytes = System.IO.File.ReadAllBytes(@"c:\folder\myfile.ext");
    string fileName = "myfile.ext";
    return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
}
Ian Henry
sumber
4
Ok, saya bisa mencobanya, tapi apa yang masuk ke byte [] array?
Anders
3
Sudahlah, kurasa aku sudah menemukan jawabannya. Saya membaca nama file (path lengkap) menjadi FileStream dan kemudian ke array byte, dan kemudian bekerja seperti pesona! Terima kasih!
Anders
5
Ini memuat seluruh file ke dalam memori hanya untuk mengalirkannya; untuk file besar, ini adalah babi. Solusi yang jauh lebih baik adalah yang di bawah ini yang tidak harus memuat file ke dalam memori terlebih dahulu.
HBlackorby
13
Karena jawaban ini hampir lima tahun, ya. Jika Anda melakukan ini untuk menyajikan file yang sangat besar, jangan. Jika memungkinkan, gunakan server file statis terpisah sehingga Anda tidak mengikat utas aplikasi Anda, atau salah satu dari banyak teknik baru untuk menyajikan file yang ditambahkan ke MVC sejak 2010. Ini hanya menunjukkan tipe MIME yang tepat untuk digunakan ketika tipe MIME tidak diketahui . ReadAllBytesditambahkan bertahun-tahun kemudian dalam edit. Mengapa ini jawaban kedua saya yang paling banyak dipilih? Baiklah.
Ian Henry
10
Mendapat kesalahan ini:non-invocable member "File" cannot be used like a method.
A-Sharabiani
105

Kerangka kerja MVC mendukung ini secara asli. The System.Web.MVC.Controller.File controller menyediakan metode untuk kembali file dengan nama / aliran / array yang .

Misalnya menggunakan jalur virtual ke file Anda bisa melakukan hal berikut.

return File(virtualFilePath, System.Net.Mime.MediaTypeNames.Application.Octet,  Path.GetFileName(virtualFilePath));
Jonathan
sumber
36

Jika Anda menggunakan .NET Framework 4.5 maka Anda menggunakan MimeMapping.GetMimeMapping (string FileName) untuk mendapatkan MIME-Type untuk file Anda. Ini adalah bagaimana saya menggunakannya dalam tindakan saya.

return File(Path.Combine(@"c:\path", fileFromDB.FileNameOnDisk), MimeMapping.GetMimeMapping(fileFromDB.FileName), fileFromDB.FileName);
Salman Hasrat Khan
sumber
Pemetaan Mime yang bagus itu bagus, tetapi bukankah ini merupakan proses yang berat untuk mengetahui jenis file apa dalam waktu berjalan?
Mohammed Noureldin
@MohammedNoureldin bukan "mencari", ada tabel pemetaan sederhana berdasarkan ekstensi file atau sesuatu seperti itu. Server melakukannya untuk semua file statis, tidak lambat.
Al Kepp
13

Phil Haack memiliki artikel yang bagus di mana ia menciptakan kelas Hasil Tindakan Unduhan File Khusus. Anda hanya perlu menentukan jalur virtual file dan nama yang akan disimpan.

Saya menggunakannya sekali dan ini kode saya.

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Download(int fileID)
        {
            Data.LinqToSql.File file = _fileService.GetByID(fileID);

            return new DownloadResult { VirtualPath = GetVirtualPath(file.Path),
                                        FileDownloadName = file.Name };
        }

Dalam contoh saya, saya menyimpan jalur fisik file jadi saya menggunakan metode pembantu ini - yang saya temukan di suatu tempat yang tidak dapat saya ingat - untuk mengubahnya menjadi jalur virtual

        private string GetVirtualPath(string physicalPath)
        {
            string rootpath = Server.MapPath("~/");

            physicalPath = physicalPath.Replace(rootpath, "");
            physicalPath = physicalPath.Replace("\\", "/");

            return "~/" + physicalPath;
        }

Inilah kelas lengkap yang diambil dari artikel Phill Haack

public class DownloadResult : ActionResult {

    public DownloadResult() {}

    public DownloadResult(string virtualPath) {
        this.VirtualPath = virtualPath;
    }

    public string VirtualPath {
        get;
        set;
    }

    public string FileDownloadName {
        get;
        set;
    }

    public override void ExecuteResult(ControllerContext context) {
        if (!String.IsNullOrEmpty(FileDownloadName)) {
            context.HttpContext.Response.AddHeader("content-disposition", 
            "attachment; filename=" + this.FileDownloadName)
        }

        string filePath = context.HttpContext.Server.MapPath(this.VirtualPath);
        context.HttpContext.Response.TransmitFile(filePath);
    }
}
Manaf Abu
sumber
1
Benar, ya, saya melihat artikel itu juga, tetapi tampaknya melakukan hal yang sama dengan artikel yang saya gunakan (lihat referensi ke posting saya sebelumnya), dan dia berkata pada dirinya sendiri di bagian atas halaman bahwa solusinya tidak boleh ' t diperlukan lagi karena: "PEMBARUAN BARU: Tidak perlu lagi ActionResult kustom ini karena ASP.NET MVC sekarang termasuk satu di dalam kotak." Tapi sayangnya, dia tidak mengatakan apa pun tentang bagaimana ini akan digunakan.
Anders
@ ManafAbuRous, jika Anda membaca kode dengan cermat, Anda akan melihatnya benar-benar mengubah jalur virtual ke jalur fisik ( Server.MapPath(this.VirtualPath)) jadi memakan ini secara langsung tanpa perubahan adalah naif. Anda harus menghasilkan alternatif yang menerima PhysicalPathmengingat itulah yang akhirnya diperlukan dan apa yang Anda simpan. Ini akan jauh lebih aman karena Anda telah membuat asumsi bahwa jalur fisik dan jalur relatif akan sama (tidak termasuk root). File data yang sering disimpan adalah App_Data. Ini tidak dapat diakses sebagai jalur relatif.
Paul Fleming
GetVirtualPath sangat bagus .... sangat berguna. Terima kasih!
Zvi Redler
6

Terima kasih kepada Ian Henry !

Dalam hal jika Anda perlu mendapatkan file dari MS SQL Server di sini adalah solusinya.

public FileResult DownloadDocument(string id)
        {
            if (!string.IsNullOrEmpty(id))
            {
                try
                {
                    var fileId = Guid.Parse(id);

                    var myFile = AppModel.MyFiles.SingleOrDefault(x => x.Id == fileId);

                    if (myFile != null)
                    {
                        byte[] fileBytes = myFile.FileData;
                        return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, myFile.FileName);
                    }
                }
                catch
                {
                }
            }

            return null;
        }

Di mana AppModel adalah EntityFrameworkmodel dan MyFiles menyajikan tabel di database Anda. FileData ada varbinary(MAX)di tabel MyFiles .

Pengembang
sumber
2

sederhana saja berikan path fisik Anda di directoryPath dengan nama file

public FilePathResult GetFileFromDisk(string fileName)
{
    return File(directoryPath, "multipart/form-data", fileName);
}
DARSHAN SHINDE
sumber
Bagaimana dengan sisi klien, menyebut metode ini? Katakanlah jika Anda ingin menampilkan save as dialog?
FrenkyB
0
   public ActionResult Download()
        {
            var document = //Obtain document from database context
    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = document.FileName,
        Inline = false,
    };
            Response.AppendHeader("Content-Disposition", cd.ToString());
            return File(document.Data, document.ContentType);
        }
hossein zakizadeh
sumber
-1

if (string.IsNullOrWhiteSpace (fileName)) mengembalikan Konten ("nama file tidak ada");

        var path = Path.Combine(your path, your filename);

        var stream = new FileStream(path, FileMode.Open);

        return File(stream, System.Net.Mime.MediaTypeNames.Application.Octet, fileName);
Caio Augusto
sumber
-4

GetFile harus menutup file (atau membukanya dengan menggunakan). Kemudian Anda dapat menghapus file setelah konversi ke byte - unduhan akan dilakukan pada buffer byte itu.

    byte[] GetFile(string s)
    {
        byte[] data;
        using (System.IO.FileStream fs = System.IO.File.OpenRead(s))
        {
            data = new byte[fs.Length];
            int br = fs.Read(data, 0, data.Length);
            if (br != fs.Length)
                throw new System.IO.IOException(s);
        }
        return data;
    }

Jadi, dalam metode unduhan Anda ...

        byte[] fileBytes = GetFile(file);
        // delete the file after conversion to bytes
        System.IO.File.Delete(file);
        // have the file download dialog only display the base name of the file            return File(fileBytes, System.Net.Mime.MediaTypeNames.Application.Octet, Path.GetFileName(file));
CDichter
sumber
2
Tolong jangan pernah, pernah memuat seluruh file ke dalam memori ke dalam produksi seperti ini
makhdumi