Скачать файл любого типа в Asp.Net MVC, используя FileResult?

228

Мне было предложено использовать FileResult, чтобы пользователи могли загружать файлы из моего приложения Asp.Net MVC. Но единственные примеры, которые я могу найти, всегда связаны с файлами изображений (указав тип содержимого image / 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
То, что вы делаете, довольно опасно. Вы в значительной степени позволяете пользователям загружать любые файлы с вашего сервера, к которым может иметь доступ исполняющий пользователь.
Пол Флеминг
1
Верно - удаление пути к файлу и закрепление его в теле результата действия будет несколько безопаснее. По крайней мере, таким образом, они имеют доступ только к определенной папке.
Шубниггурат
2
Существуют ли инструменты, которые позволяют вам найти потенциально опасные лазейки, такие как эта?
Дэвид
Я считаю, что удобно установить тип контента как Response.ContentType = MimeMapping.GetMimeMapping(filePath);, из stackoverflow.com/a/22231074/4573839
Ю Ян Цзянь
Что вы используете на стороне клиента?
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 это не "вычисление" это, есть простая таблица сопоставления, основанная на расширениях файла или что-то в этом роде. Сервер делает это для всех статических файлов, это не медленно.
Аль Кепп
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 - это здорово .... очень полезно. Спасибо!
Цви Редлер
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));
CDichter
источник
2
Пожалуйста, никогда не загружайте целые файлы в память в производство, как это
Махдуми