Télécharger le fichier à partir d'une méthode API Web ASP.NET à l'aide d'AngularJS

132

Dans mon projet Angular JS, j'ai une <a>balise d'ancrage qui, lorsqu'elle est cliquée, GETenvoie une requête HTTP à une méthode WebAPI qui renvoie un fichier.

Maintenant, je veux que le fichier soit téléchargé vers l'utilisateur une fois la demande réussie. Comment je fais ça?

La balise d'ancrage:

<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) {
        // ...
    });
}

Ma méthode WebAPI:

[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;
}
oùDragonsDwell
la source
1
Quel serait le type de fichier? image seulement?
Rashmin Javiya
@RashminJaviya Peut être .jpg, .doc, .xlsx, .docx, .txt ou .pdf.
whereDragonsDwell
Quel framework .Net vous utilisez?
Rashmin Javiya
@RashminJaviya .net 4.5
whereDragonsDwell
1
@Kurkula vous devriez utiliser le fichier de System.IO.File not from controller
Javysk

Réponses:

242

Le support du téléchargement de fichiers binaires en utilisant ajax n'est pas génial, il est encore en cours de développement en tant que brouillons de travail .

Méthode de téléchargement simple:

Vous pouvez demander au navigateur de télécharger le fichier demandé simplement en utilisant le code ci-dessous, ce qui est pris en charge dans tous les navigateurs et déclenchera évidemment la requête WebApi de la même manière.

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

Méthode de téléchargement binaire Ajax:

L'utilisation d'ajax pour télécharger le fichier binaire peut être effectuée dans certains navigateurs et ci-dessous se trouve une implémentation qui fonctionnera dans les dernières versions de Chrome, Internet Explorer, FireFox et Safari.

Il utilise un arraybuffertype de réponse, qui est ensuite converti en JavaScript blob, qui est ensuite présenté pour être enregistré à l'aide de la saveBlobméthode - bien que ce ne soit actuellement présent que dans Internet Explorer - ou transformé en une URL de données blob qui est ouverte par le navigateur, déclenchant la boîte de dialogue de téléchargement si le type mime est pris en charge pour l'affichage dans le navigateur.

Prise en charge d'Internet Explorer 11 (fixe)

Remarque: Internet Explorer 11 n'aimait pas utiliser la msSaveBlobfonction si elle avait été aliasée - peut-être une fonction de sécurité, mais plus probablement une faille, donc l'utilisation var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob ... etc.pour déterminer le saveBlobsupport disponible a provoqué une exception; d'où pourquoi le code ci-dessous teste maintenant navigator.msSaveBlobséparément. Merci? 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;
    });
};

Usage:

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

Remarques:

Vous devez modifier votre méthode WebApi pour renvoyer les en-têtes suivants:

  • J'ai utilisé l'en- x-filenametête pour envoyer le nom de fichier. Il s'agit d'un en-tête personnalisé pour plus de commodité, vous pouvez cependant extraire le nom de fichier de l'en- content-dispositiontête à l'aide d'expressions régulières.

  • Vous devez également définir l'en- content-typetête mime pour votre réponse, afin que le navigateur connaisse le format des données.

J'espère que ça aide.

