AngularJS kullanarak ASP.NET Web API yönteminden dosya indirin


132

Angular JS projemde <a>, tıklandığında GETbir dosya döndüren bir WebAPI yöntemine HTTP isteğinde bulunan bir bağlantı etiketim var.

Şimdi, istek başarılı olduğunda dosyanın kullanıcıya indirilmesini istiyorum. Bunu nasıl yaparım?

Bağlantı etiketi:

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

WebAPI yöntemim:

[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
Dosya türü ne olabilir? sadece görüntü?
Rashmin Javiya

@RashminJaviya .jpg, .doc, .xlsx, .docx, .txt veya .pdf olabilir.
whereDragonsDwell

Hangi .Net çerçevesini kullanıyorsunuz?
Rashmin Javiya

@RashminJaviya .net 4.5
whereDragonsDwell

1
@Kurkula, File of System.IO. file'ı kullanmalısınız. Controller'dan değil
Javysk

Yanıtlar:


242

Ajax kullanarak ikili dosyaları indirmek için destek harika değil, çalışma taslakları olarak hala geliştirme aşamasındadır .

Basit indirme yöntemi:

Tarayıcının aşağıdaki kodu kullanarak istenen dosyayı indirmesini sağlayabilirsiniz ve bu tüm tarayıcılarda desteklenir ve açıkça WebApi isteğini aynı şekilde tetikleyecektir.

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

Ajax ikili indirme yöntemi:

İkili dosyayı indirmek için ajax kullanmak bazı tarayıcılarda yapılabilir ve aşağıda Chrome, Internet Explorer, FireFox ve Safari'nin en son sürümlerinde çalışacak bir uygulama yer almaktadır.

Daha arraybuffersonra bir JavaScript'e dönüştürülen bir yanıt türü kullanır ve bu daha sonra yöntemi blobkullanarak kaydetmek için sunulur saveBlob- bu yalnızca şu anda Internet Explorer'da mevcut olsa da - veya tarayıcı tarafından açılan bir blob veri URL'sine dönüştürülerek tetiklenir. mime türü tarayıcıda görüntüleme için destekleniyorsa indirme iletişim kutusu.

Internet Explorer 11 Desteği (Sabit)

Not: Internet Explorer 11, msSaveBlobtakma ad verilmişse bu işlevi kullanmayı sevmezdi - belki bir güvenlik özelliği, ancak daha büyük olasılıkla bir kusur, Bu nedenle var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob ... etc., mevcut saveBlobdesteği belirlemek için kullanmak bir istisnaya neden oldu; bu nedenle aşağıdaki kod şimdi navigator.msSaveBlobayrı ayrı test edilmektedir . Teşekkürler? 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;
    });
};

Kullanımı:

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

Notlar:

Aşağıdaki başlıkları döndürmek için WebApi yönteminizi değiştirmelisiniz:

  • x-filenameDosya adını göndermek için başlığı kullandım . Bu, kolaylık sağlamak için özel bir başlıktır, ancak dosya content-dispositionadını normal ifadeler kullanarak başlıktan çıkarabilirsiniz .

  • content-typeYanıtınız için mime başlığını da ayarlamalısınız , böylece tarayıcı veri formatını bilir.

Umarım bu yardımcı olur.


Merhaba @Scott Yönteminizi kullandım ve işe yarıyor ancak tarayıcı dosyayı pdf değil html türü olarak kaydediyor. İçerik türünü application / pdf olarak ayarladım ve chrome'da geliştirici araçlarını kontrol ettiğimde yanıt türü application / pdf olarak ayarlandı ancak dosyayı kaydettiğimde html olarak gösteriliyor, çalışıyor, açtığımda dosya pdf olarak açıldı, ancak tarayıcıda ve tarayıcım için varsayılan simge var. Neyi yanlış yapabilirim biliyor musun?
Bartosz Bialecki

