ดาวน์โหลดไฟล์ประเภทใดก็ได้ใน Asp.Net MVC โดยใช้ FileResult?


228

ฉันแนะนำให้ฉันใช้ FileResult เพื่ออนุญาตให้ผู้ใช้ดาวน์โหลดไฟล์จากแอปพลิเคชัน Asp.Net MVC ของฉัน แต่ตัวอย่างเดียวของสิ่งนี้ที่ฉันสามารถหาได้เสมอเกี่ยวกับไฟล์รูปภาพ (การระบุประเภทเนื้อหาอิมเมจ / jpeg)

แต่ถ้าฉันไม่รู้จักประเภทไฟล์ล่ะ ฉันต้องการให้ผู้ใช้สามารถดาวน์โหลดไฟล์ใดก็ได้จากไฟล์ของเว็บไซต์ของฉัน

ฉันได้อ่านวิธีหนึ่งในการทำเช่นนี้ (ดูโพสต์ก่อนหน้าสำหรับรหัส) ที่ใช้งานได้จริงยกเว้นสิ่งหนึ่ง: ชื่อของไฟล์ที่เกิดขึ้นในกล่องโต้ตอบบันทึกเป็นถูกตัดแบ่งจากพา ธ ไฟล์ที่มีเครื่องหมายขีดล่าง ( folder_folder_file.ext) นอกจากนี้ดูเหมือนว่าผู้คนคิดว่าฉันควรคืน FileResult แทนที่จะใช้คลาสที่กำหนดเองนี้ซึ่งฉันได้พบ BinaryContentResult

ใครรู้วิธีที่ถูกต้องในการดาวน์โหลดเช่นนี้ใน MVC

แก้ไข: ฉันได้รับคำตอบ (ด้านล่าง) แต่แค่คิดว่าฉันควรโพสต์รหัสการทำงานแบบเต็มหากมีคนสนใจ:

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

12
สิ่งที่คุณทำค่อนข้างอันตราย คุณค่อนข้างอนุญาตให้ผู้ใช้ดาวน์โหลดไฟล์ใด ๆ จากเซิร์ฟเวอร์ของคุณที่ผู้ใช้ที่เรียกใช้สามารถเข้าถึงได้
Paul Fleming

1
True - การลบพา ธ ของไฟล์และการตอกย้ำมันลงไปในเนื้อหาของ actionresult จะค่อนข้างปลอดภัยกว่า อย่างน้อยพวกเขาก็สามารถเข้าถึงโฟลเดอร์บางอย่างเท่านั้น
shubniggurath

2
มีเครื่องมือใดที่อนุญาตให้คุณหาช่องโหว่ที่อาจเป็นอันตรายเช่นนี้หรือไม่?
David

ฉันพบว่าสะดวกในการตั้งค่าประเภทเนื้อหาเป็นResponse.ContentType = MimeMapping.GetMimeMapping(filePath);จากstackoverflow.com/a/22231074/4573839
yu yang Jian Jian

คุณใช้อะไรในด้านลูกค้า?
FrenkyB

คำตอบ:


425

คุณสามารถระบุประเภท MIME octet-stream ทั่วไป:

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

4
ตกลงฉันสามารถลองสิ่งนั้นได้ แต่สิ่งที่จะเข้าสู่อาร์เรย์ []
Anders

3
ไม่เป็นไรฉันคิดว่าฉันคิดออก ฉันอ่านชื่อไฟล์ (พา ธ เต็ม) ลงใน FileStream และจากนั้นเป็นอาร์เรย์ไบต์แล้วมันก็ทำงานได้เหมือนมีเสน่ห์! ขอบคุณ!
Anders

5
ไฟล์นี้โหลดไฟล์ทั้งหมดในหน่วยความจำเพื่อสตรีมออก สำหรับไฟล์ขนาดใหญ่นี่เป็นหมู ทางออกที่ดีกว่าคือด้านล่างที่ไม่ต้องโหลดไฟล์ลงในหน่วยความจำก่อน
HBlackorby

13
เพราะคำตอบนี้อายุเกือบห้าขวบใช่แล้ว หากคุณกำลังทำสิ่งนี้เพื่อให้บริการไฟล์ที่มีขนาดใหญ่มาก ๆ อย่าทำเช่นนั้น หากเป็นไปได้ให้ใช้เซิร์ฟเวอร์ไฟล์แบบคงที่แยกต่างหากเพื่อให้คุณไม่ผูกหัวข้อแอปพลิเคชันของคุณหรือหนึ่งในเทคนิคใหม่สำหรับการให้บริการไฟล์ที่เพิ่มใน MVC ตั้งแต่ปี 2010 เพียงแสดงประเภท MIME ที่ถูกต้องที่จะใช้เมื่อไม่ทราบประเภท MIME . ReadAllBytesถูกเพิ่มหลายปีต่อมาในการแก้ไข ทำไมนี่เป็นคำตอบอันดับที่สองที่ฉันโหวตมากที่สุด? โอ้ดี
เอียนเฮนรี่

10
รับข้อผิดพลาดนี้:non-invocable member "File" cannot be used like a method.
A-Sharabiani

105

กรอบ MVC รองรับสิ่งนี้ System.Web.MVC.Controller.Fileควบคุมมีวิธีการที่จะกลับแฟ้มโดยชื่อ / สตรีม / อาร์เรย์

ตัวอย่างเช่นการใช้พา ธ เสมือนกับไฟล์คุณสามารถทำสิ่งต่อไปนี้

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

