Повернення файлу для перегляду / завантаження в ASP.NET MVC


304

У мене виникає проблема з поверненням файлів, що зберігаються в базі даних, назад користувачеві в ASP.NET MVC. Я хочу - це перегляд із переліком двох посилань, одне для перегляду файлу та дозвол міметипу, надісланому браузеру, визначає, яким чином слід обробляти, а інший для примусового завантаження.

Якщо я вирішую переглянути файл, який називається, SomeRandomFile.bakі у веб-переглядача немає асоційованої програми для відкриття файлів такого типу, тоді я не маю жодних проблем із цим дефолтом у поведінці завантаження. Однак якщо я виріблю переглянути файл, який називається, SomeRandomFile.pdfабо SomeRandomFile.jpgхочу, щоб він просто відкрився. Але я також хочу відключити посилання для завантаження убік, щоб я міг змусити запросити завантаження незалежно від типу файлу. Це має сенс?

Я спробував, FileStreamResultі він працює для більшості файлів, його конструктор не приймає ім'я файлу за замовчуванням, тому невідомим файлам присвоюється ім’я файлу на основі URL-адреси (яка не знає розширення, яке давати залежно від типу вмісту). Якщо я змушую ім'я файлу, вказуючи його, я втрачаю можливість браузера безпосередньо відкривати файл, і я отримую запит на завантаження. Хтось ще стикався з цим?

Ось приклади того, що я намагався поки що.

//Gives me a download prompt.
return File(document.Data, document.ContentType, document.Name);

//Opens if it is a known extension type, downloads otherwise (download has bogus name and missing extension)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType);

//Gives me a download prompt (lose the ability to open by default if known type)
return new FileStreamResult(new MemoryStream(document.Data), document.ContentType) {FileDownloadName = document.Name};

Будь-які пропозиції?


ОНОВЛЕННЯ: Здається, це питання вражає акорд багатьох людей, тому я подумав, що опублікую оновлення. Попередження щодо прийнятої відповіді нижче, яку додав Оскар щодо міжнародних символів, є абсолютно дійсним, і я кілька разів потрапив у нього через використання ContentDispositionкласу. З того часу я оновив свою реалізацію, щоб виправити це. Хоча наведений нижче код є моїм останнім втіленням цієї проблеми в додатку ASP.NET Core (Full Framework), він повинен працювати з мінімальними змінами в старій програмі MVC, оскільки я використовую System.Net.Http.Headers.ContentDispositionHeaderValueклас.

using System.Net.Http.Headers;

public IActionResult Download()
{
    Document document = ... //Obtain document from database context

    //"attachment" means always prompt the user to download
    //"inline" means let the browser try and handle it
    var cd = new ContentDispositionHeaderValue("attachment")
    {
        FileNameStar = document.FileName
    };
    Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());

    return File(document.Data, document.ContentType);
}

// an entity class for the document in my database 
public class Document
{
    public string FileName { get; set; }
    public string ContentType { get; set; }
    public byte[] Data { get; set; }
    //Other properties left out for brevity
}

Відповіді:


430
public ActionResult Download()
{
    var document = ...
    var cd = new System.Net.Mime.ContentDisposition
    {
        // for example foo.bak
        FileName = document.FileName, 

        // always prompt the user for downloading, set to true if you want 
        // the browser to try to show the file inline
        Inline = false, 
    };
    Response.AppendHeader("Content-Disposition", cd.ToString());
    return File(document.Data, document.ContentType);
}

ПРИМІТКА. Цей приклад вище коду не відповідає належним чином для міжнародних символів у імені файлу. Див. RFC6266 щодо відповідної стандартизації. Я вважаю, що останні версії методу ASP.Net MVC File()і ContentDispositionHeaderValueклас належним чином пояснюють це. - Оскар 2016-02-25


7
Якщо я пригадую правильно, його можна не цитувати, доки в імені файлу немає пробілів (я провів мій через HttpUtility.UrlEncode (), щоб досягти цього).
Кіт Вільямс

22
Примітка. Якщо ви використовуєте це та встановлено, Inline = trueне переконайтесь, що НЕ використовуйте 3-парам-перевантаження, File()яке приймає ім'я файлу як 3-й парам. Він буде працювати в IE, але Chrome повідомить про дублікат заголовка і відмовиться представляти зображення.
Фауст

