ดาวน์โหลดไฟล์จากวิธี ASP.NET Web API โดยใช้ AngularJS


132

ในโครงการ Angular JS ของฉันฉันมี<a>แท็กจุดยึดซึ่งเมื่อคลิกจะGETส่งคำขอHTTP ไปยังเมธอด WebAPI ที่ส่งคืนไฟล์

ตอนนี้ฉันต้องการให้ดาวน์โหลดไฟล์ไปยังผู้ใช้เมื่อคำขอสำเร็จ ฉันจะทำอย่างไร

แท็กจุดยึด:

<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 ของฉัน:

[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
ประเภทไฟล์คืออะไร? ภาพเท่านั้น?
Rashmin Javiya

@RashminJaviya อาจเป็น. jpg, .doc, .xlsx, .docx, .txt หรือ. pdf
kvothe

NET Framework ใดที่คุณใช้อยู่
Rashmin Javiya

@RashminJaviya .net 4.5
kvothe

1
@Kurkula คุณควรใช้ File of System.IO ไฟล์ที่ไม่ได้มาจากคอนโทรลเลอร์
Javysk

คำตอบ:


242

การสนับสนุนการดาวน์โหลดไฟล์ไบนารีในการใช้ ajax นั้นไม่ดีนัก แต่ยังอยู่ระหว่างการพัฒนาเป็นแบบร่างที่ใช้งานได้

วิธีดาวน์โหลดง่ายๆ:

คุณสามารถให้เบราว์เซอร์ดาวน์โหลดไฟล์ที่ร้องขอได้ง่ายๆโดยใช้รหัสด้านล่างและสิ่งนี้ได้รับการสนับสนุนในทุกเบราว์เซอร์และจะทำให้คำขอ WebApi เหมือนกันอย่างเห็นได้ชัด

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

วิธีการดาวน์โหลด Ajax ไบนารี:

การใช้ ajax เพื่อดาวน์โหลดไฟล์ไบนารีสามารถทำได้ในบางเบราว์เซอร์และด้านล่างนี้เป็นการใช้งานที่จะทำงานใน Chrome, Internet Explorer, FireFox และ Safari รุ่นล่าสุด

ใช้arraybufferประเภทการตอบกลับซึ่งจะถูกแปลงเป็น JavaScript blobซึ่งจะถูกนำเสนอให้บันทึกโดยใช้saveBlobวิธีการนี้แม้ว่าจะมีอยู่ใน Internet Explorer ในปัจจุบันเท่านั้นหรือเปลี่ยนเป็น URL ข้อมูลหยดซึ่งเบราว์เซอร์เปิดขึ้น กล่องโต้ตอบดาวน์โหลดหากประเภท mime ได้รับการสนับสนุนสำหรับการดูในเบราว์เซอร์

การสนับสนุน Internet Explorer 11 (คงที่)

หมายเหตุ: Internet Explorer 11 ไม่ชอบใช้msSaveBlobฟังก์ชันนี้หากมีการใช้นามแฝง - อาจเป็นคุณลักษณะด้านความปลอดภัย แต่มีแนวโน้มว่าจะเกิดข้อบกพร่องดังนั้นการใช้var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob ... etc.เพื่อพิจารณาการsaveBlobสนับสนุนที่มีอยู่ทำให้เกิดข้อยกเว้น ด้วยเหตุนี้โค้ดด้านล่างจึงทดสอบnavigator.msSaveBlobแยกกัน ขอบคุณ? ไมโครซอฟต์

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

การใช้งาน:

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

หมายเหตุ:

คุณควรแก้ไขวิธี WebApi ของคุณเพื่อส่งคืนส่วนหัวต่อไปนี้:

  • ฉันใช้x-filenameส่วนหัวเพื่อส่งชื่อไฟล์ นี่คือส่วนหัวที่กำหนดเองเพื่อความสะดวกอย่างไรก็ตามคุณสามารถแยกชื่อไฟล์จากcontent-dispositionส่วนหัวโดยใช้นิพจน์ทั่วไป

  • คุณควรตั้งค่าcontent-typeส่วนหัวละครใบ้สำหรับการตอบสนองของคุณด้วยเพื่อให้เบราว์เซอร์รู้รูปแบบข้อมูล

ฉันหวังว่านี่จะช่วยได้.


สวัสดี @Scott ฉันใช้วิธีการของคุณและได้ผล แต่เบราว์เซอร์บันทึกไฟล์เป็นประเภท html ไม่ใช่ pdf ฉันตั้งค่า content-type เป็น application / pdf และเมื่อฉันเช็คอินเครื่องมือสำหรับนักพัฒนาใน chrome ประเภทของการตอบกลับถูกตั้งค่าเป็น application / pdf แต่เมื่อฉันบันทึกไฟล์มันจะแสดงเป็น html มันใช้งานได้เมื่อฉันเปิดไฟล์ขึ้นมา เปิดเป็น pdf แต่อยู่ในเบราว์เซอร์และมีไอคอนเริ่มต้นสำหรับเบราว์เซอร์ของฉัน รู้ไหมว่าฉันทำอะไรผิด?
Bartosz Bialecki

1
:-( ขออภัยฉันพลาดที่จะเห็น BTW นี้ทำงานได้ดีเยี่ยมยิ่งกว่า filesaver.js
Jeeva Jsb

1
เมื่อฉันพยายามดาวน์โหลดไฟล์ปฏิบัติการของ Microsoft ด้วยวิธีนี้ฉันจะได้ขนาด Blob กลับคืนมาที่ประมาณ 1.5 เท่าของขนาดไฟล์จริง ไฟล์ที่ดาวน์โหลดมีขนาดของหยดไม่ถูกต้อง มีความคิดว่าเหตุใดจึงอาจเกิดขึ้น จากการดูมือเล่นซอขนาดของการตอบสนองนั้นถูกต้อง แต่การแปลงเนื้อหาเป็นหยดจะเพิ่มขึ้นอย่างใด
user3517454

1
ในที่สุดก็พบปัญหา ... ฉันเปลี่ยนรหัสเซิร์ฟเวอร์จากโพสต์เพื่อรับ แต่ฉันไม่ได้เปลี่ยนพารามิเตอร์สำหรับ $ http.get ดังนั้นจึงไม่เคยตั้งค่าประเภทการตอบกลับเป็น arraybuffer เนื่องจากถูกส่งผ่านเป็นอาร์กิวเมนต์ที่สามไม่ใช่ครั้งที่สอง
user3517454

1
@RobertGoldwein คุณสามารถทำได้ แต่ข้อสันนิษฐานก็คือหากคุณใช้แอปพลิเคชัน angularjs คุณต้องการให้ผู้ใช้ยังคงอยู่ในแอปพลิเคชันโดยที่สถานะและความสามารถในการใช้ฟังก์ชันหลังจากเริ่มการดาวน์โหลดจะยังคงอยู่ หากคุณไปที่การดาวน์โหลดโดยตรงไม่มีการรับประกันว่าแอปพลิเคชันจะยังคงใช้งานได้เนื่องจากเบราว์เซอร์อาจไม่จัดการการดาวน์โหลดตามที่เราคาดหวัง ลองนึกภาพว่าเซิร์ฟเวอร์ 500 หรือ 404 เป็นคำขอ ขณะนี้ผู้ใช้ออกจากแอป Angular แล้ว ข้อเสนอแนะที่ง่ายที่สุดในการเปิดลิงค์ในหน้าต่างใหม่โดยใช้window.openเป็นข้อเสนอแนะ
Scott

10

ดาวน์โหลด C # WebApi PDF ทั้งหมดที่ทำงานร่วมกับ Angular JS Authentication

ตัวควบคุม 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;
    }

บริการ 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;
        }

