Restituzione di un file da visualizzare / scaricare in ASP.NET MVC


304

Riscontro un problema con l'invio di file archiviati in un database all'utente in ASP.NET MVC. Quello che voglio è una vista che elenca due collegamenti, uno per visualizzare il file e lasciare che il mimetype inviato al browser determini come deve essere gestito e l'altro per forzare un download.

Se scelgo di visualizzare un file chiamato SomeRandomFile.bake il browser non ha un programma associato per aprire file di questo tipo, allora non ho problemi con l'impostazione predefinita del comportamento di download. Tuttavia, se scelgo di visualizzare un file chiamato SomeRandomFile.pdfo SomeRandomFile.jpgvoglio che il file si apra semplicemente. Ma voglio anche mantenere un collegamento di download sul lato in modo da poter forzare un prompt di download indipendentemente dal tipo di file. Questo ha senso?

Ho provato FileStreamResult e funziona per la maggior parte dei file, il suo costruttore non accetta un nome file per impostazione predefinita, quindi ai file sconosciuti viene assegnato un nome file basato sull'URL (che non conosce l'estensione da fornire in base al tipo di contenuto). Se forzo il nome del file specificandolo, perdo la capacità del browser di aprire direttamente il file e ricevo un prompt per il download. Qualcun altro ha riscontrato questo?

Questi sono gli esempi di ciò che ho provato finora.

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

Eventuali suggerimenti?


AGGIORNAMENTO: Questa domanda sembra colpire un accordo con molte persone, quindi ho pensato di pubblicare un aggiornamento. L'avvertimento sulla risposta accettata di seguito che è stato aggiunto da Oskar riguardo ai personaggi internazionali è completamente valido, e l'ho colpito un paio di volte a causa dell'uso della ContentDispositionclasse. Da allora ho aggiornato la mia implementazione per risolvere questo problema. Mentre il codice riportato di seguito proviene dalla mia incarnazione più recente di questo problema in un'app ASP.NET Core (Full Framework), dovrebbe funzionare con modifiche minime in un'applicazione MVC precedente e poiché sto usando la System.Net.Http.Headers.ContentDispositionHeaderValueclasse.

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
}

Risposte:


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

NOTA: questo codice di esempio sopra riportato non riesce a tenere correttamente conto dei caratteri internazionali nel nome del file. Vedere RFC6266 per la standardizzazione pertinente. Credo che le versioni recenti del File()metodo ASP.Net MVC e la ContentDispositionHeaderValueclasse lo spieghino correttamente. - Oskar 25/02/2016


7
Se ricordo bene, può essere senza virgolette fintanto che il nome del file non ha spazi (ho eseguito il mio attraverso HttpUtility.UrlEncode () per raggiungere questo obiettivo).
Keith Williams,

22
Nota: se lo usi e imposti, Inline = trueassicurati di NON usare il sovraccarico a 3 parametri File()che assume il nome del file come terzo parametro. Essa sarà lavorare in IE, ma Chrome segnalare un colpo di testa duplicato e si rifiutano di presentare l'immagine.
Faust,

74
Di che tipo è var document = ...?
TTT,

7
@ user1103990, è il tuo modello di dominio.
Darin Dimitrov,

3
Usando MVC 5, non è più necessario l'intestazione content-disposition, poiché fa già parte dell'intestazione response. Ma ottengo solo una finestra di download in FF, nessuna finestra di dialogo in Chrome e IE
Legends,

124

Ho avuto problemi con la risposta accettata a causa della mancanza di suggerimenti sul tipo nella variabile "documento": var document = ...Quindi sto pubblicando ciò che ha funzionato per me come alternativa nel caso in cui qualcun altro abbia problemi.

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
La variabile del documento era solo una classe (POCO) che rappresentava le informazioni sul documento che si voleva restituire. Ciò è stato chiesto anche sulla risposta accettata. Potrebbe provenire da un ORM, da una query SQL creata manualmente, dal file system (mentre il tuo estrae informazioni da) o da qualche altro archivio dati. Era irrilevante per la domanda originale da dove provenivano i byte del documento / nome file / tipo mime, quindi è stato lasciato fuori per non confondere il codice. Grazie per aver contribuito con un esempio usando solo il file system.
Nick Albrecht,