74
Який тип вар-документ = ...?
TTT

7
@ user1103990, це ваша модель домену.
Дарин Димитров

3
Використовуючи MVC 5, вже немає потреби в заголовку диспозиції вмісту, оскільки він вже є частиною заголовка відповідей. Але я отримую лише діалог завантаження у FF, немає діалогу в хромі та IE
Легенди

124

У мене виникли проблеми з прийнятою відповіддю через відсутність натяку на тип змінної «документ»: var document = ...Тому я публікую те, що працювало для мене як альтернативу, якщо у когось інших виникнуть проблеми.

public ActionResult DownloadFile()
{
    string filename = "File.pdf";
    string filepath = AppDomain.CurrentDomain.BaseDirectory + "/Path/To/File/" + filename;
    byte[] filedata = System.IO.File.ReadAllBytes(filepath);
    string contentType = MimeMapping.GetMimeMapping(filepath);

    var cd = new System.Net.Mime.ContentDisposition
    {
        FileName = filename,
        Inline = true,
    };

    Response.AppendHeader("Content-Disposition", cd.ToString());

    return File(filedata, contentType);
}

4
Змінна документа була просто класом (POCO), що представляє інформацію про документ, який ви хочете повернути. Про це попросили і відповідь, яку прийняли. Він може надходити з ORM, з вбудованого вручну SQL-запиту, з файлової системи (як ваша витягує інформацію з) або якогось іншого сховища даних. Для початкового питання це було неважливо, звідки походить ваш байт / ім'я / ім'я / mime тип документа, тому його не залишилося, щоб не заплутати код. Дякуємо, що навели приклад, використовуючи лише файлову систему.
Нік Альбрехт

1
Це рішення не працює належним чином, якщо ім'я файлу містить міжнародні символи за межами US-ASCII.
Оскар Берггрен

1
Дякую, врятував мій день :)
Dipesh

1
Альтернативою AppDomain.CurrentDomain.BaseDirectoryє те System.Web.HttpContext.Current.Server.MapPath("~"), що це може працювати краще на реальному сервері порівняно з локальною машиною.
Кріс Томпсон

15

Відповідь Даріна Димитрова правильна. Просто доповнення:

Response.AppendHeader("Content-Disposition", cd.ToString());може призвести до того, що браузер не зможе надати файл, якщо ваша відповідь вже містить заголовок "Контент-диспозиція". У такому випадку ви можете використовувати:

Response.Headers.Add("Content-Disposition", cd.ToString());

Я вважаю , що зміст типу також має вплив, для pdfфайлу, якщо встановити тип вмісту , як System.Net.Mime.MediaTypeNames.Application.Octetвін буде змушувати завантаження навіть коли я встановив Inline = true, але якщо встановити , як Response.ContentType = MimeMapping.GetMimeMapping(filePath), тобто application/pdf, він може відкрити правильно , а не завантажити
юй Ян Цзянь

Response.Headers.Addвимагають інтегрованого режиму трубопроводу IIS. Крім того, навіть якщо пул додатків встановлений як інтегрований, це призведе до виключення. Рішення. Використовуйте Response.AddHeader. Дивіться нитку SO: stackoverflow.com/questions/22313167/…
roland

12

Щоб переглянути файл (наприклад, txt):

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain);

Щоб завантажити файл (наприклад, txt):

return File("~/TextFileInRootDir.txt", MediaTypeNames.Text.Plain, "TextFile.txt");

Примітка: щоб завантажити файл, ми повинні передати аргумент fileDownloadName


Єдина проблема такого підходу полягає в тому, що ім'я файлу надається лише в тому випадку, якщо ви використовуєте метод, який змушує завантажити. Це не дозволяє мені надсилати правильне ім'я файлу, коли я хочу дозволити браузеру визначати, як відкрити файл. Тож зовнішній додаток намагатиметься використовувати мою URL-адресу як назву файлу. Це, як правило, якийсь ідентифікатор документа в базі даних, як правило, із розширенням файлу. Це робить для досить бідним ім'я, яке не відповідає URL-адресі, яка використовується для доступу до файлу, оскільки сценарій полягає в тому, що якщо ви не вкажете.
Нік Альбрехт

Використання Inlineвластивості в Content-Disposition дозволяє мені відокремити можливість встановлення імені файлу від поведінки примусового завантаження чи ні.
Нік Альбрехт