อย่างใดอย่างหนึ่งจะทำ

Angular JS Controller เรียกใช้บริการ

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

และสุดท้ายหน้า HTML

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

สิ่งนี้จะได้รับการปรับโครงสร้างใหม่เพียงแค่แบ่งปันรหัสตอนนี้หวังว่าจะช่วยใครบางคนได้เนื่องจากฉันใช้เวลาสักพักกว่าจะทำงาน


โค้ดด้านบนใช้งานได้กับทุกระบบยกเว้น ios ดังนั้นให้ใช้ขั้นตอนเหล่านี้หากคุณต้องการให้สิ่งนี้ทำงานบน iOS ขั้นตอนที่ 1 ตรวจสอบว่า ios stackoverflow.com/questions/9038625/detect-if-device-is-ios ขั้นตอนที่ 2 (ถ้า ios) ใช้สิ่งนี้stackoverflow.com/questions/24485077/…
tfa


6

สำหรับฉัน Web API คือ Rails และฝั่งไคลเอ็นต์ Angular ที่ใช้กับRestangularและFileSaver.js

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>

ตัวควบคุมเชิงมุม

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

บริการเชิงมุม

'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 กับสิ่งนี้ได้อย่างไร? คุณใช้มันอย่างไร?
Alan Dunning

2

