Завантажити файл будь-якого типу в Asp.Net MVC за допомогою FileResult?


228

У мене було запропоновано мені використовувати FileResult, щоб дозволити користувачам завантажувати файли з моєї програми Asp.Net MVC. Але єдині приклади цього, які я можу знайти, завжди стосуються файлів зображень (із зазначенням типу вмісту image / jpeg).

Але що робити, якщо я не можу знати тип файлу? Я хочу, щоб користувачі могли завантажити майже будь-який файл із файлового файлу мого сайту.

Я прочитав один метод виконання цього коду (див. Попередній пост для коду), який насправді працює добре, за винятком однієї речі: ім'я файлу, що з’являється у діалоговому вікні «Зберегти як», є об'єднаним із шляху файлу з підкресленнями ( folder_folder_file.ext). Крім того, здається, люди думають, що я повинен повернути FileResult замість цього спеціального класу, який я знайшов BinaryContentResult.

Хтось знає "правильний" спосіб такого завантаження в MVC?

EDIT: Я отримав відповідь (нижче), але просто подумав, що я повинен опублікувати повний робочий код, якщо хтось інший зацікавлений:

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
Те, що ти робиш, досить небезпечно. Ви в значній мірі дозволяєте користувачам завантажувати будь-який файл з вашого сервера, до якого може отримати доступ виконавець.
Пол Флемінг

1
Правда - видалити шлях до файлу та прибити його в тіло результату дій було б дещо безпечніше. Принаймні таким чином вони мають доступ лише до певної папки.
shubniggurath

2
Чи є інструменти, які дозволяють знайти потенційно небезпечні лазівки, як ця?
Девід

Я вважаю, що зручно встановлювати тип контенту як Response.ContentType = MimeMapping.GetMimeMapping(filePath);, з stackoverflow.com/a/22231074/4573839
yu yang Jian

Що ви використовуєте на стороні клієнта?
FrenkyB

Відповіді:


425

Ви можете просто вказати загальний тип MIME для октетного потоку:

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
Гаразд, я міг би спробувати це, але що входить до масиву byte []?
Андерс

3
Неважливо, я думаю, я це зрозумів. Я читав ім'я файлу (повний шлях) у FileStream, а потім у байтовий масив, і тоді він працював як шарм! Дякую!
Андерс

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 (рядок FileName), щоб отримати тип MIME для свого файлу. Ось як я це використовував у своїй дії.

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

Отримати відображення Mime добре, але хіба це не важкий процес, щоб зрозуміти, який тип файлу під час виконання?
Мохаммед Нурелдін

@MohammedNoureldin це не "зрозуміти" це, є проста таблиця зіставлення на основі розширень файлів або щось подібне. Сервер робить це для всіх статичних файлів, це не повільно.
Al Kepp

13

У Філа Хака є чудова стаття, де він створив власний клас Результат завантаження файлів. Потрібно лише вказати віртуальний шлях до файлу та ім'я, яке слід зберегти.

Я використовував це колись і ось мій код.

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

Ось повний клас, викладений із статті Філла Хака

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
Так, я бачив і цю статтю, але, схоже, це те саме, що і статтю, яку я використав (див. Посилання на мою попередню публікацію), і він каже сам у верхній частині сторінки, що вирішення проблеми не повинно ' більше не потрібна, тому що: "НОВА ОНОВЛЕННЯ: У цьому користувальницькому ActionResult більше немає потреби, оскільки ASP.NET MVC тепер містить один у полі." Але, на жаль, він нічого іншого не каже про те, як це використовувати.
Андерс

@ManafAbuRous, якщо ви уважно прочитаєте код, ви побачите, що він фактично перетворює віртуальний шлях у фізичний шлях ( Server.MapPath(this.VirtualPath)), тож споживаючи це безпосередньо без змін, це наївно. Ви повинні створити альтернативний варіант, який приймає PhysicalPathдані, що саме в кінцевому підсумку потрібно і те, що ви зберігаєте. Це було б набагато безпечніше, оскільки ви зробили припущення, що фізичний шлях та відносний шлях будуть однаковими (виключаючи корінь). Файли даних, які часто зберігаються, це App_Data. Це не є доступним як відносний шлях.
Пол Флемінг

GetVirtualPath чудово .... дуже корисно. спасибі!
Zvi Redler

6

Дякую Іану Генрі !

Якщо вам потрібно отримати файл з 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 є EntityFrameworkмоделлю, а 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)) повертає Content ("ім'я файлу немає");

        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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.