Zurückgeben einer Datei zum Anzeigen / Herunterladen in ASP.NET MVC

304

Ich habe ein Problem beim Senden von in einer Datenbank gespeicherten Dateien an den Benutzer in ASP.NET MVC. Ich möchte eine Ansicht mit zwei Links, von denen einer die Datei anzeigt und der an den Browser gesendete Mimetyp bestimmt, wie damit umgegangen werden soll, und der andere, um einen Download zu erzwingen.

Wenn ich eine aufgerufene Datei anzeigen möchte SomeRandomFile.bakund dem Browser kein Programm zum Öffnen von Dateien dieses Typs zugeordnet ist, kann ich problemlos das Download-Verhalten verwenden. Wenn ich jedoch eine aufgerufene Datei anzeigen möchte SomeRandomFile.pdfoder SomeRandomFile.jpgdie Datei einfach öffnen möchte. Ich möchte aber auch einen Download-Link zur Seite halten, damit ich unabhängig vom Dateityp eine Download-Eingabeaufforderung erzwingen kann. Macht das Sinn?

Ich habe es versucht FileStreamResultund es funktioniert für die meisten Dateien. Der Konstruktor akzeptiert standardmäßig keinen Dateinamen. Daher wird unbekannten Dateien ein Dateiname zugewiesen, der auf der URL basiert (die die zu erweiternde Erweiterung basierend auf dem Inhaltstyp nicht kennt). Wenn ich den Dateinamen durch Angabe erzwinge, kann der Browser die Datei nicht mehr direkt öffnen, und ich erhalte eine Download-Eingabeaufforderung. Hat das noch jemand erlebt?

Dies sind die Beispiele für das, was ich bisher versucht habe.

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

Irgendwelche Vorschläge?


UPDATE: Diese Frage scheint bei vielen Leuten Anklang zu finden, daher dachte ich, ich würde ein Update veröffentlichen. Die Warnung zu der akzeptierten Antwort unten, die Oskar in Bezug auf internationale Zeichen hinzugefügt hat, ist vollständig gültig, und ich habe sie aufgrund der Verwendung der ContentDispositionKlasse einige Male getroffen . Ich habe seitdem meine Implementierung aktualisiert, um dies zu beheben. Der folgende Code stammt aus meiner letzten Version dieses Problems in einer ASP.NET Core-App (Full Framework). Er sollte jedoch auch in einer älteren MVC-Anwendung mit minimalen Änderungen funktionieren, da ich die System.Net.Http.Headers.ContentDispositionHeaderValueKlasse verwende.

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
}
Nick Albrecht
quelle

Antworten:

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

HINWEIS: In diesem obigen Beispielcode werden internationale Zeichen im Dateinamen nicht ordnungsgemäß berücksichtigt. Siehe RFC6266 für die entsprechende Standardisierung. Ich glaube, dass neuere Versionen der ASP.Net MVC- File()Methode und der ContentDispositionHeaderValueKlasse dies ordnungsgemäß berücksichtigen. - Oskar 25.02.2016

Darin Dimitrov
quelle
7
Wenn ich mich richtig erinnere, kann es ohne Anführungszeichen stehen, solange der Dateiname keine Leerzeichen enthält (ich habe meinen durch HttpUtility.UrlEncode () geführt, um dies zu erreichen).
Keith Williams
22
Hinweis: Wenn Sie dies verwenden und festlegen Inline = true, stellen Sie sicher, dass Sie NICHT die 3-Parameter-Überladung verwenden File(), bei der der Dateiname als 3. Parameter verwendet wird. Es wird im Internet Explorer arbeiten, aber Chrome wird ein Duplikat Kopf berichten und sich weigern , das Bild zu präsentieren.
Faust
74
Welcher Typ ist var document = ...?
TTT
7
@ user1103990, es ist Ihr Domain-Modell.
Darin Dimitrov
3
Bei Verwendung von MVC 5 ist der Header für die Inhaltsdisposition nicht mehr erforderlich, da er bereits Teil des Antwortheaders ist. Aber ich bekomme nur einen Download-Dialog in FF, keinen Dialog in Chrome und IE
Legends
124