นอกจากนี้เรายังต้องพัฒนาโซลูชันซึ่งจะทำงานร่วมกับ API ที่ต้องมีการตรวจสอบสิทธิ์ (ดูบทความนี้ )

การใช้ AngularJS โดยสรุปนี่คือวิธีที่เราทำ:

ขั้นตอนที่ 1: สร้างคำสั่งเฉพาะ

// 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: สร้างเทมเพลต

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

ขั้นตอนที่ 3: ใช้มัน

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

ปุ่มสีน้ำเงินจะแสดงขึ้น เมื่อคลิกไฟล์ PDF จะถูกดาวน์โหลด (ข้อควรระวัง: แบ็กเอนด์ต้องส่ง PDF ในการเข้ารหัส Base64!) และใส่ลงใน href ปุ่มเปลี่ยนเป็นสีเขียวและสวิทช์ข้อความที่จะบันทึก ผู้ใช้สามารถคลิกอีกครั้งและจะนำเสนอกับกล่องโต้ตอบดาวน์โหลดไฟล์มาตรฐานสำหรับแฟ้มของฉัน-awesome.pdf


1

ส่งไฟล์ของคุณเป็นสตริง 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();

หากเมธอด attr ไม่ทำงานใน Firefox คุณยังสามารถใช้เมธอด javaScript setAttribute


var blob = หยดใหม่ ([atob (response.payload)], {"data": "attachment / csv; charset = utf-8;"}); saveAs (หยด 'ชื่อไฟล์');
PPB

ขอบคุณ PPB โซลูชันของคุณใช้ได้ผลสำหรับฉันยกเว้น atob นั่นไม่จำเป็นสำหรับฉัน
Larry Flewwelling

0

คุณสามารถใช้ฟังก์ชัน showfile ซึ่งรับพารามิเตอร์ของข้อมูลที่ส่งคืนจาก WEBApi และชื่อไฟล์สำหรับไฟล์ที่คุณพยายามดาวน์โหลด สิ่งที่ฉันทำคือสร้างบริการเบราว์เซอร์แยกต่างหากระบุเบราว์เซอร์ของผู้ใช้จากนั้นจัดการการเรนเดอร์ไฟล์ตามเบราว์เซอร์ ตัวอย่างเช่นหากเบราว์เซอร์เป้าหมายเป็น chrome บน iPad คุณต้องใช้วัตถุ 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);
}

1
ขอบคุณ Scott ที่จับสิ่งของเหล่านั้น ฉันได้ปรับโครงสร้างใหม่และเพิ่มคำอธิบาย
Erkin Djindjiev

0

ฉันได้ทำตามวิธีแก้ปัญหามากมายและนี่คือสิ่งที่ฉันพบว่าได้ผลดีสำหรับฉัน

ในกรณีของฉันฉันต้องส่งคำขอโพสต์พร้อมข้อมูลรับรองบางอย่าง ค่าใช้จ่ายเล็กน้อยคือการเพิ่ม jquery ภายในสคริปต์ แต่ก็คุ้มค่า.

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

ในส่วนประกอบของคุณเช่นรหัส js เชิงมุม:

function getthefile (){
window.location.href='http://localhost:1036/CourseRegConfirm/getfile';
};
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.