Scarica il file da un metodo API Web ASP.NET utilizzando AngularJS


132

Nel mio progetto Angular JS, ho un <a>tag anchor, che quando viene cliccato fa una GETrichiesta HTTP a un metodo WebAPI che restituisce un file.

Ora voglio che il file venga scaricato per l'utente una volta che la richiesta ha esito positivo. Come lo faccio?

Il tag anchor:

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

Il mio metodo 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;
}

1
Quale sarebbe il tipo di file? solo immagine?
Rashmin Javiya,

@RashminJaviya Potrebbe essere .jpg, .doc, .xlsx, .docx, .txt o .pdf.
WhereDragonsDwell

Quale framework .Net stai usando?
Rashmin Javiya,

@RashminJaviya .net 4.5
whereDragonsDwell

1
@Kurkula dovresti usare File di System.IO.File non dal controller
Javysk

Risposte:


242

Il supporto per il download di file binari nell'uso di Ajax non è eccezionale, è ancora in fase di sviluppo come bozze funzionanti .

Metodo di download semplice:

Puoi fare in modo che il browser scarichi il file richiesto semplicemente usando il codice qui sotto, che è supportato in tutti i browser e ovviamente attiverà la richiesta WebApi allo stesso modo.

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

Metodo di download binario Ajax:

L'utilizzo di ajax per scaricare il file binario può essere eseguito in alcuni browser e di seguito è un'implementazione che funzionerà con le versioni più recenti di Chrome, Internet Explorer, FireFox e Safari.

Usa un arraybuffer tipo di risposta, che viene quindi convertito in JavaScript blob, che viene quindi presentato per salvare utilizzando il saveBlobmetodo - sebbene sia attualmente presente solo in Internet Explorer - o trasformato in un URL di dati BLOB che viene aperto dal browser, attivando la finestra di download se il tipo mime è supportato per la visualizzazione nel browser.

Supporto per Internet Explorer 11 (fisso)

Nota: a Internet Explorer 11 non piaceva usare la msSaveBlobfunzione se fosse stata aliasata - forse una funzionalità di sicurezza, ma più probabilmente un difetto, quindi l'utilizzo var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob ... etc.per determinare il saveBlobsupporto disponibile ha causato un'eccezione; quindi perché il codice qui sotto ora prova navigator.msSaveBlobseparatamente. Grazie? 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;
    });
};

Uso:

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

Appunti:

È necessario modificare il metodo WebApi per restituire le seguenti intestazioni:

  • Ho usato l' x-filenameintestazione per inviare il nome file. Questa è un'intestazione personalizzata per comodità, tuttavia è possibile estrarre il nome file dall'intestazione content-dispositionutilizzando espressioni regolari.

  • Dovresti impostare anche l' content-typeintestazione mime per la tua risposta, in modo che il browser conosca il formato dei dati.

Spero che aiuti.


Ciao @Scott Ho usato il tuo metodo e funziona ma il browser salva il file come tipo html non pdf. Ho impostato content-type su application / pdf e quando accedo a strumenti di sviluppo in Chrome il tipo di risposta è impostato su application / pdf ma quando salvo il file viene mostrato come HTML, funziona, quando lo apro il file è aperto come pdf ma nel browser e icona predefinita per il mio browser. Sai cosa potrei fare di sbagliato?
Bartosz Bialecki,