1
Questa soluzione non funziona correttamente quando il nome file contiene caratteri internazionali al di fuori di US-ASCII.
Oskar Berggren,

1
Grazie, mi
hai

1
Un'alternativa AppDomain.CurrentDomain.BaseDirectoryè che System.Web.HttpContext.Current.Server.MapPath("~")potrebbe funzionare meglio su un server reale rispetto a un computer locale.
Chris Thompson,

15

La risposta di Darin Dimitrov è corretta. Solo un'aggiunta:

Response.AppendHeader("Content-Disposition", cd.ToString());è possibile che il browser non riesca a eseguire il rendering del file se la risposta contiene già un'intestazione "Disposizione contenuto". In tal caso, potresti voler utilizzare:

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

Trovo che anche il tipo di contenuto abbia influenza, per il pdffile, se imposto il tipo di contenuto come System.Net.Mime.MediaTypeNames.Application.Octet, forzerà il download anche quando lo imposto Inline = true, ma se imposto come Response.ContentType = MimeMapping.GetMimeMapping(filePath), cioè application/pdf, può aprirsi correttamente anziché scaricare
yu Yang Jian,

Response.Headers.Addrichiede la modalità di pipeline integrata IIS. Inoltre, anche se il pool di app è impostato come integrato, genererà un'eccezione. Soluzione. Usa Response.AddHeader. Vedi discussione SO: stackoverflow.com/questions/22313167/…
roland

12

Per visualizzare il file (ad esempio txt):

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

Per scaricare il file (ad esempio txt):

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

nota: per scaricare il file dovremmo passare l' argomento fileDownloadName


L'unico problema con questo approccio è che il nome del file viene fornito solo se si utilizza il metodo che forza un download. Non mi consente di inviare il nome file corretto quando desidero che il browser determini come aprire il file. Quindi l'app esterna tenterà di utilizzare il mio URL come nome del file. Che di solito sarà una sorta di id per il documento nel database, in genere senza estensione di file. Questo rende per un nome piuttosto scadente il nome non corrisponde all'URL utilizzato per accedere al file, poiché lo scenario è che se non si fornisce.
Nick Albrecht,

L'utilizzo della Inlineproprietà in Content-Disposition mi consente di separare la possibilità di impostare il nome del file dal comportamento di forzare il download o meno.
Nick Albrecht,

4