4

Я вважаю, що ця відповідь чистіша (на основі https://stackoverflow.com/a/3007668/550975 )

    public ActionResult GetAttachment(long id)
    {
        FileAttachment attachment;
        using (var db = new TheContext())
        {
            attachment = db.FileAttachments.FirstOrDefault(x => x.Id == id);
        }

        return File(attachment.FileData, "application/force-download", Path.GetFileName(attachment.FileName));
    }

9
Не рекомендується, Content-Disposition є кращим методом ясності та порівнянності. stackoverflow.com/questions/10615797/…
Нік Альбрехт

Дякую за це. Хоча я змінив тип MIME application/octet-streamі все-таки спричинив завантаження файлу, а не показ, і він здається сумісним.
Serj Sagan

1
Брехня про тип вмісту звучить як дійсно погана ідея. Деякі браузери залежать від правильного типу вмісту, щоб запропонувати програми для користувача у діалоговому вікні "зберегти-або-відкрити".
Оскар Берггрен

Якщо ваш браузер має намір запропонувати браузер програму, то це добре, але це питання стосується примушення завантаження ...
Serj Sagan

@SerjSagan Я думаю, що йдеться більше про обхід поведінки веб-переглядачів, дефолтом є перегляд певних типів файлів безпосередньо або використання плагіна, а не пропонувати вибір між збереженням / відкриттям. Не намагався зараз, наприклад, з JPEG, тому не впевнений у точній поведінці.
Оскар Берггрен

3

FileVirtualPath -> Дослідження \ Global Office Review.pdf

public virtual ActionResult GetFile()
{
    return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}

5
Про це вже було сказано в попередній відповіді і не рекомендується. Дивіться наступне запитання для більш детальної інформації про те, чому. stackoverflow.com/questions/10615797/…
Нік Альбрехт

1
Таким чином, він не використовує ресурс на сервері, завантажуючи файл у пам'ять на сервері, я правильно?
Ian Jowett

1
Я вважаю, що так не потрібно, щоб ви прав @CrashOverride
Bishoy Hanna

1

Нижче код працював у мене для отримання файлу PDF від служби API та відповіді на нього у браузері - сподіваюся, що це допоможе;

public async Task<FileResult> PrintPdfStatements(string fileName)
    {
         var fileContent = await GetFileStreamAsync(fileName);
         var fileContentBytes = ((MemoryStream)fileContent).ToArray();
         return File(fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf);
    }

2
Дякую за відповідь. Ви пропустили частину, де я шукав рішення, яке включало можливість вказувати ім’я файлу. Ви просто повертаєте байти, тому ім’я буде виведено з URL-адреси. Я використовував PDF як приклад, але мені він потрібен, щоб він працював і для кількох інших типів файлів у моєму випадку. Я мушу зазначити, що ваше рішення працюватиме до тих пір, поки ви використовуєте .NET Framework 4.x та MVC <= 5. Якщо ви протидієте .NET Core, найкраще використовуватиMicrosoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider
Нік Альбрехт,

@ NickAlbrecht я не використовував. Однак якщо ви хочете завантажити, спробуйте: File (fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf, "ваше ім'я файлу"). Я не впевнений, хоча ти отримав свою відповідь. будь ласка, дайте мені знати, якщо це допомагає. Дякую, проте, за пропозицію .Net Core. Також якщо ви вважаєте мою відповідь корисною, додайте голосування.
Jonny Boy

Я вже давно вирішив це питання з відповіддю, який я позначив як прийнятий. Я більше вказував на кілька застережень, якщо ви вважаєте їх корисними. Моє оригінальне запитання було 2011 року, тож це вже досить датоване.
Нік Альбрехт

@NickAlbrecht Дякую Я не знав, що ти це вирішив, я бачив, що це дуже стара публікація. Я знайшов цей форум, шукаючи відповіді, пов'язані з асинхронією, я новачок у процесах асинхронізації. я зміг вирішити свою проблему, тому просто поділився. Чим ти за свій час.
Джоні Хлопчик

0

Метод дії повинен повернути FileResult або потоком, байтом [], або віртуальним шляхом до файлу. Вам також потрібно знати тип вмісту завантажуваного файлу. Ось зразок (швидкий / брудний) корисний метод. Зразок відеопосилання Як завантажувати файли за допомогою ядра asp.net

[Route("api/[controller]")]
public class DownloadController : Controller
{
    [HttpGet]
    public async Task<IActionResult> Download()
    {
        var path = @"C:\Vetrivel\winforms.png";
        var memory = new MemoryStream();
        using (var stream = new FileStream(path, FileMode.Open))
        {
            await stream.CopyToAsync(memory);
        }
        memory.Position = 0;
        var ext = Path.GetExtension(path).ToLowerInvariant();
        return File(memory, GetMimeTypes()[ext], Path.GetFileName(path));
    }

    private Dictionary<string, string> GetMimeTypes()
    {
        return new Dictionary<string, string>
        {
            {".txt", "text/plain"},
            {".pdf", "application/pdf"},
            {".doc", "application/vnd.ms-word"},
            {".docx", "application/vnd.ms-word"},
            {".png", "image/png"},
            {".jpg", "image/jpeg"},
            ...
        };
    }
}

Посилання на те, з чим ви пов’язані (наприклад, з бібліотекою, інструментом, продуктом, навчальним посібником чи веб-сайтом), не розкриваючи, що це ваше , вважається спамом під час переповнення стека. Дивіться: Що означає "хороша" самореклама? , Деякі поради і рекомендації про саморекламу , Що таке точне визначення поняття «спам» для переповнення стека? , і Що робить щось спамом .
Самуель Liew

0

Якщо ви, як і я, підходили до цієї теми за допомогою компонентів Razor, коли ви навчаєтеся Blazor, тоді вам здається, що вам потрібно подумати трохи більше, щоб вирішити цю проблему. Це трохи мінне поле, якщо (як і я) Blazor - це ваш перший пробіг у світ типу MVC, оскільки документація не є такою корисною для таких «головних» завдань.

Отже, під час написання цього ви не можете досягти цього, використовуючи ванільний Blazor / Razor, не вставивши контролер MVC для обробки частини завантаження файлу, приклад якого наведено нижче:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Net.Http.Headers;

[Route("api/[controller]")]
[ApiController]
public class FileHandlingController : ControllerBase
{
    [HttpGet]
    public FileContentResult Download(int attachmentId)
    {
        TaskAttachment taskFile = null;

        if (attachmentId > 0)
        {
            // taskFile = <your code to get the file>
            // which assumes it's an object with relevant properties as required below

            if (taskFile != null)
            {
                var cd = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment")
                {
                    FileNameStar = taskFile.Filename
                };

                Response.Headers.Add(HeaderNames.ContentDisposition, cd.ToString());
            }
        }

        return new FileContentResult(taskFile?.FileData, taskFile?.FileContentType);
    }
}

Далі переконайтеся, що програма запуску програми (Startup.cs) налаштована на правильне використання MVC та має такий рядок (додайте його, якщо ні):

        services.AddMvc();

.. а потім остаточно модифікуйте ваш компонент, щоб він посилався на контролер, наприклад (ітеративний приклад на основі користувацького класу):

    <tbody>
        @foreach (var attachment in yourAttachments)
        {
        <tr>
            <td><a href="api/FileHandling?attachmentId=@attachment.TaskAttachmentId" target="_blank">@attachment.Filename</a> </td>
            <td>@attachment.CreatedUser</td>
            <td>@attachment.Created?.ToString("dd MMM yyyy")</td>
            <td><ul><li class="oi oi-circle-x delete-attachment"></li></ul></td>
        </tr>
        }
        </tbody>

Сподіваємось, це допомагає кожному, хто намагався (як я!) Отримати відповідну відповідь на це, здавалося б, просте питання у царинах Блазора…!


Хоча це може бути корисним для інших, хто зіткнувся з тією ж проблемою, що і ви, але для них було б краще виявити, якби ви опублікували власне запитання із заголовком, який вказує, що він унікальний для Блазора, відповісти на нього самостійно та додати тут коментар, який пропонує будь-кому звернутися це питання з проблемами, пов’язаними з блазором, перевірте ваше посилання. Я відчув, що досягаю навіть цього оригінального питання, пов’язаного з ASP.NET MVC, і адаптував його відповідь так, щоб він був відповідним ASP.NET Core. Блазор зовсім інший звір, як ви виявили.
Нік Альбрехт
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.