1
:-( scusami. Mi mancava di vederlo. A proposito, questo funziona molto. Ancora meglio di filesaver.js
Jeeva Jsb

1
Quando provo a scaricare un eseguibile Microsoft tramite questo metodo, ottengo una dimensione BLOB pari a circa 1,5 volte la dimensione effettiva del file. Il file che viene scaricato ha le dimensioni errate del BLOB. Qualche idea sul perché questo potrebbe accadere? Basandoti su Fiddler, la dimensione della risposta è corretta, ma la conversione del contenuto in un BLOB lo sta in qualche modo aumentando.
user3517454,

1
Alla fine ho capito il problema ... Avevo cambiato il codice del server da un post per ottenere, ma non avevo modificato i parametri per $ http.get. Quindi il tipo di risposta non è mai stato impostato come arraybuffer poiché veniva passato come terzo argomento e non come secondo.
user3517454,

1
@RobertGoldwein Puoi farlo, ma il presupposto è che se stai usando un'applicazione angularjs vuoi che l'utente rimanga nell'applicazione, dove lo stato e la capacità di usare la funzionalità dopo l'avvio del download sono mantenuti. Se si accede direttamente al download, non vi è alcuna garanzia che l'applicazione rimanga attiva, poiché il browser potrebbe non gestire il download nel modo previsto. Immagina se il server 500s o 404s la richiesta. L'utente è ora fuori dall'app Angular. window.openViene suggerito il suggerimento più semplice di aprire il collegamento in una nuova finestra .
Scott,

10

Download PDF C # WebApi tutto funzionante con autenticazione JS angolare

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

Servizio angolare JS

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

O uno farà

Controller angolare JS che chiama il servizio

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

E infine la pagina HTML

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

Questo sarà riformattato semplicemente condividendo il codice ora spero che aiuti qualcuno perché mi ci è voluto un po 'per farlo funzionare.


Il codice sopra funziona su tutti i sistemi ad eccezione di iOS, quindi utilizzare questi passaggi se è necessario per farlo su iOS Passaggio 1 verificare se iOS Stackoverflow.com/questions/9038625/detect-if-device-is-ios Passaggio 2 (se iOS) utilizzare questo stackoverflow.com/questions/24485077/...
TFA


6

Per me l'API Web era Rails e Angular lato client utilizzato con Restangular e 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>

Controller angolare

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

Servizio angolare

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

Come hai usato Filesaver.js con questo? Come lo hai implementato?
Alan Dunning,

2

Abbiamo anche dovuto sviluppare una soluzione che funzionasse anche con le API che richiedono l'autenticazione (vedi questo articolo )

Usando AngularJS in breve, ecco come l'abbiamo fatto:

Passaggio 1: creare una direttiva dedicata

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

Passaggio 2: creare un modello

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

Passaggio 3: utilizzalo

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

Questo renderà un pulsante blu. Quando si fa clic, verrà scaricato un PDF (Attenzione: il backend deve consegnare il PDF con la codifica Base64!) E inserirlo nell'href. Il pulsante diventa verde e cambia il testo in Salva . L'utente può fare nuovamente clic e verrà visualizzata una finestra di dialogo per il download del file standard per il file my-awesome.pdf .


1

Invia il tuo file come stringa 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();

Se il metodo attr non funziona in Firefox È inoltre possibile utilizzare il metodo javaScript setAttribute


var blob = new Blob ([atob (response.payload)], {"data": "allegato / csv; charset = utf-8;"}); saveAs (blob, 'nome file');
PPB,

Grazie PPB, la tua soluzione ha funzionato per me ad eccezione di atob. Non era necessario per me.
Larry Flewwelling,

0

È possibile implementare una funzione showfile che accetta i parametri dei dati restituiti da WEBApi e un nome file per il file che si sta tentando di scaricare. Quello che ho fatto è stato creare un servizio browser separato che identifica il browser dell'utente e quindi gestisce il rendering del file in base al browser. Ad esempio, se il browser di destinazione è Chrome su un iPad, è necessario utilizzare l'oggetto FileReader javascripts.

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

1
Grazie Scott per aver catturato quegli oggetti. Ho effettuato il refactoring e aggiunto una spiegazione.
Erkin Djindjiev,

0

Ho esaminato una serie di soluzioni e questo è quello che ho scoperto che ha funzionato alla grande per me.

Nel mio caso avevo bisogno di inviare una richiesta di post con alcune credenziali. Un piccolo sovraccarico era aggiungere jquery all'interno della sceneggiatura. Ma ne è valsa la pena.

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

    }

-1

Nel tuo componente cioè il codice js angolare:

function getthefile (){
window.location.href='http://localhost:1036/CourseRegConfirm/getfile';
};
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.