Laden Sie die Datei mit AngularJS von einer ASP.NET-Web-API-Methode herunter

132

In meinem Angular JS-Projekt habe ich ein <a>Ankertag, das beim Klicken eine HTTP- GETAnforderung an eine WebAPI-Methode sendet, die eine Datei zurückgibt.

Jetzt möchte ich, dass die Datei auf den Benutzer heruntergeladen wird, sobald die Anforderung erfolgreich ist. Wie mache ich das?

Das Ankertag:

<a href="#" ng-click="getthefile()">Download img</a>

AngularJS:

$scope.getthefile = function () {        
    $http({
        method: 'GET',
        cache: false,
        url: $scope.appPath + 'CourseRegConfirm/getfile',            
        headers: {
            'Content-Type': 'application/json; charset=utf-8'
        }
    }).success(function (data, status) {
        console.log(data); // Displays text data if the file is a text file, binary if it's an image            
        // What should I write here to download the file I receive from the WebAPI method?
    }).error(function (data, status) {
        // ...
    });
}

Meine WebAPI-Methode:

[Authorize]
[Route("getfile")]
public HttpResponseMessage GetTestFile()
{
    HttpResponseMessage result = null;
    var localFilePath = HttpContext.Current.Server.MapPath("~/timetable.jpg");

    if (!File.Exists(localFilePath))
    {
        result = Request.CreateResponse(HttpStatusCode.Gone);
    }
    else
    {
        // Serve the file to the client
        result = Request.CreateResponse(HttpStatusCode.OK);
        result.Content = new StreamContent(new FileStream(localFilePath, FileMode.Open, FileAccess.Read));
        result.Content.Headers.ContentDisposition = new System.Net.Http.Headers.ContentDispositionHeaderValue("attachment");
        result.Content.Headers.ContentDisposition.FileName = "SampleImg";                
    }

    return result;
}
whereDragonsDwell
quelle
1
Was wäre der Dateityp? nur Bild?
Rashmin Javiya
@RashminJaviya Könnte .jpg, .doc, .xlsx, .docx, .txt oder .pdf sein.
whereDragonsDwell
Welches .Net-Framework verwenden Sie?
Rashmin Javiya
@ RashminJaviya .net 4.5
whereDragonsDwell
1
@Kurkula Sie sollten Datei von System.IO.File nicht von Controller verwenden
Javysk

Antworten:

242

Die Unterstützung für das Herunterladen von Binärdateien mit Ajax ist nicht großartig, sie wird derzeit noch als Arbeitsentwurf entwickelt .

Einfache Download-Methode:

Sie können den Browser die angeforderte Datei einfach mithilfe des folgenden Codes herunterladen lassen. Dies wird in allen Browsern unterstützt und löst die WebApi-Anforderung offensichtlich trotzdem aus.

$scope.downloadFile = function(downloadPath) { 
    window.open(downloadPath, '_blank', '');  
}

Ajax binäre Download-Methode:

Die Verwendung von Ajax zum Herunterladen der Binärdatei kann in einigen Browsern erfolgen. Im Folgenden finden Sie eine Implementierung, die in den neuesten Versionen von Chrome, Internet Explorer, FireFox und Safari funktioniert.

Es wird ein arraybufferAntworttyp verwendet, der dann in ein JavaScript konvertiert wird blob, das dann entweder zum Speichern mithilfe der saveBlobMethode angezeigt wird - obwohl dies derzeit nur in Internet Explorer vorhanden ist - oder in eine Blob-Daten-URL umgewandelt wird, die vom Browser geöffnet und ausgelöst wird Der Download-Dialog, wenn der MIME-Typ für die Anzeige im Browser unterstützt wird.

Internet Explorer 11-Unterstützung (behoben)

Hinweis: Internet Explorer 11 mochte die Verwendung der msSaveBlobFunktion nicht, wenn sie mit einem Alias ​​versehen war - möglicherweise eine Sicherheitsfunktion, aber eher ein Fehler. Die Verwendung var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob ... etc.zur Ermittlung der verfügbaren saveBlobUnterstützung verursachte also eine Ausnahme. Daher wird der folgende Code jetzt navigator.msSaveBlobseparat getestet . Vielen Dank? Microsoft

