Tải xuống tệp từ phương pháp API Web ASP.NET bằng AngularJS


132

Trong dự án Angular JS của tôi, tôi có một <a>thẻ neo, khi được nhấp sẽ tạo một GETyêu cầu HTTP đến phương thức WebAPI trả về một tệp.

Bây giờ, tôi muốn tệp được tải xuống cho người dùng sau khi yêu cầu thành công. Làm thế nào để làm điều đó?

Thẻ neo:

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

Phương pháp WebAPI của tôi:

[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
Điều gì sẽ là filetype? chỉ hình ảnh?
Rashmin Javiya

@RashminJaviya Có thể là .jpg, .doc, .xlsx, .docx, .txt hoặc .pdf.
whereDragonsDwell

Mà .Net framework bạn đang sử dụng?
Rashmin Javiya

@RashminJaviya Net 4.5
whereDragonsDwell

1
@Kurkula bạn nên sử dụng Tệp của System.IO. Không phải từ bộ điều khiển
Javysk

Câu trả lời:


242

Hỗ trợ tải xuống các tệp nhị phân trong việc sử dụng ajax không phải là tuyệt vời, nó vẫn đang được phát triển rất nhiều dưới dạng bản nháp đang hoạt động .

Phương pháp tải xuống đơn giản:

Bạn có thể yêu cầu trình duyệt tải xuống tệp được yêu cầu chỉ bằng cách sử dụng mã bên dưới và điều này được hỗ trợ trong tất cả các trình duyệt và rõ ràng sẽ kích hoạt yêu cầu WebApi giống như vậy.

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

Phương pháp tải xuống nhị phân Ajax:

Sử dụng ajax để tải xuống tệp nhị phân có thể được thực hiện trong một số trình duyệt và dưới đây là một triển khai sẽ hoạt động theo các hương vị mới nhất của Chrome, Internet Explorer, FireFox và Safari.

Nó sử dụng một arraybuffer loại phản hồi, sau đó được chuyển đổi thành JavaScript blob, sau đó được trình bày để lưu bằng saveBlobphương thức - mặc dù điều này hiện chỉ có trong Internet Explorer - hoặc biến thành URL dữ liệu blob được trình duyệt mở ra, kích hoạt hộp thoại tải xuống nếu loại mime được hỗ trợ để xem trong trình duyệt.

Hỗ trợ Internet Explorer 11 (Đã sửa)

Lưu ý: Internet Explorer 11 không thích sử dụng msSaveBlobchức năng nếu nó đã được đặt bí danh - có lẽ là một tính năng bảo mật, nhưng nhiều khả năng là một lỗ hổng, Vì vậy, việc sử dụng var saveBlob = navigator.msSaveBlob || navigator.webkitSaveBlob ... etc.để xác định saveBlobhỗ trợ có sẵn đã gây ra ngoại lệ; do đó tại sao mã dưới đây bây giờ kiểm tra navigator.msSaveBlobriêng. Cảm ơn? 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;
    });
};

Sử dụng:

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

Ghi chú:

Bạn nên sửa đổi phương thức WebApi của mình để trả về các tiêu đề sau:

  • Tôi đã sử dụng x-filenametiêu đề để gửi tên tệp. Đây là một tiêu đề tùy chỉnh để thuận tiện, tuy nhiên bạn có thể trích xuất tên tệp từ content-dispositiontiêu đề bằng các biểu thức thông thường.

  • Bạn cũng nên đặt content-typetiêu đề mime cho phản hồi của mình, để trình duyệt biết định dạng dữ liệu.

Tôi hi vọng cái này giúp được.


Xin chào @Scott Tôi đã sử dụng phương pháp của bạn và nó hoạt động nhưng trình duyệt lưu tệp dưới dạng html chứ không phải pdf. Tôi đặt loại nội dung thành application / pdf và khi tôi kiểm tra các công cụ dành cho nhà phát triển trong chrome, loại phản hồi được đặt thành application / pdf nhưng khi tôi lưu tệp thì nó được hiển thị dưới dạng html, nó hoạt động, khi tôi mở tệp thì đã mở dưới dạng pdf nhưng trong trình duyệt và có biểu tượng mặc định cho trình duyệt của tôi. Bạn có biết những gì tôi có thể làm sai?
Bartosz Bialecki

