Unduh file dari metode ASP.NET Web API menggunakan AngularJS

132

Dalam proyek JS Angular saya, saya memiliki <a>jangkar tag, yang ketika diklik membuat GETpermintaan HTTP ke metode WebAPI yang mengembalikan file.

Sekarang, saya ingin file diunduh ke pengguna setelah permintaan berhasil. Bagaimana aku melakukan itu?

Tag jangkar:

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

Metode WebAPI saya:

[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;
}
dimanaDragonsDwell
sumber
1
Apa yang akan menjadi tipe file? hanya gambar?
Rashmin Javiya
@RashminJaviya Bisa berupa .jpg, .doc, .xlsx, .docx, .txt atau .pdf.
whereDragonsDwell
Kerangka kerja .Net mana yang Anda gunakan?
Rashmin Javiya
@RashminJaviya .net 4.5
whereDragonsDwell
1
@Kurkula Anda harus menggunakan File System.IO.File bukan dari controller
Javysk

Jawaban:

242

Dukungan untuk mengunduh file biner dalam menggunakan ajax tidak bagus, masih banyak yang sedang dikembangkan sebagai draft yang masih berfungsi .

Metode pengunduhan sederhana:

Anda dapat meminta browser mengunduh file yang diminta hanya dengan menggunakan kode di bawah ini, dan ini didukung di semua browser, dan jelas akan memicu permintaan WebApi sama saja.

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

Metode pengunduhan biner Ajax:

Menggunakan ajax untuk mengunduh file biner dapat dilakukan di beberapa browser dan di bawah ini merupakan implementasi yang akan bekerja dalam rasa terbaru Chrome, Internet Explorer, FireFox dan Safari.

Ini menggunakan arraybufferjenis respons, yang kemudian dikonversi menjadi JavaScript blob, yang kemudian disajikan untuk menyimpan menggunakan saveBlobmetode - meskipun ini hanya saat ini ada di Internet Explorer - atau berubah menjadi gumpalan data URL yang dibuka oleh browser, memicu dialog unduhan jika tipe mime didukung untuk dilihat di browser.

Dukungan Internet Explorer 11 (Tetap)

Catatan: Internet Explorer 11 tidak suka menggunakan msSaveBlobfungsi jika telah alias - mungkin fitur keamanan, tetapi lebih cenderung cacat, Jadi menggunakan var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob ... etc.untuk menentukan saveBlobdukungan yang tersedia menyebabkan pengecualian; karenanya mengapa kode di bawah sekarang menguji secara navigator.msSaveBlobterpisah. Terima kasih? 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;
    });
};

Pemakaian:

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

Catatan:

Anda harus memodifikasi metode WebApi Anda untuk mengembalikan header berikut:

  • Saya telah menggunakan x-filenameheader untuk mengirim nama file. Ini adalah tajuk khusus untuk kenyamanan, namun Anda dapat mengekstrak nama file dari content-dispositiontajuk menggunakan ekspresi reguler.

  • Anda juga harus mengatur content-typeheader mime untuk respons Anda, sehingga browser mengetahui format data.

Saya harap ini membantu.

