Tải xuống tệp thuộc loại nào trong Asp.Net MVC bằng FileResult?


228

Tôi đã đề nghị với tôi rằng tôi nên sử dụng FileResult để cho phép người dùng tải xuống các tệp từ ứng dụng Asp.Net MVC của tôi. Nhưng các ví dụ duy nhất về điều này tôi có thể tìm thấy luôn phải thực hiện với các tệp hình ảnh (chỉ định loại hình ảnh / jpeg nội dung).

Nhưng nếu tôi không thể biết loại tệp thì sao? Tôi muốn người dùng có thể tải xuống khá nhiều tập tin từ filearea của trang web của tôi.

Tôi đã đọc một phương pháp để làm điều này (xem một bài viết trước về mã), nó thực sự hoạt động tốt, ngoại trừ một điều: tên của tệp xuất hiện trong hộp thoại Save As được nối từ đường dẫn tệp có dấu gạch dưới ( thư mục_folder_file.ext). Ngoài ra, có vẻ như mọi người nghĩ rằng tôi nên trả lại FileResult thay vì sử dụng lớp tùy chỉnh này mà tôi đã tìm thấy BinaryContentResult.

Bất cứ ai cũng biết cách "chính xác" để thực hiện tải xuống như vậy trong MVC?

EDIT: Tôi đã nhận được câu trả lời (bên dưới), nhưng chỉ nghĩ rằng tôi nên đăng mã làm việc đầy đủ nếu có người khác quan tâm:

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
Những gì bạn đang làm là khá nguy hiểm. Bạn khá nhiều cho phép người dùng tải xuống bất kỳ tệp nào từ máy chủ của bạn mà người dùng thực thi có thể truy cập.
Paul Fleming

1
Đúng - loại bỏ đường dẫn tệp và đóng đinh nó vào phần thân của bộ xử lý sẽ an toàn hơn. Ít nhất theo cách đó họ chỉ có quyền truy cập vào một thư mục nhất định.
shubniggurath

2
Có công cụ nào cho phép bạn tìm ra những sơ hở nguy hiểm tiềm tàng như cái này không?
David

Tôi thấy rằng thật tiện lợi khi đặt loại nội dung là Response.ContentType = MimeMapping.GetMimeMapping(filePath);, từ stackoverflow.com/a/222 31074/4573839
yu yang Jian

Bạn đang sử dụng gì về phía khách hàng?
FrenkyB

Câu trả lời:


425

Bạn chỉ có thể chỉ định loại MIME octet-stream chung:

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
Ok, tôi có thể thử điều đó, nhưng cái gì đi vào mảng byte []?
Anders

3
Không sao, tôi nghĩ rằng tôi đã tìm ra nó. Tôi đọc tên tệp (đường dẫn đầy đủ) vào FileStream và sau đó vào một mảng byte, và sau đó nó hoạt động như một bùa mê! Cảm ơn!
Anders

5
Điều này tải toàn bộ tập tin vào bộ nhớ chỉ để truyền nó ra; Đối với các tập tin lớn, đây là một con heo. Một giải pháp tốt hơn nhiều là giải pháp dưới đây không phải tải tệp vào bộ nhớ trước.
HBlackorby

13
Vì câu trả lời này đã gần năm tuổi, yeah. Nếu bạn đang làm điều này để phục vụ các tệp rất lớn, thì không. Nếu có thể, hãy sử dụng một máy chủ tệp tĩnh riêng biệt để bạn không buộc các luồng ứng dụng của mình hoặc một trong nhiều kỹ thuật mới để phục vụ các tệp được thêm vào MVC từ năm 2010. Điều này chỉ hiển thị loại MIME chính xác để sử dụng khi loại MIME không xác định . ReadAllBytesđã được thêm vào những năm sau đó trong một chỉnh sửa. Tại sao đây là câu trả lời nâng cao thứ hai của tôi? Ồ tốt
Ian Henry

10
Nhận được lỗi này:non-invocable member "File" cannot be used like a method.
A-Sharabiani 2/2/2016

105

Khung MVC hỗ trợ nguyên bản này. Bộ điều khiển System.Web.MVC.Controll.File cung cấp các phương thức để trả về một tệp theo tên / luồng / mảng .

Ví dụ: sử dụng đường dẫn ảo đến tệp bạn có thể thực hiện như sau.

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