Credo che questa risposta sia più pulita, (basata su 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
Sconsigliato, Content-Disposition è il metodo preferito per chiarezza e compatibilità. stackoverflow.com/questions/10615797/…
Nick Albrecht,

Grazie per quello Anche se ho cambiato il tipo MIME in application/octet-streame ciò ha comunque causato il download del file anziché la sua visualizzazione, e sembra essere compatibile.
Serj Sagan,

1
Mentire sul tipo di contenuto sembra una pessima idea. Alcuni browser dipendono dal tipo di contenuto corretto per suggerire applicazioni per l'utente nella finestra di dialogo "Salva o apri".
Oskar Berggren,

Se il tuo intento è quello di fare in modo che il browser suggerisca un'app, allora va bene, ma questa domanda riguarda specificamente la forzatura del download ...
Serj Sagan,

@SerjSagan Penso che si tratti più di aggirare il comportamento dei browser di default per visualizzare determinati tipi di file direttamente o usando un plugin, invece di offrire la scelta tra save / open. Non ho provato con ad esempio JPEG in questo momento, quindi non sono sicuro del comportamento esatto però.
Oskar Berggren

3

FileVirtualPath -> Ricerca \ Global Office Review.pdf

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

5
Questo è già stato menzionato nella risposta precedente e non è raccomandato. Vedi la seguente domanda per maggiori dettagli sul perché. stackoverflow.com/questions/10615797/…
Nick Albrecht

1
In questo modo non utilizza le risorse sul server caricando il file nella memoria sul server, ho ragione?
Ian Jowett,

1
Credo di sì, poiché non c'è bisogno di te, giusto @CrashOverride
Bishoy Hanna,

1

Il codice seguente ha funzionato per me per ottenere un file pdf da un servizio API e rispondere al browser - spero che sia d'aiuto;

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
Grazie per la risposta. Ti sei perso la parte in cui cercavo una soluzione che includesse la possibilità di specificare il nome del file. Stai solo restituendo byte, quindi il nome verrebbe dedotto dall'URL. Stavo usando PDF come esempio, ma nel mio caso ne avevo bisogno per funzionare anche con più altri tipi di file. Devo dire che la tua soluzione funzionerebbe fino a quando stai usando .NET Framework 4.xe MVC <= 5. Se corri contro .NET Core, la soluzione migliore è usareMicrosoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider
Nick Albrecht,

@NickAlbrecht Non ho usato .Net Core - l'esempio sopra è stato per la risposta pdf su un browser. Tuttavia, se si desidera scaricare provare: File (fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf, "il tuo nome file"). Non sono sicuro però se hai la tua risposta. per favore fatemi sapere se questo aiuta.Grazie per il suggerimento .Net Core però. Inoltre, se hai trovato utile la mia risposta, aggiungi un voto.
Jonny Boy,

Ho già risolto il problema molto tempo fa con la risposta che ho contrassegnato come accettato. Stavo più sottolineando alcuni avvertimenti nel caso li avessi trovati utili. La mia domanda originale era il 2011, quindi ormai è abbastanza datata.
Nick Albrecht,

@NickAlbrecht Grazie. Non sapevo che lo avessi risolto, ho visto che è un post molto vecchio. Ho trovato questo forum mentre cercavo risposte asincrone, sono nuovo ai processi asincroni. sono stato in grado di risolvere il mio problema, quindi appena condiviso. Di te per il tuo tempo.
Jonny Boy,

0

Il metodo di azione deve restituire FileResult con uno stream, byte [] o percorso virtuale del file. Sarà inoltre necessario conoscere il tipo di contenuto del file scaricato. Ecco un esempio di metodo di utilità (rapido / sporco). Esempio di collegamento video Come scaricare file utilizzando asp.net core

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

Il collegamento a qualcosa a cui sei affiliato (ad es. Una biblioteca, uno strumento, un prodotto, un tutorial o un sito Web) senza rivelare il tuo è considerato spam sullo StackTranslate.it. Vedi: Cosa significa autopromozione "buona"? , alcuni suggerimenti e consigli sull'autopromozione , Qual è la definizione esatta di "spam" per StackTranslate.it? e cosa rende spam qualcosa .
Samuel Liew

0

Se, come me, sei arrivato a questo argomento tramite i componenti Razor mentre stai imparando Blazor, allora troverai che devi risolvere un po 'di più fuori dagli schemi per risolvere questo problema. È un po 'un campo minato se (anche come me) Blazor è il tuo primo avamposto nel mondo di tipo MVC, poiché la documentazione non è altrettanto utile per tali compiti "umili".

Pertanto, al momento della stesura di questo documento, non è possibile ottenere questo risultato utilizzando Vanilla Blazor / Razor senza incorporare un controller MVC per gestire la parte di download del file, un esempio del quale è il seguente:

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

Successivamente, assicurati che l'avvio dell'applicazione (Startup.cs) sia configurato per utilizzare correttamente MVC e abbia presente la seguente riga (aggiungila in caso contrario):

        services.AddMvc();

.. e infine modificare il componente per collegarlo al controller, ad esempio (esempio basato su iterativi che utilizzano una classe personalizzata):

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

Spero che questo aiuti chiunque abbia lottato (come me!) Per ottenere una risposta adeguata a questa domanda apparentemente semplice nei regni di Blazor ...!


Mentre questo potrebbe essere utile per gli altri che incontrano lo stesso problema che hai riscontrato, sarebbe più rilevabile per loro se hai pubblicato la tua domanda con un titolo che indica che è unico per Blazor, rispondi tu stesso e aggiungi un commento qui suggerendo a chiunque di raggiungere questa domanda con problemi riguardanti blazor controlla il tuo link. Ho sentito che stavo raggiungendo anche con questa domanda originale rivolta ad ASP.NET MVC e ad adattare la sua risposta per essere rilevante per ASP.NET Core. Blazor è una bestia totalmente diversa, come hai scoperto.
Nick Albrecht,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.