Pobierz plik z metody interfejsu API sieci Web ASP.NET przy użyciu AngularJS

132

W moim projekcie Angular JS mam <a>tag kotwicy, który po kliknięciu wysyła GETżądanie HTTP do metody WebAPI, która zwraca plik.

Teraz chcę, aby plik został pobrany do użytkownika, gdy żądanie zakończy się pomyślnie. Jak mogę to zrobić?

Tag kotwicy:

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

Moja metoda 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;
}
kvothe
źródło
1
Jaki byłby typ pliku? tylko obraz?
Rashmin Javiya
@RashminJaviya Może to być .jpg, .doc, .xlsx, .docx, .txt lub .pdf.
kvothe
Którego frameworka .Net używasz?
Rashmin Javiya
@RashminJaviya .net 4.5
kvothe
1
@Kurkula powinieneś użyć pliku System.IO.File not from controller
Javysk

Odpowiedzi:

242

Obsługa pobierania plików binarnych przy użyciu Ajax nie jest świetna, jest nadal w fazie rozwoju jako robocze wersje robocze .

Prosta metoda pobierania:

Możesz poprosić przeglądarkę o pobranie żądanego pliku po prostu za pomocą poniższego kodu, a jest to obsługiwane we wszystkich przeglądarkach i oczywiście spowoduje to samo wywołanie żądania WebApi.

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

Metoda pobierania plików binarnych Ajax:

Korzystanie z AJAX do pobierania pliku binarnego można wykonać w niektórych przeglądarkach, a poniżej znajduje się implementacja, która będzie działać w najnowszych wersjach Chrome, Internet Explorer, FireFox i Safari.

Używa typu arraybufferodpowiedzi, który jest następnie konwertowany na JavaScript blob, który jest następnie przedstawiany w celu zapisania przy użyciu saveBlobmetody - chociaż jest to obecnie tylko w przeglądarce Internet Explorer - lub zamieniany na adres URL danych typu blob, który jest otwierany przez przeglądarkę, wyzwalając okno dialogowe pobierania, jeśli typ MIME jest obsługiwany do przeglądania w przeglądarce.

Obsługa przeglądarki Internet Explorer 11 (stała)

Uwaga: Internet Explorer 11 nie lubił używać tej msSaveBlobfunkcji, jeśli była aliasowana - być może była to funkcja bezpieczeństwa, ale bardziej prawdopodobne jest luka, więc użycie var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob ... etc.do określenia dostępnej saveBlobobsługi spowodowało wyjątek; stąd dlaczego poniższy kod testuje teraz navigator.msSaveBloboddzielnie. Dzięki? 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;
    });
};

Stosowanie:

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

Uwagi:

Należy zmodyfikować metodę WebApi, aby zwracała następujące nagłówki:

  • Użyłem x-filenamenagłówka, aby wysłać nazwę pliku. Jest to niestandardowy nagłówek dla wygody, możesz jednak wyodrębnić nazwę pliku z content-dispositionnagłówka za pomocą wyrażeń regularnych.

  • Powinieneś także ustawić content-typenagłówek MIME dla swojej odpowiedzi, aby przeglądarka znała format danych.

Mam nadzieję, że to pomoże.

Scott
źródło
Cześć @Scott Użyłem twojej metody i działa, ale przeglądarka zapisuje plik jako typ html, a nie pdf. Ustawiam typ zawartości na application / pdf, a kiedy sprawdzam narzędzia programistyczne w chrome, typ odpowiedzi jest ustawiony na application / pdf, ale kiedy zapisuję plik, jest wyświetlany jako html, działa, kiedy go otwieram, plik jest otwarty jako pdf, ale w przeglądarce i ma domyślną ikonę dla mojej przeglądarki. Czy wiesz, co mogłem zrobić źle?
Bartosz Białecki
1
:-( przepraszam. Tęskniłem za tym. Przy okazji, to działa świetnie. Nawet lepiej niż filesaver.js
Jeeva Jsb
1
Kiedy próbuję pobrać plik wykonywalny firmy Microsoft za pomocą tej metody, otrzymuję rozmiar obiektu blob, który jest około 1,5 razy większy od rzeczywistego rozmiaru pliku. Pobrany plik ma nieprawidłowy rozmiar obiektu BLOB. Jakieś przemyślenia, dlaczego tak się dzieje? Opierając się na spojrzeniu na skrzypka, rozmiar odpowiedzi jest poprawny, ale konwersja zawartości na obiekt blob w jakiś sposób ją zwiększa.
user3517454
1
W końcu rozwiązałem problem ... Zmieniłem kod serwera z postu na pobranie, ale nie zmieniłem parametrów dla $ http.get. Więc typ odpowiedzi nigdy nie był ustawiany jako bufor tablicy, ponieważ był przekazywany jako trzeci argument, a nie jako drugi.
user3517454
1
@RobertGoldwein Możesz to zrobić, ale założenie jest takie, że jeśli korzystasz z aplikacji angularjs, chcesz, aby użytkownik pozostał w aplikacji, gdzie jest zachowany stan i możliwość korzystania z funkcjonalności po rozpoczęciu pobierania. Jeśli przejdziesz bezpośrednio do pobierania, nie ma gwarancji, że aplikacja pozostanie aktywna, ponieważ przeglądarka może nie obsłużyć pobierania w oczekiwany sposób. Wyobraź sobie, że serwer 500s lub 404s żądanie. Użytkownik jest teraz poza aplikacją Angular. Sugerowana jest najprostsza sugestia otwarcia linku w nowym oknie za pomocą window.open.
Scott
10

C # WebApi PDF do pobrania, wszystko działa z uwierzytelnianiem Angular JS

Kontroler Web Api

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

Usługa 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;
        }