Ich hatte Probleme mit der akzeptierten Antwort, da kein Typ auf die Variable "document" hinwies: var document = ...Ich poste also, was für mich als Alternative funktioniert hat, falls jemand anderes Probleme hat.

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);
}
ooXei1sh
quelle
4
Die Dokumentvariable war nur eine Klasse (POCO), die die Informationen zu dem Dokument darstellt, das Sie zurückgeben möchten. Das wurde auch bei der akzeptierten Antwort gefragt. Es kann von einem ORM, von einer manuell erstellten SQL-Abfrage, vom Dateisystem (aus dem Sie Informationen abrufen) oder von einem anderen Datenspeicher stammen. Für die ursprüngliche Frage, woher Ihr Dokumentbytes / Dateiname / MIME-Typ stammt, war dies irrelevant, sodass der Code nicht verschmutzt wurde. Vielen Dank, dass Sie ein Beispiel nur mit dem Dateisystem beigesteuert haben.
Nick Albrecht
1
Diese Lösung funktioniert nicht richtig, wenn der Dateiname internationale Zeichen außerhalb von US-ASCII enthält.
Oskar Berggren
1
Danke, hat meinen Tag gerettet :)
Dipesh
1
Eine Alternative dazu AppDomain.CurrentDomain.BaseDirectoryist System.Web.HttpContext.Current.Server.MapPath("~"), dass dies auf einem realen Server möglicherweise besser funktioniert als auf einem lokalen Computer.
Chris Thompson
15

Darin Dimitrovs Antwort ist richtig. Nur eine Ergänzung:

Response.AppendHeader("Content-Disposition", cd.ToString());Dies kann dazu führen, dass der Browser das Rendern der Datei fehlschlägt, wenn Ihre Antwort bereits einen "Content-Disposition" -Header enthält. In diesem Fall möchten Sie möglicherweise Folgendes verwenden:

Response.Headers.Add("Content-Disposition", cd.ToString());
Löwe
quelle
Ich finde , dass der Content-Type auch Einfluss hat, für pdfDatei, wenn ich Content-Type gesetzt , wie System.Net.Mime.MediaTypeNames.Application.Octetes herunterladen sogar zwingen werde , wenn ich gesetzt Inline = true, aber wenn ich nach Response.ContentType = MimeMapping.GetMimeMapping(filePath), das heißt application/pdf, es kann richtig eher geöffnet als Download
yu Yang Jian
Response.Headers.Adderfordern einen integrierten IIS-Pipeline-Modus. Selbst wenn der App-Pool als integriert festgelegt ist, wird eine Ausnahme ausgelöst. Lösung. Verwenden Sie Response.AddHeader. Siehe Thread SO: stackoverflow.com/questions/22313167/...
roland
12

Um anzuzeigen Datei (txt zum Beispiel):

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

So laden Sie eine Datei herunter (z. B. txt):

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

Hinweis: Um eine Datei herunterzuladen, müssen wir das Argument fileDownloadName übergeben

Ashot Muradian
quelle
Das einzige Problem bei diesem Ansatz besteht darin, dass der Dateiname nur angegeben wird, wenn Sie die Methode verwenden, die einen Download erzwingt. Ich kann nicht den richtigen Dateinamen senden, wenn der Browser bestimmen soll, wie die Datei geöffnet werden soll. Die externe App versucht also, meine URL als Dateinamen zu verwenden. Dies ist normalerweise eine Art ID für das Dokument in der Datenbank, normalerweise ohne Dateierweiterung. Dies führt dazu, dass der Name ziemlich schlecht ist und der Name nicht mit der URL übereinstimmt, die für den Zugriff auf die Datei verwendet wird. Das Szenario ist das, wenn Sie nichts angeben.
Nick Albrecht
Durch die Verwendung der InlineEigenschaft in der Inhaltsdisposition kann ich die Möglichkeit zum Festlegen des Dateinamens vom Verhalten beim Erzwingen des Herunterladens trennen oder nicht.
Nick Albrecht
4