1
:-( xin lỗi. Tôi đã bỏ lỡ khi thấy điều đó. BTW này đang hoạt động rất tốt. Thậm chí còn tốt hơn filesaver.js
Jeeva Jsb

1
Khi tôi cố tải xuống một tệp thực thi của Microsoft thông qua phương pháp này, tôi nhận lại kích thước blob gấp khoảng 1,5 lần kích thước tệp thực tế. Các tập tin được tải xuống có kích thước không chính xác của blob. Bất kỳ suy nghĩ về lý do tại sao điều này có thể xảy ra? Dựa trên việc xem xét fiddler, kích thước của phản hồi là chính xác, nhưng việc chuyển đổi nội dung thành một blob đang làm tăng nó bằng cách nào đó.
dùng3517454

1
Cuối cùng đã tìm ra vấn đề ... Tôi đã thay đổi mã máy chủ từ một bài đăng để nhận, nhưng tôi đã không thay đổi các tham số cho $ http.get. Vì vậy, kiểu phản hồi không bao giờ được đặt là Arraybuffer vì nó được truyền vào làm đối số thứ ba chứ không phải là đối số thứ hai.
dùng3517454

1
@RobertGoldwein Bạn có thể làm điều đó, nhưng giả định là nếu bạn đang sử dụng ứng dụng angularjs, bạn muốn người dùng ở lại ứng dụng, nơi duy trì trạng thái và khả năng sử dụng chức năng sau khi quá trình tải xuống được duy trì. Nếu bạn điều hướng trực tiếp đến phần tải xuống, không có gì đảm bảo ứng dụng sẽ vẫn hoạt động, vì trình duyệt có thể không xử lý việc tải xuống theo cách chúng tôi mong đợi. Hãy tưởng tượng nếu máy chủ 500 hoặc 404s yêu cầu. Người dùng hiện đã ra khỏi ứng dụng Angular. Đề xuất đơn giản nhất về việc mở liên kết trong một cửa sổ mới bằng cách sử dụng window.openđược đề xuất.
Scott

10

C # WebApi PDF tải xuống tất cả hoạt động với Xác thực JS góc

Trình điều khiển 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;
    }

Dịch vụ JS góc

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

Một trong hai sẽ làm

Bộ điều khiển JS góc cạnh gọi dịch vụ

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

Và cuối trang HTML

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

Điều này sẽ được tái cấu trúc chỉ chia sẻ mã bây giờ hy vọng nó sẽ giúp được ai đó vì tôi phải mất một thời gian để làm việc này.


Mã ở trên hoạt động trên tất cả các hệ thống ngoại trừ ios, vì vậy hãy sử dụng các bước này nếu bạn cần điều này để hoạt động trên ios Bước 1 kiểm tra xem ios stackoverflow.com/questions/9038625/detect-if-device-is-ios Bước 2 (nếu ios) sử dụng stackoverflow.com/questions/24485077/ cường
tfa


6

Đối với tôi API Web là Rails và phía máy khách Angular được sử dụng với RestangularFileSaver.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>

Bộ điều khiển góc

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

Dịch vụ góc

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

Làm thế nào bạn sử dụng Filesaver.js với điều này? Làm thế nào bạn thực hiện nó?
Alan Dunning

2

Chúng tôi cũng đã phải phát triển một giải pháp thậm chí sẽ hoạt động với các API yêu cầu xác thực (xem bài viết này )

Sử dụng AngularJS một cách ngắn gọn ở đây là cách chúng tôi đã làm:

Bước 1: Tạo một chỉ thị chuyên dụng

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

Bước 2: Tạo mẫu

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

Bước 3: Sử dụng nó

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

Điều này sẽ khiến một nút màu xanh. Khi được nhấp, một tệp PDF sẽ được tải xuống (Chú ý: phần phụ trợ phải gửi bản PDF ở dạng mã hóa Base64!) Và đưa vào href. Nút chuyển sang màu xanh lá cây và chuyển văn bản thành Lưu . Người dùng có thể nhấp lại và sẽ được trình bày với hộp thoại tệp tải xuống tiêu chuẩn cho tệp my-awesome.pdf .


1

Gửi tệp của bạn dưới dạng chuỗi 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();

Nếu phương thức attr không hoạt động trong Firefox Bạn cũng có thể sử dụng phương thức javaScript setAttribution


var blob = new Blob ([atob (respons.payload)], {"data": "tệp đính kèm / csv; charset = utf-8;"}); saveAs (blob, 'tên tệp');
PPB

Cảm ơn PPB, giải pháp của bạn đã làm việc cho tôi ngoại trừ atob. Điều đó không bắt buộc đối với tôi.
Larry Flewwelling

0

Bạn có thể triển khai chức năng showfile lấy các tham số của dữ liệu được trả về từ WEBApi và tên tệp cho tệp bạn đang cố tải xuống. Những gì tôi đã làm là tạo một dịch vụ trình duyệt riêng xác định trình duyệt của người dùng và sau đó xử lý kết xuất tệp dựa trên trình duyệt. Chẳng hạn, nếu trình duyệt đích là chrome trên ipad, bạn phải sử dụng đối tượng FileReader của 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
Cảm ơn Scott vì đã bắt được những món đồ đó. Tôi đã tái cấu trúc và thêm một lời giải thích.
Erkin Djindjiev

0

Tôi đã trải qua một loạt các giải pháp và đây là những gì tôi thấy đã làm việc rất tốt cho tôi.

Trong trường hợp của tôi, tôi cần gửi một yêu cầu bài viết với một số thông tin. Chi phí nhỏ là để thêm jquery bên trong kịch bản. Nhưng nó là giá trị nó.

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

Trong thành phần của bạn tức là mã js góc:

function getthefile (){
window.location.href='http://localhost:1036/CourseRegConfirm/getfile';
};
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.