Każdy to zrobi

Angular JS Controller wywołujący usługę

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

I ostatnia strona HTML

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

Zostanie to zmienione po prostu udostępniając kod, teraz mam nadzieję, że pomoże to komuś, ponieważ zajęło mi to trochę czasu, zanim to zadziałało.

tfa
źródło
Powyższy kod działa na wszystkich systemach z wyjątkiem ios, więc jeśli potrzebujesz tego do pracy na iOS Krok 1, sprawdź, czy ios stackoverflow.com/questions/9038625/detect-if-device-is-ios Krok 2 (jeśli ios) użyj tego stackoverflow.com/questions/24485077/…
tfa
6

Dla mnie interfejsem API sieci Web był Rails i Angular po stronie klienta, używane z Restangular i FileSaver.js

Internetowy interfejs 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>

Kontroler kątowy

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

Usługa 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
źródło
W jaki sposób korzystałeś z Filesaver.js w tym? Jak to wdrożyliście?
Alan Dunning
2

Musieliśmy również opracować rozwiązanie, które działałoby nawet z interfejsami API wymagającymi uwierzytelniania (zobacz ten artykuł )

Korzystanie z AngularJS w pigułce, jak to zrobiliśmy:

Krok 1: Utwórz dedykowaną dyrektywę

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

Krok 2: Utwórz szablon

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

Krok 3: użyj go

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

Spowoduje to renderowanie niebieskiego przycisku. Po kliknięciu plik PDF zostanie pobrany (Uwaga: zaplecze musi dostarczyć plik PDF w kodowaniu Base64!) I umieszczony w href. Przycisk zmienia kolor na zielony i przełącza tekst na Zapisz . Użytkownik może kliknąć ponownie, a zostanie wyświetlone standardowe okno dialogowe pobierania pliku my-awesome.pdf .

aix
źródło
1

Wyślij plik jako ciąg 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();

Jeśli metoda attr nie działa w przeglądarce Firefox Możesz również użyć metody setAttribute w javaScript

PPB
źródło
var blob = new Blob ([atob (response.payload)], {"data": "załącznik / csv; charset = utf-8;"}); saveAs (blob, 'filename');
PPB
Dziękuję PPB, twoje rozwiązanie zadziałało dla mnie z wyjątkiem atob. Nie było to dla mnie wymagane.
Larry Flewwelling
0

Możesz zaimplementować funkcję showfile, która pobiera parametry danych zwróconych z WEBApi oraz nazwę pliku, który próbujesz pobrać. Stworzyłem osobną usługę przeglądarki, która identyfikuje przeglądarkę użytkownika, a następnie obsługuje renderowanie pliku na podstawie przeglądarki. Na przykład, jeśli docelową przeglądarką jest chrome na iPadzie, musisz użyć obiektu 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);
}
Erkin Djindjiev
źródło
1
Dziękuję Scott za złapanie tych przedmiotów. Zrefaktoryzowałem i dodałem wyjaśnienie.
Erkin Djindjiev
0

Przeszedłem przez szereg rozwiązań i stwierdziłem, że świetnie się dla mnie sprawdzają.

W moim przypadku musiałem wysłać prośbę o pocztę z pewnymi poświadczeniami. Niewielkim narzutem było dodanie jquery do skryptu. Ale było tego warte.

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
źródło
-1

W twoim komponencie, tj. Kątowy kod js:

function getthefile (){
window.location.href='http://localhost:1036/CourseRegConfirm/getfile';
};
Shivani Jadhav
źródło