Bir dosyayı ASP.NET MVC'de Görüntüleme / İndirme işlemine döndürme


304

ASP.NET MVC kullanıcıya geri bir veritabanında depolanan dosyaları gönderme ile ilgili bir sorunla karşılaşıyorum. Ne istediğim, bir dosyayı görüntülemek ve tarayıcıya gönderilen mime türünün nasıl ele alınacağını belirlemesine ve diğeri de indirmeye zorlamak için iki bağlantıyı listeleyen bir görünümdür.

Adında bir dosyayı görüntülemeyi seçersem SomeRandomFile.bakve tarayıcının bu tür dosyaları açmak için ilişkili bir programı yoksa, indirme davranışını varsayılan olarak onunla ilgili bir sorunum yok. Ancak, adlı bir dosyayı görüntülemeyi seçersem SomeRandomFile.pdfveya SomeRandomFile.jpgdosyanın açılmasını istiyorum. Ancak dosya türünden bağımsız olarak bir indirme istemini zorlayabilmem için bir indirme bağlantısını yanda tutmak istiyorum. Bu mantıklı mı?

Denedim FileStreamResultve çoğu dosya için çalışıyor, yapıcısı varsayılan olarak bir dosya adını kabul etmiyor, bu nedenle bilinmeyen dosyalara URL'ye (içerik türüne göre verilecek uzantıyı bilmeyen) bir dosya adı atanıyor. Dosya adını belirterek zorlarsam, tarayıcının dosyayı doğrudan açma yeteneğini kaybederim ve bir indirme istemi alırım. Başka kimse bununla karşılaştı mı?

Bunlar şimdiye kadar denediklerimin örnekleri.

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

Herhangi bir öneri?


GÜNCELLEME: Bu sorular birçok insanla bir akor vuruyor gibi görünüyor, bu yüzden bir güncelleme yayınlayacağımı düşündüm. Oskar'ın uluslararası karakterlerle ilgili eklediği aşağıdaki cevap ile ilgili uyarı tamamen geçerlidir ve ContentDispositionsınıfı kullandığım için birkaç kez vurdum . O zamandan beri bunu düzeltmek için uygulamamı güncelledim. Aşağıdaki kod bir ASP.NET Core (Full Framework) uygulamasında bu sorunun en son enkarnasyonundan olsa da, System.Net.Http.Headers.ContentDispositionHeaderValuesınıfı kullandığım için eski bir MVC uygulamasında da minimum değişikliklerle çalışmalıdır .

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
}

Yanıtlar:


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

NOT: Yukarıdaki bu örnek kod, dosya adındaki uluslararası karakterleri doğru şekilde açıklayamaz. İlgili standardizasyon için RFC6266'ya bakın. ASP.Net MVC File()yönteminin son ContentDispositionHeaderValuesınıf ve sınıf düzgün bunun için hesap inanıyoruz . - Oskar 2016-02-25


7
Doğru hatırlıyorsam, dosya adında boşluk olmadığı sürece tırnak işareti alınabilir (bunu başarmak için benim HttpUtility.UrlEncode () üzerinden koştum).
Keith Williams

22
Not: Bunu kullanır ve ayarlarsanız , dosya adını 3. parametre olarak alan 3 parametreli Inline = trueaşırı yükü KULLANMADIĞINIZDAN emin olun File(). Bu olacak IE çalışır, ancak Krom bir yinelenen başlığı rapor ve görüntü sunmak reddedecektir.
Faust

74
Var document = ... nedir?
TTT

7
@ user1103990, etki alanı modelinizdir.
Darin Dimitrov

3
MVC 5 kullanıldığında, zaten içerik başlığının bir parçası olduğu için içerik düzenleme başlığına artık gerek yoktur. Ancak yalnızca FF'de bir indirme iletişim kutusu alıyorum, krom ve IE'de iletişim kutusu yok
Legends

124

"Belge" değişkenine ilişkin herhangi bir ipucu olmadığı için kabul edilen cevapla ilgili sorun yaşadım: var document = ...Bu yüzden, başka birinin sorun yaşaması durumunda benim için çalışanı alternatif olarak gönderiyorum.

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
Belge değişkeni, yalnızca geri vermek istediğiniz belge hakkındaki bilgileri temsil eden bir sınıf (POCO) idi. Kabul edilen cevapta da bu soru soruldu. Bir ORM'den, manuel olarak oluşturulmuş bir SQL sorgusundan, dosya sisteminizden (sizinkinden bilgi alırken) veya başka bir veri deposundan gelebilir. Doküman bayt / dosya adı / mime türünüzün geldiği orijinal soru ile ilgisi yoktu, bu yüzden kodu çamurlamamak için dışarıda bırakıldı. Yine de sadece dosya sistemini kullanarak bir örnek verdiğiniz için teşekkür ederiz.
Nick Albrecht