// Based on an implementation here: web.student.tuwien.ac.at/~e0427417/jsdownload.html
$scope.downloadFile = function(httpPath) {
    // Use an arraybuffer
    $http.get(httpPath, { responseType: 'arraybuffer' })
    .success( function(data, status, headers) {

        var octetStreamMime = 'application/octet-stream';
        var success = false;

        // Get the headers
        headers = headers();

        // Get the filename from the x-filename header or default to "download.bin"
        var filename = headers['x-filename'] || 'download.bin';

        // Determine the content type from the header or default to "application/octet-stream"
        var contentType = headers['content-type'] || octetStreamMime;

        try
        {
            // Try using msSaveBlob if supported
            console.log("Trying saveBlob method ...");
            var blob = new Blob([data], { type: contentType });
            if(navigator.msSaveBlob)
                navigator.msSaveBlob(blob, filename);
            else {
                // Try using other saveBlob implementations, if available
                var saveBlob = navigator.webkitSaveBlob || navigator.mozSaveBlob || navigator.saveBlob;
                if(saveBlob === undefined) throw "Not supported";
                saveBlob(blob, filename);
            }
            console.log("saveBlob succeeded");
            success = true;
        } catch(ex)
        {
            console.log("saveBlob method failed with the following exception:");
            console.log(ex);
        }

        if(!success)
        {
            // Get the blob url creator
            var urlCreator = window.URL || window.webkitURL || window.mozURL || window.msURL;
            if(urlCreator)
            {
                // Try to use a download link
                var link = document.createElement('a');
                if('download' in link)
                {
                    // Try to simulate a click
                    try
                    {
                        // Prepare a blob URL
                        console.log("Trying download link method with simulated click ...");
                        var blob = new Blob([data], { type: contentType });
                        var url = urlCreator.createObjectURL(blob);
                        link.setAttribute('href', url);

                        // Set the download attribute (Supported in Chrome 14+ / Firefox 20+)
                        link.setAttribute("download", filename);

                        // Simulate clicking the download link
                        var event = document.createEvent('MouseEvents');
                        event.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
                        link.dispatchEvent(event);
                        console.log("Download link method with simulated click succeeded");
                        success = true;

                    } catch(ex) {
                        console.log("Download link method with simulated click failed with the following exception:");
                        console.log(ex);
                    }
                }

                if(!success)
                {
                    // Fallback to window.location method
                    try
                    {
                        // Prepare a blob URL
                        // Use application/octet-stream when using window.location to force download
                        console.log("Trying download link method with window.location ...");
                        var blob = new Blob([data], { type: octetStreamMime });
                        var url = urlCreator.createObjectURL(blob);
                        window.location = url;
                        console.log("Download link method with window.location succeeded");
                        success = true;
                    } catch(ex) {
                        console.log("Download link method with window.location failed with the following exception:");
                        console.log(ex);
                    }
                }

            }
        }

        if(!success)
        {
            // Fallback to window.open method
            console.log("No methods worked for saving the arraybuffer, using last resort window.open");
            window.open(httpPath, '_blank', '');
        }
    })
    .error(function(data, status) {
        console.log("Request failed with status: " + status);

        // Optionally write the error out to scope
        $scope.errorDetails = "Request failed with status: " + status;
    });
};

Verwendung:

var downloadPath = "/files/instructions.pdf";
$scope.downloadFile(downloadPath);

Anmerkungen:

Sie sollten Ihre WebApi-Methode ändern, um die folgenden Header zurückzugeben:

  • Ich habe den x-filenameHeader verwendet, um den Dateinamen zu senden. Dies ist der Einfachheit halber ein benutzerdefinierter Header. Sie können den Dateinamen jedoch content-dispositionmit regulären Ausdrücken aus dem Header extrahieren .

  • Sie sollten den content-typeMIME-Header auch für Ihre Antwort festlegen , damit der Browser das Datenformat kennt.

Ich hoffe das hilft.