36

Nếu bạn đang sử dụng .NET Framework 4.5 thì bạn sử dụng MimeMapping.GetMimeMapping (chuỗi FileName) để lấy MIME-Type cho tệp của bạn. Đây là cách tôi đã sử dụng nó trong hành động của mình.

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

Điều đó nhận được ánh xạ Mime là tốt, nhưng đó không phải là một quá trình nặng nề để tìm ra loại tệp trong thời gian chạy là gì?
Mohammed Noureldin

@MohammedNoureldin không phải là "hình dung" nó, có một bảng ánh xạ đơn giản dựa trên các phần mở rộng tập tin hoặc một cái gì đó tương tự. Máy chủ thực hiện nó cho tất cả các tệp tĩnh, nó không chậm.
Al Kepp

13

Phil Haack có một bài viết hay , nơi ông đã tạo ra một lớp Kết quả hành động tải xuống tệp tùy chỉnh. Bạn chỉ cần xác định đường dẫn ảo của tệp và tên sẽ được lưu dưới dạng.

Tôi đã sử dụng nó một lần và đây là mã của tôi.

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

Trong ví dụ của tôi, tôi đã lưu trữ đường dẫn vật lý của các tệp vì vậy tôi đã sử dụng phương thức trợ giúp này - tôi đã tìm thấy ở đâu đó tôi không thể nhớ - để chuyển đổi nó thành một đường dẫn ảo

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

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

            return "~/" + physicalPath;
        }

Đây là lớp đầy đủ như được lấy từ bài viết của 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
Đúng, đúng, tôi cũng thấy bài viết đó, nhưng nó dường như cũng giống như bài viết tôi đã sử dụng (xem phần tham khảo bài viết trước của tôi), và anh ấy tự nói mình ở đầu trang rằng cách giải quyết không nên ' Không còn cần thiết nữa vì: "CẬP NHẬT MỚI: Không còn cần cho ActionResult tùy chỉnh này nữa vì ASP.NET MVC hiện bao gồm một cái trong hộp." Nhưng thật không may, anh ta không nói gì thêm về việc sử dụng nó như thế nào.
Anders

@ManafAbuRous, nếu bạn đọc mã chặt chẽ, bạn sẽ thấy nó thực sự chuyển đổi đường dẫn ảo thành đường dẫn vật lý ( Server.MapPath(this.VirtualPath)) vì vậy việc tiêu thụ trực tiếp mà không thay đổi là một điều ngây thơ. Bạn nên tạo ra một lựa chọn thay thế chấp nhận PhysicalPathđược những gì cuối cùng là bắt buộc và là những gì bạn đang lưu trữ. Điều này sẽ an toàn hơn nhiều vì bạn đã đưa ra một giả định rằng đường dẫn vật lý và đường dẫn tương đối sẽ giống nhau (không bao gồm gốc). Các tệp dữ liệu thường được lưu trữ là App_Data. Điều này không thể truy cập như một đường dẫn tương đối.
Paul Fleming

GetVirtualPath rất tuyệt .... rất hữu ích. cảm ơn bạn!
Zvi Redler

6

Cảm ơn Ian Henry !

Trong trường hợp nếu bạn cần lấy tệp từ MS SQL Server thì đây là giải pháp.

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

Trong đó AppModelEntityFrameworkmô hình và MyFiles trình bày bảng trong cơ sở dữ liệu của bạn. FileData nằm varbinary(MAX)trong bảng MyFiles .


2

nó đơn giản chỉ cần cung cấp đường dẫn vật lý của bạn trong thư mụcPath với tên tệp

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

Còn phía khách hàng, gọi phương thức này thì sao? Hãy nói nếu bạn muốn hiển thị lưu dưới dạng hộp thoại?
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)) return Content ("tên tệp không có mặt");

        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 sẽ đóng tệp (hoặc mở tệp trong khi sử dụng). Sau đó, bạn có thể xóa tệp sau khi chuyển đổi thành byte-- việc tải xuống sẽ được thực hiện trên bộ đệm byte đó.

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

Vì vậy, trong phương pháp tải xuống của bạn ...

        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
Xin đừng bao giờ, bao giờ tải toàn bộ tệp vào bộ nhớ vào sản xuất như thế này
makhdumi
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.