1
:-( üzgünüm. Bunu görmeyi kaçırdım. BTW bu harika çalışıyor. filesaver.js'den bile daha iyi
Jeeva Jsb

1
Bu yöntemle bir Microsoft yürütülebilir dosyasını indirmeye çalıştığımda, gerçek dosya boyutunun yaklaşık 1,5 katı olan bir blob boyutunu geri alıyorum. İndirilen dosya blob boyutunun yanlış. Bunun neden olabileceğine dair herhangi bir fikriniz var mı? Kemancıya bakıldığında, yanıtın boyutu doğrudur, ancak içeriği bir blobe dönüştürmek onu bir şekilde artırmaktadır.
user3517454

1
Sonunda sorunu anladım ... Sunucu kodunu bir gönderiden almak için değiştirdim, ancak $ http.get için parametreleri değiştirmedim. Dolayısıyla yanıt türü, ikinci değil üçüncü argüman olarak iletildiğinden, hiçbir zaman dizi tampon olarak ayarlanmadı.
user3517454

1
@RobertGoldwein Bunu yapabilirsiniz, ancak varsayım, bir angularjs uygulaması kullanıyorsanız, kullanıcının, indirme başladıktan sonra işlevselliği kullanma durumunun ve yeteneğinin korunduğu uygulamada kalmasını istemenizdir. Doğrudan indirme işlemine giderseniz, tarayıcı indirmeyi beklediğimiz gibi yapamayabileceğinden, uygulamanın etkin kalacağının garantisi yoktur. Sunucunun isteği 500'lü mü yoksa 404 mü olduğunu düşünün. Kullanıcı artık Angular uygulamasının dışındadır. Bağlantıyı yeni bir pencerede açmanın en basit önerisi window.openile önerilir.
Scott

10

C # WebApi PDF indirme, tümü Angular JS Authentication ile çalışıyor

Web Api Denetleyicisi

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

Açısal JS Hizmeti

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

İkisinden biri yapacak

Hizmeti çağıran açısal JS Denetleyicisi

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

Ve son olarak HTML sayfası

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

Bu, sadece kodu paylaşarak yeniden düzenlenecek, şimdi bunu çalıştırmam biraz zaman aldığından birine yardımcı olacağını umuyorum.


Yukarıdaki kod, ios dışındaki tüm sistemlerde çalışır, bu nedenle ios Adım 1'de çalışmak için buna ihtiyacınız varsa bu adımları kullanın, ios stackoverflow.com/questions/9038625/detect-if-device-is-ios Adım 2'nin (ios ise) bunu kullanıp kullanmadığını kontrol edin stackoverflow.com/questions/24485077/…
tfa


6

Benim için Web API Rails ve istemci tarafı Angular, Restangular ve FileSaver.js ile kullanıldı

Web 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>

Açısal kontrolör

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

Açısal Hizmet

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

Filesaver.js'yi bununla nasıl kullandınız? Nasıl uyguladınız?
Alan Dunning

2

Ayrıca, kimlik doğrulama gerektiren API'lerle bile çalışacak bir çözüm geliştirmemiz gerekiyordu ( bu makaleye bakın )

Kısaca AngularJS kullanarak bunu şu şekilde yaptık:

1. Adım: Özel bir yönerge oluşturun

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

2. Adım: Bir şablon oluşturun

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

3. Adım: Kullanın

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

Bu mavi bir düğme oluşturacaktır. Tıklandığında, bir PDF indirilecek (Dikkat: arka uç PDF'yi Base64 kodlamasında teslim etmelidir!) Ve href içine konulacaktır. Düğme yeşile döner ve metni Kaydet olarak değiştirir . Kullanıcı tekrar tıklayabilir ve my-awesome.pdf dosyası için standart bir dosya indirme iletişim kutusu sunulur .


1

Dosyanızı bir base64 dizesi olarak gönderin.

 var element = angular.element('<a/>');
                         element.attr({
                             href: 'data:attachment/csv;charset=utf-8,' + encodeURI(atob(response.payload)),
                             target: '_blank',
                             download: fname
                         })[0].click();

Firefox'ta attr yöntemi çalışmıyorsa javaScript setAttribute yöntemini de kullanabilirsiniz


var blob = new Blob ([atob (response.payload)], {"veri": "attachment / csv; charset = utf-8;"}); saveAs (blob, 'dosya adı');
PPB

Teşekkürler PPB, çözümünüz atob dışında benim için çalıştı. Bu benim için gerekli değildi.
Larry Flewwelling

0

WEBApi'den döndürülen verilerin parametrelerini ve indirmeye çalıştığınız dosyanın bir dosya adını alan bir showfile işlevi uygulayabilirsiniz. Yaptığım şey, kullanıcının tarayıcısını tanımlayan ve ardından tarayıcıya dayalı olarak dosyanın işlenmesini gerçekleştiren ayrı bir tarayıcı hizmeti oluşturmaktı. Örneğin, hedef tarayıcı bir ipad'deki chrome ise, javascripts FileReader nesnesini kullanmanız gerekir.

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
Bu eşyaları yakaladığınız için teşekkürler Scott. Yeniden düzenledim ve bir açıklama ekledim.
Erkin Djindjiev

0

Bir dizi çözümden geçtim ve bunun benim için harika olduğunu buldum.

Benim durumumda bazı kimlik bilgileriyle bir gönderi isteği göndermem gerekiyordu. Küçük ek yük betiğin içine jquery eklemekti. Ama buna değdi.

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

Bileşeninizde, yani açısal js kodunda:

function getthefile (){
window.location.href='http://localhost:1036/CourseRegConfirm/getfile';
};
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.