Ich glaube, diese Antwort ist sauberer (basierend auf 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));
    }
Serj Sagan
quelle
9
Nicht empfohlen, Content-Disposition ist die bevorzugte Methode für Klarheit und Kompatibilität. stackoverflow.com/questions/10615797/…
Nick Albrecht
Dank dafür. Obwohl ich den MIME-Typ in geändert habe application/octet-streamund dies dennoch dazu führte, dass die Datei heruntergeladen und nicht angezeigt wurde, scheint sie kompatibel zu sein.
Serj Sagan
1
Über den Inhaltstyp zu lügen, klingt nach einer wirklich schlechten Idee. Einige Browser hängen vom richtigen Inhaltstyp ab, um Anwendungen für den Benutzer im Dialogfeld "Speichern oder Öffnen" vorzuschlagen.
Oskar Berggren
Wenn Sie beabsichtigen, dass der Browser eine App vorschlägt, ist das in Ordnung, aber bei dieser Frage geht es speziell darum, den Download zu erzwingen ...
Serj Sagan
@SerjSagan Ich denke, es geht mehr darum, das Verhalten des Browsers zu umgehen, bestimmte Dateitypen standardmäßig direkt anzuzeigen oder ein Plugin zu verwenden, anstatt die Wahl zwischen Speichern / Öffnen anzubieten. Ich habe es momentan nicht mit zB JPEG versucht, bin mir also nicht sicher, wie es sich genau verhält.
Oskar Berggren
3

FileVirtualPath -> Research \ Global Office Review.pdf

public virtual ActionResult GetFile()
{
    return File(FileVirtualPath, "application/force-download", Path.GetFileName(FileVirtualPath));
}
Bishoy Hanna
quelle
5
Dies wurde bereits in der vorherigen Antwort erwähnt und wird nicht empfohlen. Weitere Informationen zum Warum finden Sie in der folgenden Frage. stackoverflow.com/questions/10615797/…
Nick Albrecht
1
Auf diese Weise werden keine Ressourcen auf dem Server verwendet, indem Dateien in den Speicher auf dem Server geladen werden. Bin ich richtig?
Ian Jowett
1
Ich glaube schon, da du nicht richtig bist @CrashOverride
Bishoy Hanna
1

Der folgende Code hat für mich funktioniert, um eine PDF-Datei von einem API-Dienst abzurufen und an den Browser zu senden - ich hoffe, es hilft;

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);
    }
Jonny Boy
quelle
2
Danke für die Antwort. Sie haben den Teil verpasst, in dem ich nach einer Lösung gesucht habe, bei der der Name der Datei angegeben werden kann. Sie geben nur Bytes zurück, sodass der Name aus der URL abgeleitet wird. Ich habe PDF als Beispiel verwendet, aber ich brauchte es, um in meinem Fall auch für mehrere andere Dateitypen zu funktionieren. Ich sollte erwähnen , dass Ihre Lösung würde so lange arbeiten , wie Sie .NET Framework 4.x verwenden und MVC <= 5. Wenn Sie gegen .NET - Core laufen lassen , ist Ihre beste Wette verwendenMicrosoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider
Nick Albrecht
@NickAlbrecht Ich habe .Net Core nicht verwendet - das obige Beispiel war für die PDF-Antwort in einem Browser. Wenn Sie jedoch herunterladen möchten, versuchen Sie: Datei (fileContentBytes, System.Net.Mime.MediaTypeNames.Application.Pdf, "Ihr Dateiname"). Ich bin mir jedoch nicht sicher, ob Sie Ihre Antwort erhalten haben. Bitte lassen Sie mich wissen, ob dies hilft. Vielen Dank für den .Net Core-Vorschlag. Auch wenn Sie meine Antwort nützlich fanden, fügen Sie bitte eine Stimme hinzu.
Jonny Boy
Ich habe das Problem bereits vor langer Zeit mit der Antwort gelöst, die ich als akzeptiert markiert habe. Ich habe eher auf einige Vorbehalte hingewiesen, falls Sie sie nützlich fanden. Meine ursprüngliche Frage war 2011, daher ist diese mittlerweile ziemlich veraltet.
Nick Albrecht
@ NickAlbrecht Danke. Ich wusste nicht, dass du es gelöst hast, ich habe gesehen, dass es ein sehr alter Beitrag ist. Ich habe dieses Forum gefunden, als ich nach asynchronen Antworten gesucht habe. Ich bin neu in den asynchronen Prozessen. Ich konnte mein Problem lösen, also nur geteilt. Als du für deine Zeit.
Jonny Boy
0