36

หากคุณใช้. NET Framework 4.5 คุณจะต้องใช้MimeMapping.GetMimeMapping (string FileName) เพื่อรับ MIME-Type สำหรับไฟล์ของคุณ นี่คือวิธีที่ฉันใช้ในการกระทำของฉัน

return File(Path.Combine(@"c:\path", fileFromDB.FileNameOnDisk), MimeMapping.GetMimeMapping(fileFromDB.FileName), fileFromDB.FileName);

การทำแผนที่แบบ Mime นั้นดี แต่มันไม่ใช่ขั้นตอนที่ต้องใช้เพื่อพิจารณาว่าไฟล์ประเภทใดในเวลาทำงาน?
Mohammed Noureldin

@MohammedNoureldin ไม่ใช่ "การหา" มันมีตารางการแมปที่เรียบง่ายตามนามสกุลไฟล์หรืออะไรทำนองนั้น เซิร์ฟเวอร์ทำเพื่อไฟล์คงที่ทั้งหมดไม่ใช่ช้า
อัล Kepp

13

Phil Haack มีบทความที่ดีที่เขาสร้างคลาสผลลัพธ์การดำเนินการดาวน์โหลดไฟล์แบบกำหนดเอง คุณจะต้องระบุเส้นทางเสมือนของไฟล์และชื่อที่จะบันทึกเป็น

ฉันใช้ครั้งเดียวและนี่คือรหัสของฉัน

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

ในตัวอย่างของฉันฉันจัดเก็บพา ธ ทางกายภาพของไฟล์ดังนั้นฉันจึงใช้วิธีการช่วยเหลือนี้ - ฉันพบบางที่ที่ฉันจำไม่ได้ - แปลงเป็นเส้นทางเสมือน

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

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

            return "~/" + physicalPath;
        }

นี่คือคลาสเต็มรูปแบบที่นำมาจากบทความของ 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);
    }
}

1
ใช่แล้วฉันเห็นบทความนั้นด้วย แต่ดูเหมือนว่าจะทำในสิ่งเดียวกันกับบทความที่ฉันใช้ (ดูการอ้างอิงถึงโพสต์ก่อนหน้าของฉัน) และเขาบอกตัวเองที่ด้านบนสุดของหน้าซึ่งวิธีแก้ปัญหาไม่ควร ' ไม่จำเป็นอีกต่อไปเพราะ: "NEW UPDATE: ไม่จำเป็นต้องใช้ ActionResult แบบกำหนดเองอีกต่อไปเพราะ ASP.NET MVC ได้รวมไว้ในหนึ่งกล่อง" แต่น่าเสียดายที่เขาไม่ได้พูดอะไรอีกเกี่ยวกับการใช้สิ่งนี้
Anders

@ManafAbuRous หากคุณอ่านรหัสอย่างใกล้ชิดคุณจะเห็นว่ามันแปลงเส้นทางเสมือนไปเป็นเส้นทางกายภาพ ( Server.MapPath(this.VirtualPath)) ดังนั้นการบริโภคสิ่งนี้โดยตรงโดยไม่ต้องเปลี่ยนเป็นเรื่องไร้เดียงสา คุณควรสร้างทางเลือกที่ยอมรับPhysicalPathได้นั่นคือสิ่งที่จำเป็นในที่สุดและเป็นสิ่งที่คุณเก็บไว้ สิ่งนี้จะปลอดภัยกว่ามากเมื่อคุณทำการสันนิษฐานว่าเส้นทางกายภาพและเส้นทางสัมพัทธ์จะเหมือนกัน (ไม่รวมรูท) ไฟล์ข้อมูลมักจะถูกจัดเก็บคือ App_Data ไม่สามารถเข้าถึงได้เป็นเส้นทางสัมพัทธ์
Paul Fleming

GetVirtualPath ยอดเยี่ยม .... มีประโยชน์มาก ขอบคุณ!
Zvi Redler

6

ขอบคุณIan Henry !

ในกรณีที่คุณต้องการรับไฟล์จาก MS SQL Serverนี่คือทางออก

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

โดยที่AppModelคือEntityFrameworkmodel และMyFilesนำเสนอตารางในฐานข้อมูลของคุณ FileDataอยู่varbinary(MAX)ในตารางMyFiles


2

มันง่ายเพียงให้เส้นทางกายภาพของคุณใน directoryPath ด้วยชื่อไฟล์

public FilePathResult GetFileFromDisk(string fileName)
{
    return File(directoryPath, "multipart/form-data", fileName);
}

แล้วลูกค้าเรียกวิธีนี้ว่าอะไร ให้บอกว่าคุณต้องการแสดงบันทึกเป็นกล่องโต้ตอบหรือไม่
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);
        }

-1

if (string.IsNullOrWhiteSpace (fileName)) คืนเนื้อหา ("ชื่อไฟล์ไม่มีอยู่");

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

-4

GetFile ควรปิดไฟล์ (หรือเปิดภายในการใช้) จากนั้นคุณสามารถลบไฟล์หลังจากการแปลงเป็นไบต์ - การดาวน์โหลดจะทำในบัฟเฟอร์ไบต์นั้น

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

ดังนั้นในวิธีการดาวน์โหลดของคุณ ...

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

2
โปรดอย่าเคยโหลดไฟล์ทั้งหมดลงในหน่วยความจำเพื่อการผลิตเช่นนี้
makhdumi
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.