Scott
sumber
Hai @ Esc Saya menggunakan metode Anda dan itu berfungsi tetapi browser menyimpan file sebagai tipe html bukan pdf. Saya mengatur tipe-konten ke application / pdf dan ketika saya memeriksa alat pengembang di chrome jenis respons diatur ke application / pdf tetapi ketika saya menyimpan file itu ditampilkan sebagai html, itu berfungsi, ketika saya membukanya file tersebut dibuka sebagai pdf tetapi di browser dan memiliki ikon default untuk browser saya. Apakah Anda tahu apa yang bisa saya lakukan salah?
Bartosz Bialecki
1
:-( maaf. Aku rindu melihat itu. BTW ini bekerja sangat baik. Bahkan lebih baik daripada filesaver.js
Jeeva Jsb
1
Ketika saya mencoba mengunduh Microsoft executable melalui metode ini, saya mendapatkan kembali ukuran gumpalan yang kira-kira 1,5 kali dari ukuran file yang sebenarnya. File yang diunduh memiliki ukuran gumpalan yang salah. Adakah pemikiran mengapa ini bisa terjadi? Berdasarkan melihat fiddler, ukuran responsnya benar, tetapi mengubah konten menjadi gumpalan entah bagaimana meningkatkannya.
user3517454
1
Akhirnya menemukan masalah ... Saya telah mengubah kode server dari posting untuk mendapatkan, tapi saya belum mengubah parameter untuk $ http. Get. Jadi tipe respon tidak pernah ditetapkan sebagai arraybuffer karena ini dilewatkan sebagai argumen ketiga dan bukan yang kedua.
user3517454
1
@RobertGoldwein Anda dapat melakukannya, tetapi asumsinya adalah jika Anda menggunakan aplikasi angular, Anda ingin pengguna tetap berada di dalam aplikasi, di mana keadaan dan kemampuan untuk menggunakan fungsi setelah unduhan dimulai dipertahankan. Jika Anda menavigasi langsung ke unduhan, tidak ada jaminan aplikasi akan tetap aktif, karena peramban mungkin tidak menangani unduhan seperti yang kami harapkan. Bayangkan jika server 500 atau 404 permintaan. Pengguna sekarang keluar dari aplikasi Angular. Saran paling sederhana untuk membuka tautan di jendela baru menggunakan window.opendisarankan.
Scott
10

C # WebApi unduh PDF semua bekerja dengan Otentikasi Angular JS

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

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

Salah satu akan melakukannya

Kontroler JS Angular memanggil layanan

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

Dan terakhir halaman HTML

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

Ini akan di-refactored hanya dengan membagikan kode sekarang, harap ini membantu seseorang karena perlu waktu bagi saya untuk membuatnya berfungsi.

tfa
sumber
Kode di atas bekerja pada semua sistem kecuali ios jadi gunakan langkah-langkah ini jika Anda memerlukan ini untuk bekerja pada ios Langkah 1 periksa apakah ios stackoverflow.com/questions/9038625/detect-if-device-is-ios Langkah 2 (jika ios) gunakan ini stackoverflow.com/questions/24485077/…
tfa
6

Bagi saya API Web adalah Rails dan sisi klien Angular digunakan dengan Restangular dan 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>

Pengontrol sudut

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

Layanan 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');
    })
  }
});
Hebat
sumber
Bagaimana Anda menggunakan Filesaver.js dengan ini? Bagaimana Anda menerapkannya?
Alan Dunning
2

Kami juga harus mengembangkan solusi yang bahkan dapat bekerja dengan API yang memerlukan otentikasi (lihat artikel ini )

Singkatnya, menggunakan AngularJS di sini adalah bagaimana kami melakukannya:

Langkah 1: Buat arahan khusus

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

Langkah 2: Buat templat

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

Langkah 3: Gunakan

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

Ini akan membuat tombol biru. Ketika diklik, PDF akan diunduh (Perhatian: backend harus mengirimkan PDF dalam pengkodean Base64!) Dan dimasukkan ke dalam href. Tombol berubah menjadi hijau dan mengalihkan teks ke Simpan . Pengguna dapat mengklik lagi dan akan disajikan dialog file unduhan standar untuk file my-awesome.pdf .

aix
sumber
1

Kirim file Anda sebagai 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();

Jika metode attr tidak berfungsi di Firefox Anda juga dapat menggunakan metode setScript javaAtribut javaScript

PPB
sumber
var blob = blob baru ([atob (response.payload)], {"data": "attachment / csv; charset = utf-8;"}); saveAs (gumpalan, 'nama file');
PPB
Terima kasih PPB, solusi Anda berhasil untuk saya kecuali untuk atob. Itu tidak diperlukan bagi saya.
Larry Flewwelling
0

Anda bisa mengimplementasikan fungsi showfile yang mengambil parameter data yang dikembalikan dari WEBApi, dan nama file untuk file yang Anda coba unduh. Apa yang saya lakukan adalah membuat layanan browser yang terpisah mengidentifikasi browser pengguna dan kemudian menangani rendering file berdasarkan browser. Misalnya jika browser target chrome pada ipad, Anda harus menggunakan objek 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
sumber
1
Terima kasih Scott untuk menangkap barang-barang itu. Saya telah refactored dan menambahkan penjelasan.
Erkin Djindjiev
0

Saya telah melalui berbagai solusi dan inilah yang saya temukan telah bekerja sangat baik untuk saya.

Dalam kasus saya, saya perlu mengirim permintaan posting dengan beberapa kredensial. Overhead kecil adalah untuk menambahkan jquery di dalam skrip. Tapi itu sepadan.

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
sumber
-1

Dalam komponen Anda yaitu kode js sudut:

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