Scott
quelle
Hallo @Scott, ich habe deine Methode verwendet und sie funktioniert, aber der Browser speichert die Datei als HTML-Typ, nicht als PDF. Ich setze den Inhaltstyp auf application / pdf und wenn ich Entwicklertools in Chrome einchecke, wird der Antworttyp auf application / pdf gesetzt, aber wenn ich die Datei speichere, wird sie als HTML angezeigt, es funktioniert, wenn ich sie öffne, ist die Datei als pdf geöffnet aber im browser und mit symbol standard für meinen browser. Weißt du was ich falsch machen könnte?
Bartosz Bialecki
1
:-( Entschuldigung. Ich habe das verpasst. Übrigens funktioniert das sehr gut. Noch besser als filesaver.js
Jeeva Jsb
1
Wenn ich versuche, eine ausführbare Microsoft-Datei über diese Methode herunterzuladen, erhalte ich eine Blob-Größe zurück, die ungefähr dem 1,5-fachen der tatsächlichen Dateigröße entspricht. Die heruntergeladene Datei hat die falsche Größe des Blobs. Irgendwelche Gedanken darüber, warum dies passieren könnte? Basierend auf dem Blick auf Fiddler ist die Größe der Antwort korrekt, aber die Konvertierung des Inhalts in einen Blob erhöht ihn irgendwie.
Benutzer3517454
1
Endlich das Problem herausgefunden ... Ich hatte den Servercode von einem Beitrag geändert, um ihn zu erhalten, aber ich hatte die Parameter für $ http.get nicht geändert. Daher wurde der Antworttyp nie als Array-Puffer festgelegt, da er als drittes Argument und nicht als zweites übergeben wurde.
Benutzer3517454
1
@RobertGoldwein Sie können dies tun, aber die Annahme ist, dass der Benutzer bei Verwendung einer AngularJS-Anwendung in der Anwendung bleiben soll, in der der Status und die Fähigkeit zur Verwendung der Funktionalität nach dem Start des Downloads erhalten bleiben. Wenn Sie direkt zum Download navigieren, gibt es keine Garantie dafür, dass die Anwendung aktiv bleibt, da der Browser den Download möglicherweise nicht wie erwartet handhabt. Stellen Sie sich vor, der Server 500s oder 404s die Anfrage. Der Benutzer ist jetzt nicht mehr in der Angular-App. Der einfachste Vorschlag, den Link in einem neuen Fenster mit zu öffnen, window.openwird vorgeschlagen.
Scott
10

C # WebApi PDF-Download alle, die mit Angular JS Authentication arbeiten

Web-API-Controller

[HttpGet]
    [Authorize]
    [Route("OpenFile/{QRFileId}")]
    public HttpResponseMessage OpenFile(int QRFileId)
    {
        QRFileRepository _repo = new QRFileRepository();
        var QRFile = _repo.GetQRFileById(QRFileId);
        if (QRFile == null)
            return new HttpResponseMessage(HttpStatusCode.BadRequest);
        string path = ConfigurationManager.AppSettings["QRFolder"] + + QRFile.QRId + @"\" + QRFile.FileName;
        if (!File.Exists(path))
            return new HttpResponseMessage(HttpStatusCode.BadRequest);

        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
        //response.Content = new StreamContent(new FileStream(localFilePath, FileMode.Open, FileAccess.Read));
        Byte[] bytes = File.ReadAllBytes(path);
        //String file = Convert.ToBase64String(bytes);
        response.Content = new ByteArrayContent(bytes);
        response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
        response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
        response.Content.Headers.ContentDisposition.FileName = QRFile.FileName;

        return response;
    }

Angular JS Service

this.getPDF = function (apiUrl) {
            var headers = {};
            headers.Authorization = 'Bearer ' + sessionStorage.tokenKey;
            var deferred = $q.defer();
            $http.get(
                hostApiUrl + apiUrl,
                {
                    responseType: 'arraybuffer',
                    headers: headers
                })
            .success(function (result, status, headers) {
                deferred.resolve(result);;
            })
             .error(function (data, status) {
                 console.log("Request failed with status: " + status);
             });
            return deferred.promise;
        }

        this.getPDF2 = function (apiUrl) {
            var promise = $http({
                method: 'GET',
                url: hostApiUrl + apiUrl,
                headers: { 'Authorization': 'Bearer ' + sessionStorage.tokenKey },
                responseType: 'arraybuffer'
            });
            promise.success(function (data) {
                return data;
            }).error(function (data, status) {
                console.log("Request failed with status: " + status);
            });
            return promise;
        }

Entweder wird man tun

Angular JS Controller, der den Dienst aufruft

vm.open3 = function () {
        var downloadedData = crudService.getPDF('ClientQRDetails/openfile/29');
        downloadedData.then(function (result) {
            var file = new Blob([result], { type: 'application/pdf;base64' });
            var fileURL = window.URL.createObjectURL(file);
            var seconds = new Date().getTime() / 1000;
            var fileName = "cert" + parseInt(seconds) + ".pdf";
            var a = document.createElement("a");
            document.body.appendChild(a);
            a.style = "display: none";
            a.href = fileURL;
            a.download = fileName;
            a.click();
        });
    };

Und zuletzt die HTML-Seite

<a class="btn btn-primary" ng-click="vm.open3()">FILE Http with crud service (3 getPDF)</a>

Dies wird überarbeitet, indem nur der Code geteilt wird. Ich hoffe, es hilft jemandem, da ich eine Weile gebraucht habe, um dies zum Laufen zu bringen.

tfa
quelle
Der obige Code funktioniert auf allen Systemen außer ios. Verwenden Sie diese Schritte, wenn Sie dies für ios benötigen. Schritt 1 Überprüfen Sie, ob ios stackoverflow.com/questions/9038625/detect-if-device-is-ios Schritt 2 (falls ios) dies verwendet stackoverflow.com/questions/24485077/…
tfa
6

Für mich war die Web-API Rails und clientseitig Angular, die mit Restangular und FileSaver.js verwendet wurden

Web-API

module Api
  module V1
    class DownloadsController < BaseController

      def show
        @download = Download.find(params[:id])
        send_data @download.blob_data
      end
    end
  end
end

HTML

 <a ng-click="download('foo')">download presentation</a>

Winkelregler

 $scope.download = function(type) {
    return Download.get(type);
  };

Winkelservice

'use strict';

app.service('Download', function Download(Restangular) {

  this.get = function(id) {
    return Restangular.one('api/v1/downloads', id).withHttpConfig({responseType: 'arraybuffer'}).get().then(function(data){
      console.log(data)
      var blob = new Blob([data], {
        type: "application/pdf"
      });
      //saveAs provided by FileSaver.js
      saveAs(blob, id + '.pdf');
    })
  }
});
AnkitG
quelle
Wie haben Sie Filesaver.js damit verwendet? Wie haben Sie es umgesetzt?
Alan Dunning
2

Wir mussten auch eine Lösung entwickeln, die sogar mit APIs funktioniert, die eine Authentifizierung erfordern (siehe diesen Artikel ).

AngularJS auf den Punkt gebracht: So haben wir es gemacht:

Schritt 1: Erstellen Sie eine dedizierte Direktive

// jQuery needed, uses Bootstrap classes, adjust the path of templateUrl
app.directive('pdfDownload', function() {
return {
    restrict: 'E',
    templateUrl: '/path/to/pdfDownload.tpl.html',
    scope: true,
    link: function(scope, element, attr) {
        var anchor = element.children()[0];

        // When the download starts, disable the link
        scope.$on('download-start', function() {
            $(anchor).attr('disabled', 'disabled');
        });

        // When the download finishes, attach the data to the link. Enable the link and change its appearance.
        scope.$on('downloaded', function(event, data) {
            $(anchor).attr({
                href: 'data:application/pdf;base64,' + data,
                download: attr.filename
            })
                .removeAttr('disabled')
                .text('Save')
                .removeClass('btn-primary')
                .addClass('btn-success');

            // Also overwrite the download pdf function to do nothing.
            scope.downloadPdf = function() {
            };
        });
    },
    controller: ['$scope', '$attrs', '$http', function($scope, $attrs, $http) {
        $scope.downloadPdf = function() {
            $scope.$emit('download-start');
            $http.get($attrs.url).then(function(response) {
                $scope.$emit('downloaded', response.data);
            });
        };
    }] 
});

Schritt 2: Erstellen Sie eine Vorlage

<a href="" class="btn btn-primary" ng-click="downloadPdf()">Download</a>

Schritt 3: Verwenden Sie es

<pdf-download url="/some/path/to/a.pdf" filename="my-awesome-pdf"></pdf-download>

Dadurch wird eine blaue Schaltfläche gerendert. Wenn Sie darauf klicken, wird ein PDF heruntergeladen (Achtung: Das Backend muss das PDF in Base64-Codierung liefern!) Und in die href eingefügt. Die Schaltfläche wird grün und schaltet den Text auf Speichern . Der Benutzer kann erneut klicken und erhält einen Standarddialog für die Download-Datei für die Datei my-awesome.pdf .

aix
quelle
1

Senden Sie Ihre Datei als base64-Zeichenfolge.

 var element = angular.element('<a/>');
                         element.attr({
                             href: 'data:attachment/csv;charset=utf-8,' + encodeURI(atob(response.payload)),
                             target: '_blank',
                             download: fname
                         })[0].click();

Wenn die attr-Methode in Firefox nicht funktioniert Sie können auch die JavaScript-Methode setAttribute verwenden

PPB
quelle
var blob = neuer Blob ([atob (response.payload)], {"data": "Anhang / csv; Zeichensatz = utf-8;"}); saveAs (Blob, 'Dateiname');
PPB
Vielen Dank, PPB, Ihre Lösung hat bis auf den Atob für mich funktioniert. Das war für mich nicht erforderlich.
Larry Flewwelling
0

Sie können eine Showfile-Funktion implementieren, die Parameter der von WEBApi zurückgegebenen Daten und einen Dateinamen für die Datei, die Sie herunterladen möchten, berücksichtigt. Ich habe einen separaten Browserdienst erstellt, der den Browser des Benutzers identifiziert und dann das Rendern der Datei basierend auf dem Browser übernimmt. Wenn der Zielbrowser beispielsweise auf einem iPad verchromt ist, müssen Sie das FileReader-Objekt javascripts verwenden.

FileService.showFile = function (data, fileName) {
    var blob = new Blob([data], { type: 'application/pdf' });

    if (BrowserService.isIE()) {
        window.navigator.msSaveOrOpenBlob(blob, fileName);
    }
    else if (BrowserService.isChromeIos()) {
        loadFileBlobFileReader(window, blob, fileName);
    }
    else if (BrowserService.isIOS() || BrowserService.isAndroid()) {
        var url = URL.createObjectURL(blob);
        window.location.href = url;
        window.document.title = fileName;
    } else {
        var url = URL.createObjectURL(blob);
        loadReportBrowser(url, window,fileName);
    }
}


function loadFileBrowser(url, window, fileName) {
    var iframe = window.document.createElement('iframe');
    iframe.src = url
    iframe.width = '100%';
    iframe.height = '100%';
    iframe.style.border = 'none';
    window.document.title = fileName;
    window.document.body.appendChild(iframe)
    window.document.body.style.margin = 0;
}

function loadFileBlobFileReader(window, blob,fileName) {
    var reader = new FileReader();
    reader.onload = function (e) {
        var bdata = btoa(reader.result);
        var datauri = 'data:application/pdf;base64,' + bdata;
        window.location.href = datauri;
        window.document.title = fileName;
    }
    reader.readAsBinaryString(blob);
}
Erkin Djindjiev
quelle
1
Vielen Dank, Scott, dass du diese Gegenstände gefangen hast. Ich habe überarbeitet und eine Erklärung hinzugefügt.
Erkin Djindjiev
0

Ich habe eine Reihe von Lösungen durchlaufen und festgestellt, dass dies für mich großartig funktioniert hat.

In meinem Fall musste ich eine Post-Anfrage mit einigen Anmeldeinformationen senden. Ein kleiner Aufwand bestand darin, dem Skript eine Abfrage hinzuzufügen. War es aber wert.

var printPDF = function () {
        //prevent double sending
        var sendz = {};
        sendz.action = "Print";
        sendz.url = "api/Print";
        jQuery('<form action="' + sendz.url + '" method="POST">' +
            '<input type="hidden" name="action" value="Print" />'+
            '<input type="hidden" name="userID" value="'+$scope.user.userID+'" />'+
            '<input type="hidden" name="ApiKey" value="' + $scope.user.ApiKey+'" />'+
            '</form>').appendTo('body').submit().remove();

    }
OneGhana
quelle
-1

In Ihrer Komponente, dh Winkel js Code:

function getthefile (){
window.location.href='http://localhost:1036/CourseRegConfirm/getfile';
};
Shivani Jadhav
quelle