Descargue el archivo de un método de API web ASP.NET usando AngularJS

132

En mi proyecto Angular JS, tengo una <a>etiqueta de anclaje, que al hacer clic hace una GETsolicitud HTTP a un método WebAPI que devuelve un archivo.

Ahora, quiero que el archivo se descargue al usuario una vez que la solicitud sea exitosa. ¿Cómo puedo hacer eso?

La etiqueta de anclaje:

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

Mi método 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;
}
whereDragonsDwell
fuente
1
¿Cuál sería el tipo de archivo? solo imagen?
Rashmin Javiya
@RashminJaviya podría ser .jpg, .doc, .xlsx, .docx, .txt o .pdf.
whereDragonsDwell
¿Qué marco .Net estás usando?
Rashmin Javiya
@RashminJaviya .net 4.5
whereDragonsDwell
1
@Kurkula, debe usar File of System.IO.File no desde el controlador
Javysk

Respuestas:

242

La compatibilidad para descargar archivos binarios al usar ajax no es excelente, todavía está en desarrollo como borradores de trabajo .

Método de descarga simple:

Puede hacer que el navegador descargue el archivo solicitado simplemente usando el código a continuación, y esto es compatible con todos los navegadores, y obviamente activará la solicitud de WebApi de la misma manera.

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

Método de descarga binaria Ajax:

El uso de ajax para descargar el archivo binario se puede hacer en algunos navegadores y a continuación se muestra una implementación que funcionará en las últimas versiones de Chrome, Internet Explorer, Firefox y Safari.

Utiliza un arraybuffertipo de respuesta, que luego se convierte en JavaScript blob, que luego se presenta para guardar usando el saveBlobmétodo, aunque actualmente solo está presente en Internet Explorer, o se convierte en una URL de datos de blob que abre el navegador, activando el diálogo de descarga si el tipo mime es compatible para ver en el navegador.

Soporte de Internet Explorer 11 (fijo)

Nota: a Internet Explorer 11 no le gustaba usar la msSaveBlobfunción si tenía un alias, quizás una característica de seguridad, pero más probablemente un defecto, por lo que usar var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob ... etc.para determinar el saveBlobsoporte disponible causó una excepción; por lo tanto, el siguiente código ahora prueba por navigator.msSaveBlobseparado. ¿Gracias? 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);

Notas:

Debe modificar su método WebApi para devolver los siguientes encabezados:

  • He usado el x-filenameencabezado para enviar el nombre del archivo. Este es un encabezado personalizado para su comodidad, sin embargo, puede extraer el nombre de archivo del content-dispositionencabezado utilizando expresiones regulares.

  • También debe configurar el content-typeencabezado mime para su respuesta, de modo que el navegador conozca el formato de datos.

Espero que esto ayude.

Scott
fuente
Hola @Scott, utilicé tu método y funciona, pero el navegador guarda el archivo como tipo html no pdf. Establezco el tipo de contenido en application / pdf y cuando reviso las herramientas de desarrollador en Chrome, el tipo de respuesta se establece en application / pdf pero cuando guardo el archivo se muestra como html, funciona, cuando lo abro el archivo es abierto como pdf pero en el navegador y tiene el icono predeterminado para mi navegador. ¿Sabes qué podría hacer mal?
Bartosz Bialecki
1
:-( lo siento. Me perdí de ver eso. Por cierto, esto está funcionando enormemente. Incluso mejor que filesaver.js
Jeeva Jsb
1
Cuando intento descargar un ejecutable de Microsoft a través de este método, obtengo un tamaño de blob que es aproximadamente 1,5 veces el tamaño real del archivo. El archivo que se descarga tiene el tamaño incorrecto del blob. ¿Alguna idea de por qué esto podría estar sucediendo? Basado en mirar el violinista, el tamaño de la respuesta es correcto, pero convertir el contenido en un blob lo aumenta de alguna manera.
user3517454
1
Finalmente descubrí el problema ... había cambiado el código del servidor de una publicación para obtener, pero no había cambiado los parámetros para $ http.get. Por lo tanto, el tipo de respuesta nunca se estableció como arraybuffer, ya que se pasó como el tercer argumento y no el segundo.
user3517454
1
@RobertGoldwein Puede hacerlo, pero se supone que si está utilizando una aplicación angularjs desea que el usuario permanezca en la aplicación, donde se mantiene el estado y la capacidad de utilizar la funcionalidad después de que comience la descarga. Si navega directamente a la descarga, no hay garantía de que la aplicación permanezca activa, ya que el navegador puede no manejar la descarga de la manera que esperamos. Imagine si el servidor 500s o 404s la solicitud. El usuario ya no está en la aplicación Angular. Se sugiere la sugerencia más simple de abrir el enlace en una nueva ventana window.open.
Scott,
10

Descarga de C # WebApi PDF todo funciona con autenticación angular JS

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

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

Cualquiera de los dos lo hará

Angular JS Controller llamando al servicio

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

Y por último la página HTML

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

Esto se refactorizará solo compartiendo el código, ahora espero que ayude a alguien, ya que me tomó un tiempo hacer que esto funcione.

tfa
fuente
El código anterior funciona en todos los sistemas, excepto en ios, así que use estos pasos si necesita que esto funcione en ios Paso 1 verifique si ios stackoverflow.com/questions/9038625/detect-if-device-is-ios Paso 2 (si ios) use esto stackoverflow.com/questions/24485077/…
tfa
6

Para mí, la API web era Rails y Angular del lado del cliente utilizado con Restangular y 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>

Controlador angular

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

Servicio angular

'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
fuente
¿Cómo usaste Filesaver.js con esto? ¿Cómo lo implementaste?
Alan Dunning
2

También tuvimos que desarrollar una solución que incluso funcionara con API que requieren autenticación (ver este artículo )

Usando AngularJS en pocas palabras, así es como lo hicimos:

Paso 1: crear una directiva dedicada

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

Paso 2: crea una plantilla

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

Paso 3: úsalo

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

Esto generará un botón azul. Al hacer clic, se descargará un PDF (Precaución: ¡el backend tiene que entregar el PDF en codificación Base64!) Y se colocará en el href. El botón se vuelve verde y cambia el texto a Guardar . El usuario puede hacer clic nuevamente y se le presentará un cuadro de diálogo de descarga estándar para el archivo my-awesome.pdf .

aix
fuente
1

Envíe su archivo como una cadena 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 el método attr no funciona en Firefox, también puede usar el método setAttribute de javaScript

PPB
fuente
var blob = nuevo Blob ([atob (response.payload)], {"datos": "adjunto / csv; charset = utf-8;"}); saveAs (blob, 'nombre de archivo');
PPB
Gracias PPB, su solución funcionó para mí, excepto para el atob. Eso no fue requerido para mí.
Larry Flewwelling
0

Puede implementar una función showfile que tome parámetros de los datos devueltos por WEBApi y un nombre de archivo para el archivo que está intentando descargar. Lo que hice fue crear un servicio de navegador separado que identifica el navegador del usuario y luego maneja la representación del archivo en función del navegador. Por ejemplo, si el navegador de destino es Chrome en un ipad, debe usar el objeto 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
fuente
1
Gracias Scott por atrapar esos artículos. Refactoré y agregué una explicación.
Erkin Djindjiev
0

He pasado por una variedad de soluciones y esto es lo que descubrí que funcionó muy bien para mí.

En mi caso, necesitaba enviar una solicitud de publicación con algunas credenciales. Una pequeña sobrecarga fue agregar jquery dentro del script. Pero valió 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();

    }
OneGhana
fuente
-1

En su componente, es decir, código js angular:

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