1
Dosya adı US-ASCII dışında uluslararası karakterler içerdiğinde bu çözüm düzgün çalışmaz.
Oskar Berggren

1
Teşekkürler,
Günümü kurtardım

1
Bir alternatif AppDomain.CurrentDomain.BaseDirectoryolan System.Web.HttpContext.Current.Server.MapPath("~")bu yerel makineye kıyasla gerçek bir sunucu üzerinde daha iyi çalışabilir.
Chris Thompson

15

Darin Dimitrov'un cevabı doğru. Sadece bir ek:

Response.AppendHeader("Content-Disposition", cd.ToString());yanıtınız zaten bir "Content-Disposition" başlığı içeriyorsa tarayıcının dosyayı işleyememesine neden olabilir. Bu durumda, aşağıdakileri kullanmak isteyebilirsiniz:

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

Ben içerik türü de için, bir etkiye sahip olduğunu bulmak pdfgibi ben content-type ayarlarsanız, dosyanın System.Net.Mime.MediaTypeNames.Application.Octetben set bile indir zorlamak edeceğiz Inline = true, ama ben ayarlarsanız Response.ContentType = MimeMapping.GetMimeMapping(filePath)olduğunu application/pdf, oldukça indirmek daha doğru açılabilir
yu yang Jian

Response.Headers.AddIIS ile tümleşik ardışık düzen modu gerektirir. Ayrıca, uygulama havuzu entegre olarak ayarlanmış olsa bile, bir istisna atar. Çözüm. Kullanın Response.AddHeader. Bkz. SO iş parçacığı: stackoverflow.com/questions/22313167/…
roland

12

Dosyayı görüntülemek için (örneğin txt):

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

Dosyayı indirmek için (örneğin txt):

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

not: dosyayı indirmek için fileDownloadName argümanını geçirmeliyiz


Bu yaklaşımla ilgili tek sorun, dosya adının yalnızca karşıdan yüklemeyi zorlayan yöntemi kullandığınızda sağlanmasıdır. Tarayıcının dosyayı nasıl açacağını belirlemesine izin vermek istediğimde doğru dosya adını göndermeme izin vermiyor. Harici uygulama URL'mi dosya adı olarak kullanmaya çalışacaktır. Genellikle veritabanında, genellikle bir dosya uzantısı olmadan belge için bir tür kimlik olacaktır. Bu, adın dosyaya erişmek için kullanılan URL ile eşleşmediğini oldukça zayıf hale getirir, çünkü senaryo sağlamazsanız.
Nick Albrecht

InlineContent-Disposition'daki özelliği kullanmak, dosya adını, indirme işlemini zorlamak veya zorlamamaktan ayırma yeteneğini ayırmamı sağlar.
Nick Albrecht

4

