Baixar arquivo de um método de API da Web do ASP.NET usando AngularJS


132

No meu projeto Angular JS, tenho uma <a>marca de âncora, que quando clicada faz uma GETsolicitação HTTP para um método WebAPI que retorna um arquivo.

Agora, desejo que o arquivo seja baixado para o usuário assim que a solicitação for bem-sucedida. Como faço isso?

A tag de âncora:

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

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

1
Qual seria o tipo de arquivo? apenas imagem?
Rashmin Javiya

@RashminJaviya Pode ser .jpg, .doc, .xlsx, .docx, .txt ou .pdf.
whereDragonsDwell

Qual estrutura .Net você está usando?
Rashmin Javiya

@RashminJaviya .net 4.5
whereDragonsDwell

1
@Kurkula você deve usar arquivo de System.IO.File não do controlador
Javysk

Respostas:


242

O suporte para o download de arquivos binários no uso do ajax não é ótimo, ainda está em desenvolvimento como rascunhos de trabalho .

Método simples de download:

Você pode fazer com que o navegador baixe o arquivo solicitado simplesmente usando o código abaixo, e isso é suportado em todos os navegadores e, obviamente, acionará a solicitação WebApi da mesma forma.

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

Método de download binário do Ajax:

O uso do ajax para baixar o arquivo binário pode ser feito em alguns navegadores, e abaixo está uma implementação que funcionará nos mais recentes sabores do Chrome, Internet Explorer, FireFox e Safari.

Ele usa um arraybuffertipo de resposta, que é convertido em JavaScript blob, que é apresentado para salvar usando o saveBlobmétodo - embora este esteja presente apenas no Internet Explorer - ou transformado em uma URL de dados de blob que é aberta pelo navegador, acionando a caixa de diálogo de download se o tipo MIME for suportado para exibição no navegador.

Suporte ao Internet Explorer 11 (fixo)

Nota: O Internet Explorer 11 não gostou de usar a msSaveBlobfunção se ela tivesse um alias - talvez um recurso de segurança, mas provavelmente uma falha. Portanto, o uso var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob ... etc.para determinar o saveBlobsuporte disponível causou uma exceção; por isso, o código abaixo agora é testado navigator.msSaveBlobseparadamente. Obrigado? 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:

Você deve modificar seu método WebApi para retornar os seguintes cabeçalhos:

  • Eu usei o x-filenamecabeçalho para enviar o nome do arquivo. Este é um cabeçalho personalizado por conveniência, mas você pode extrair o nome do arquivo do content-dispositioncabeçalho usando expressões regulares.

  • Você também deve definir o content-typecabeçalho MIME para sua resposta, para que o navegador conheça o formato dos dados.

Eu espero que isso ajude.


Oi @ Scott Eu usei seu método e ele funciona, mas o navegador salva o arquivo como tipo html, não pdf. Defino o tipo de conteúdo como application / pdf e, quando faço o check-in das ferramentas do desenvolvedor no chrome, o tipo de resposta é definido como application / pdf, mas quando salvo o arquivo, ele é mostrado como html, ele funciona. Quando eu o abro, o arquivo é aberto como pdf, mas no navegador e tem o ícone padrão para o meu navegador. Você sabe o que eu poderia fazer de errado?
Bartosz Bialecki

1
. :-( desculpe eu perdi de ver que BTW isso está funcionando muito Ainda melhor do que filesaver.js..
Jeeva Jsb

1
Quando tento baixar um executável da Microsoft por esse método, recebo um tamanho de blob aproximadamente 1,5 vezes o tamanho real do arquivo. O arquivo baixado tem o tamanho incorreto do blob. Alguma idéia de por que isso pode estar acontecendo? Com base na análise do violinista, o tamanho da resposta está correto, mas a conversão do conteúdo em um blob está aumentando de alguma forma.
precisa saber é o seguinte

1
Finalmente descobri o problema ... Eu havia alterado o código do servidor de uma postagem para obter, mas não havia alterado os parâmetros para $ http.get. Portanto, o tipo de resposta nunca foi definido como buffer de matriz, pois estava sendo passado como o terceiro argumento e não o segundo.
precisa saber é o seguinte

1
@RobertGoldwein Você pode fazer isso, mas a suposição é que, se você estiver usando um aplicativo angularjs, deseja que o usuário permaneça no aplicativo, onde o estado e a capacidade de usar a funcionalidade após o início do download são mantidos. Se você navegar diretamente para o download, não há garantia de que o aplicativo permanecerá ativo, pois o navegador pode não lidar com o download da maneira esperada. Imagine se o servidor 500s ou 404s a solicitação. O usuário agora está fora do aplicativo Angular. É sugerida a sugestão mais simples de abrir o link em uma nova janela window.open.
26516 Scott

10

Download em PDF do C # WebApi, todos trabalhando com autenticação Angular JS

Controlador da API da 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;
    }

Serviço JS angular

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

Qualquer um fará

Controlador JS angular que chama o serviço

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 por último a página HTML

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

Isso será refatorado apenas compartilhando o código, agora espero que ajude alguém, pois demorei um pouco para que isso funcionasse.


O código acima funciona em todos os sistemas, exceto no iOS, portanto, siga estas etapas se você precisar dele para trabalhar no iOS Etapa 1, verifique se o ios stackoverflow.com/questions/9038625/detect-if-device-is-ios Etapa 2 (se o ios) usa este stackoverflow.com/questions/24485077/…
tfa 27/11


6

Para mim, a API da Web era Rails e Angular do lado do cliente usado com Restangular e FileSaver.js

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

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

Como você usou o Filesaver.js com isso? Como você o implementou?
Alan Dunning

2

Também tivemos que desenvolver uma solução que funcionasse mesmo com APIs que exigem autenticação (consulte este artigo )

Usando AngularJS em poucas palavras, aqui está como fizemos:

Etapa 1: criar uma diretiva 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);
            });
        };
    }] 
});

Etapa 2: criar um modelo

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

Etapa 3: use-o

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

Isso renderizará um botão azul. Quando clicado, um PDF será baixado (Cuidado: o back-end deve entregar o PDF na codificação Base64!) E colocado no href. O botão fica verde e muda o texto para Salvar . O usuário pode clicar novamente e será apresentada uma caixa de diálogo de arquivo de download padrão para o arquivo my-awesome.pdf .


1

Envie seu arquivo como uma string 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 o método attr não funcionar no Firefox Você também pode usar o método javaScript setAttribute


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

Obrigado PPB, sua solução funcionou para mim, exceto para o atob. Isso não foi necessário para mim.
Larry Flewwelling

0

Você pode implementar uma função showfile que recebe parâmetros dos dados retornados do WEBApi e um nome de arquivo para o arquivo que você está tentando baixar. O que fiz foi criar um serviço de navegador separado para identificar o navegador do usuário e, em seguida, manipular a renderização do arquivo com base no navegador. Por exemplo, se o navegador de destino é chrome em um ipad, é necessário usar o objeto 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
Obrigado Scott por pegar esses itens. Refatorei e adicionei uma explicação.
Erkin Djindjiev

0

Passei por uma série de soluções e foi isso que achei que funcionou muito bem para mim.

No meu caso, eu precisava enviar uma solicitação de postagem com algumas credenciais. Uma pequena sobrecarga foi adicionar jquery dentro do script. Mas valeu a 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

No seu componente, ou seja, código js angular:

function getthefile (){
window.location.href='http://localhost:1036/CourseRegConfirm/getfile';
};
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.