Scott
la source
Salut @Scott J'ai utilisé votre méthode et cela fonctionne mais le navigateur enregistre le fichier sous le type html pas pdf. J'ai défini le type de contenu sur application / pdf et lorsque j'enregistre les outils de développement dans Chrome, le type de réponse est défini sur application / pdf, mais lorsque j'enregistre le fichier, il est affiché au format html, cela fonctionne, lorsque je l'ouvre, le fichier est ouvert en pdf mais dans le navigateur et avoir une icône par défaut pour mon navigateur. Savez-vous ce que je pourrais faire de mal?
Bartosz Bialecki
1
:-( désolé. J'ai manqué de voir ça. BTW cela fonctionne énormément. Encore mieux que filesaver.js
Jeeva Jsb
1
Lorsque j'essaye de télécharger un exécutable Microsoft via cette méthode, je récupère une taille de blob qui est environ 1,5 fois la taille réelle du fichier. Le fichier téléchargé a la taille incorrecte de l'objet blob. Des pensées sur pourquoi cela pourrait se produire? En se basant sur Fiddler, la taille de la réponse est correcte, mais la conversion du contenu en un objet blob l'augmente d'une manière ou d'une autre.
user3517454
1
J'ai finalement compris le problème ... J'avais changé le code du serveur d'un post pour obtenir, mais je n'avais pas changé les paramètres pour $ http.get. Ainsi, le type de réponse n'a jamais été défini comme tampon de tableau car il était passé en tant que troisième argument et non comme second.
user3517454
1
@RobertGoldwein Vous pouvez le faire, mais l'hypothèse est que si vous utilisez une application angularjs, vous voulez que l'utilisateur reste dans l'application, où l'état et la capacité à utiliser la fonctionnalité après le démarrage du téléchargement sont conservés. Si vous accédez directement au téléchargement, il n'y a aucune garantie que l'application restera active, car le navigateur peut ne pas gérer le téléchargement comme prévu. Imaginez si le serveur 500s ou 404s la demande. L'utilisateur est maintenant hors de l'application Angular. La suggestion la plus simple d'ouvrir le lien dans une nouvelle fenêtre en utilisant window.openest suggérée.
Scott
10

C # WebApi PDF télécharger tous fonctionnant avec l'authentification Angular JS

Contrôleur d'API Web

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

Service JS angulaire

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

L'un ou l'autre fera l'affaire

Angular JS Controller appelant le service

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

Et enfin la page HTML

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

Cela sera remanié en partageant simplement le code maintenant, j'espère que cela aidera quelqu'un car il m'a fallu un certain temps pour que cela fonctionne.

tfa
la source
Le code ci-dessus fonctionne sur tous les systèmes sauf ios, alors utilisez ces étapes si vous en avez besoin pour fonctionner sur ios Étape 1 vérifiez si ios stackoverflow.com/questions/9038625/detect-if-device-is-ios Étape 2 (si ios) utilisez ceci stackoverflow.com/questions/24485077/…
tfa
6

Pour moi, l'API Web était Rails et Angular côté client utilisé avec Restangular et FileSaver.js

API Web

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>

Contrôleur angulaire

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

Service angulaire

'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
la source
Comment avez-vous utilisé Filesaver.js avec cela? Comment l'avez-vous mis en œuvre?
Alan Dunning
2

Nous avons également dû développer une solution qui fonctionnerait même avec des API nécessitant une authentification (voir cet article )

Utiliser AngularJS en un mot, voici comment nous l'avons fait:

Étape 1: créer une directive dédiée

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

Étape 2: créer un modèle

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

Étape 3: utilisez-le

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

Cela rendra un bouton bleu. Une fois cliqué, un PDF sera téléchargé (Attention: le backend doit livrer le PDF en encodage Base64!) Et placé dans le href. Le bouton devient vert et fait passer le texte à Enregistrer . L'utilisateur peut cliquer à nouveau et sera présenté avec une boîte de dialogue de téléchargement standard du fichier my-awesome.pdf .

aix
la source
1

Envoyez votre fichier sous forme de chaîne base64.

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

Si la méthode attr ne fonctionne pas dans Firefox, vous pouvez également utiliser la méthode javaScript setAttribute

PPB
la source
var blob = new Blob ([atob (response.payload)], {"data": "attachment / csv; charset = utf-8;"}); saveAs (blob, 'nom de fichier');
PPB
Merci PPB, votre solution a fonctionné pour moi sauf pour l'atob. Ce n'était pas nécessaire pour moi.
Larry Flewwelling
0

Vous pouvez implémenter une fonction showfile qui prend en paramètres les données renvoyées par le WEBApi, et un nom de fichier pour le fichier que vous essayez de télécharger. Ce que j'ai fait, c'est de créer un service de navigateur distinct qui identifie le navigateur de l'utilisateur, puis gère le rendu du fichier en fonction du navigateur. Par exemple, si le navigateur cible est chrome sur un ipad, vous devez utiliser l'objet javascripts FileReader.

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
la source
1
Merci Scott d'avoir attrapé ces objets. J'ai remanié et ajouté une explication.
Erkin Djindjiev
0

J'ai parcouru une gamme de solutions et c'est ce que j'ai trouvé qui a très bien fonctionné pour moi.

Dans mon cas, j'avais besoin d'envoyer une demande de publication avec quelques informations d'identification. Une petite surcharge était d'ajouter jquery à l'intérieur du script. Mais ça valait le coup.

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
la source
-1

Dans votre composant ie code js angulaire:

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