Die Aktionsmethode muss FileResult entweder mit einem Stream, Byte [] oder einem virtuellen Pfad der Datei zurückgeben. Sie müssen auch den Inhaltstyp der heruntergeladenen Datei kennen. Hier ist ein Beispiel für eine (schnelle / schmutzige) Dienstprogrammmethode. Beispielvideolink So laden Sie Dateien mit asp.net core herunter

[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"},
            ...
        };
    }
}
VETRIVEL D.
quelle
Das Verknüpfen mit etwas, mit dem Sie verbunden sind (z. B. einer Bibliothek, einem Tool, einem Produkt, einem Tutorial oder einer Website), ohne dass angegeben wird, dass es Ihnen gehört, gilt als Spam bei Stack Overflow. Siehe: Was bedeutet "gute" Eigenwerbung? , einige Tipps und Ratschläge zur Eigenwerbung , Was ist die genaue Definition von "Spam" für Stapelüberlauf? und was macht etwas Spam .
Samuel Liew
0

Wenn Sie wie ich beim Erlernen von Blazor über Razor-Komponenten zu diesem Thema gekommen sind, müssen Sie etwas über den Tellerrand hinaus denken, um dieses Problem zu lösen. Es ist ein kleines Minenfeld, wenn (auch wie ich) Blazor Ihr erster Ausflug in die Welt des MVC-Typs ist, da die Dokumentation für solche „einfachen“ Aufgaben nicht so hilfreich ist.

Zum Zeitpunkt des Schreibens können Sie dies mit Vanilla Blazor / Razor nicht erreichen, ohne einen MVC-Controller für den Dateidownload-Teil einzubetten. Ein Beispiel hierfür ist wie folgt:

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

Stellen Sie als Nächstes sicher, dass Ihr Anwendungsstart (Startup.cs) für die korrekte Verwendung von MVC konfiguriert ist und die folgende Zeile vorhanden ist (fügen Sie sie hinzu, wenn nicht):

        services.AddMvc();

.. und ändern Sie schließlich Ihre Komponente, um beispielsweise eine Verknüpfung mit dem Controller herzustellen (iteratives Beispiel unter Verwendung einer benutzerdefinierten Klasse):

    <tbody>
        @foreach (var attachment in yourAttachments)
        {
        <tr>
            <td><a href="api/[email protected]" 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>

Hoffentlich hilft dies jedem, der Probleme hatte (wie ich!), Eine angemessene Antwort auf diese scheinbar einfache Frage im Reich von Blazor zu finden…!

VorTechS
quelle
Während dies für andere nützlich sein könnte, die auf dasselbe Problem stoßen wie Sie, wäre es für sie besser erkennbar, wenn Sie Ihre eigene Frage mit einem Titel veröffentlichen, der angibt, dass sie nur für Blazor gilt, sie selbst beantworten und hier einen Kommentar hinzufügen, der vorschlägt, dass jemand sie erreicht Diese Frage mit Problemen bezüglich Blazor überprüfen Sie Ihren Link. Ich hatte das Gefühl, auch mit dieser ursprünglichen Frage für ASP.NET MVC zu erreichen und ihre Antwort so anzupassen, dass sie für ASP.NET Core relevant ist. Blazor ist ein ganz anderes Tier, wie Sie entdeckt haben.
Nick Albrecht