Bu cevabın daha temiz olduğuna inanıyorum ( https://stackoverflow.com/a/3007668/550975 adresine göre )

    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
Önerilmez, İçerik Bertarafı ve uyumluluk için tercih edilen yöntemdir. stackoverflow.com/questions/10615797/…
Nick Albrecht

Bunun için teşekkürler. Her ne kadar MIME türünü değiştirmiş olsam da application/octet-streambu yine de dosyanın gösterilmek yerine indirilmesine neden oldu ve uyumlu görünüyor.
Serj Sagan

1
İçerik türü hakkında yalan söylemek gerçekten kötü bir fikir gibi geliyor. Bazı tarayıcılar, "kaydet veya aç" iletişim kutusunda kullanıcı için uygulama önermek üzere doğru içerik türüne bağlıdır.
Oskar Berggren

Niyetiniz tarayıcıya bir uygulama önermekse, o zaman bu iyi, ama bu soru özellikle indirmeye zorlamakla ilgili ...
Serj Sagan

@SerjSagan Ben daha çok kaydet / aç arasında seçim sunmak yerine tarayıcıların belirli dosya türlerini doğrudan görüntüleme veya bir eklenti kullanarak varsayılan davranışını atlatma ile ilgili olduğunu düşünüyorum. Örneğin JPEG ile şu anda denemedim, bu yüzden tam davranış konusunda emin değilim.
Oskar Berggren

3

FileVirtualPath -> Araştırma \ Global Office Review.pdf

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

5
Bu, önceki yanıtta zaten belirtilmişti ve önerilmez. Nedeniyle ilgili daha fazla ayrıntı için aşağıdaki soruya bakın. stackoverflow.com/questions/10615797/…
Nick Albrecht

1
Bu şekilde sunucudaki belleğe dosya yükleyerek sunucudaki kaynağı kullanmaz, doğru muyum?
Ian Jowett

1
Sana gerek yok gibi inanıyorum r @CrashOverride
Bishoy Hanna

1

Aşağıdaki kod benim için bir API hizmetinden bir pdf dosyası almak ve tarayıcıya yanıt için çalıştı - umarım yardımcı olur;

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
Cevap için teşekkürler. Dosyanın adını belirleme yeteneğini içeren bir çözüm aradığım kısmı kaçırdınız. Sadece bayt döndürüyorsunuz, bu nedenle ad URL'den çıkarılacaktır. Örnek olarak PDF kullanıyordum, ancak benim durumumda da birden fazla dosya türü için çalışması gerekiyordu. .NET Framework 4.x ve MVC <= 5 kullandığınız sürece çözümünüzün işe yarayacağını belirtmeliyim. .NET Core'a karşı çalışıyorsanız, en iyi seçeneğiniz kullanmaktırMicrosoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider
Nick Albrecht

@NickAlbrecht .Net Core kullanmadım - yukarıdaki örnek bir tarayıcıda pdf yanıtı içindi. Ancak indirmeyi denemek isterseniz: File (fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf, "dosya adınız"). Eğer cevabını aldıysan emin değilim. Lütfen bunun yardımcı olup olmadığını bildirin. .Net Core önerisi için teşekkürler. Ayrıca cevabımı yararlı bulduysanız lütfen bir oy ekleyin.
Jonny Boy

Sorunu uzun zaman önce kabul edilmiş olarak işaretlediğim cevapla çözdüm. Onları yararlı bulursanız, birkaç uyarıyı daha fazla işaret ediyordum. Benim asıl sorum 2011 idi, bu yüzden bu şimdiye kadar oldukça tarihli.
Nick Albrecht

@NickAlbrecht Teşekkür ederim. Çözdüğünün farkında değildim, çok eski bir yazı olduğunu gördüm. Bu forumu bazı asenkron ilgili cevapları ararken buldum, asenkron süreçlerinde yeniyim. Ben sadece paylaşılan paylaşılan sorunumu çözmeyi başardı. Zaman ayırdığın için senden daha.
Jonny Boy

0

Eylem yönteminin FileResult öğesini bir akış, bayt [] veya dosyanın sanal yolu ile döndürmesi gerekir. Ayrıca indirilen dosyanın içerik türünü de bilmeniz gerekir. İşte bir örnek (hızlı / kirli) yardımcı yöntem. Örnek video bağlantısı Asp.net core kullanarak dosya indirme

[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"},
            ...
        };
    }
}

Eğer (örneğin kütüphane, aracı, ürün, öğretici veya web) ilişkiliyseniz şeye Bağlama 's senin açıklamadan yığın taşması spam olarak kabul edilmektedir. Bakınız: "İyi" kendini tanıtmanın anlamı nedir? , kendi kendine tanıtım hakkında bazı ipuçları ve öneriler , Stack Overflow için "spam" tam tanımı nedir? ve Bir şeyi spam yapan şey .
Samuel Liew

0

Benim gibi Blazor'u öğrenirken bu konuya Razor bileşenleri aracılığıyla geldiyseniz, bu sorunu çözmek için kutunun dışında biraz daha düşünmeniz gerektiğini göreceksiniz. (Benim gibi) Blazor, MVC tipi dünyaya ilk yolunuzsa, bir mayın tarlasıdır, çünkü belgeler bu tür 'menial' görevler için yararlı değildir.

Bu nedenle, yazma sırasında, bir örneği aşağıdaki gibi olan dosya indirme bölümünü işlemek için bir MVC denetleyicisi yerleştirmeden vanilya Blazor / Razor kullanarak bunu başaramazsınız:

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

Ardından, uygulama başlatmanızın (Startup.cs) MVC'yi doğru şekilde kullanacak şekilde yapılandırıldığından ve aşağıdaki satırın bulunduğundan emin olun (yoksa ekleyin):

        services.AddMvc();

.. ve son olarak bileşeninizi denetleyiciye bağlanmak üzere değiştirin, örneğin (özel sınıf kullanan yinelemeli örnek):

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

Umarım bu, (benim gibi!) Mücadele eden herkesin Blazor alemindeki bu basit görünen soruya uygun bir cevap almasına yardımcı olur…!


Bu, aynı sorunla karşılaşan başkaları için yararlı olsa da, kendi sorunuzu Blazor'a özgü olduğunu belirten bir başlık ile gönderdiyseniz, kendinize cevap verin ve buraya ulaşan herkese işaret eden bir yorum eklerseniz onlar için daha keşfedilebilir olurdu blazor ile ilgili sorunları olan bu soru linkinizi kontrol edin. ASP.NET MVC için bu orijinal soru bile ulaşan ve cevabını ASP.NET Core ile ilgili olarak adapte olduğunu hissettim. Blazor, keşfettiğiniz gibi tamamen farklı bir canavar.
Nick Albrecht
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.