Завантажте файл із методу веб-API ASP.NET за допомогою AngularJS


132

У своєму проекті Angular JS я маю <a>тег прив’язки, який при натисканні робить HTTP- GETзапит методом 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
Який би був тип файлінгу? тільки зображення?
Рашмін Явія

@RashminJaviya Може бути .jpg, .doc, .xlsx, .docx, .txt або .pdf.
деDragonsDwell

Який .Net фреймворк ви використовуєте?
Рашмін Явія

@RashminJaviya .net 4.5
деDragonsDwell

1
@Kurkula слід використовувати Файл System.IO.File не від контролера
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окремо. Дякую? 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;
    });
};

Використання:

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

Примітки:

Ви повинні змінити метод WebApi, щоб повернути такі заголовки:

  • Я використовував x-filenameзаголовок для надсилання імені файлу. Це спеціальний заголовок для зручності, проте ви можете витягти ім'я файлу із content-dispositionзаголовка, використовуючи регулярні вирази.

  • Ви також повинні встановити content-typeзаголовок mime для вашої відповіді, щоб браузер знав формат даних.

Я сподіваюся, що це допомагає.


Привіт @Scott Я використовував ваш метод, і він працює, але браузер зберігає файл у вигляді html не pdf. Я встановлюю тип вмісту до application / pdf, і коли я перевіряю в інструментах розробника в хромі, тип відповіді встановлюється на application / pdf, але коли я зберігаю файл, він відображається як html, він працює, коли я відкриваю його, файл є відкрито у форматі PDF, але у веб-переглядачі та для мого браузера є значок за замовчуванням. Ви знаєте, що я можу зробити неправильно?
Bartosz Bialecki

1
:-( вибачте. Я пропустив це. BTW це чудово працює. Навіть краще, ніж filesaver.js
Jeeva Jsb

1
Коли я намагаюся завантажити виконувану програму Microsoft за допомогою цього методу, я повертаю розмір краплі, який приблизно в 1,5 рази перевищує фактичний розмір файлу. Файл, який завантажується, має неправильний розмір кролика. Будь-які думки, чому це може статися? Виходячи з перегляду фіддлера, розмір відповіді є правильним, але перетворення вмісту на блоб певним чином збільшує його.
користувач3517454

1
Нарешті з'ясував проблему ... Я змінив код сервера з посади, щоб отримати, але я не змінив параметри для $ http.get. Тому тип відповіді ніколи не встановлювався як масив масиву, оскільки він передається як третій аргумент, а не другий.
користувач3517454

1
@RobertGoldwein Ви можете це зробити, але припущення полягає в тому, що якщо ви використовуєте програму angularjs, ви бажаєте, щоб користувач залишався в програмі, де зберігається стан і можливість використовувати функціональність після початку завантаження. Якщо ви переходите безпосередньо до завантаження, немає гарантії, що додаток залишиться активним, оскільки браузер може не обробляти завантаження так, як ми очікуємо. Уявіть, чи сервер 500s або 404s запит. Зараз користувач вийшов із програми Angular. Пропонується найпростіша пропозиція відкрити посилання в новому вікні за допомогою window.open.
Скотт

10

C # WebApi PDF завантажте всі роботи з Angular JS Authentication

Веб-контролер 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;
    }

Кутова служба 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;
        }

Або один зробить

Кутовий контролер JS викликає службу

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

Для мене веб-API був Rails, а клієнтська сторона Angular використовується для Restangular та FileSaver.js

Веб-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 для цього? Як ти це реалізував?
Алан Даннінг

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. Кнопка стає зеленим і перемикає текст на Зберегти . Користувач може натиснути ще раз, і буде представлено стандартне діалогове вікно для завантаження файлу my-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 = new Blob ([atob (response.payload)], {"data": "вкладення / csv; charset = utf-8;"}); saveAs (blob, 'ім'я файлу');
ППБ

Дякую, PPB, ваше рішення працювало на мене, крім атобу. Цього мені не потрібно було.
Ларрі Флеуеллінг

0

Ви можете реалізувати функцію showfile, яка приймає параметри даних, повернених з WEBApi, та ім'я файлу, який ви намагаєтеся завантажити. Що я зробив, це створити окрему службу браузера, ідентифікує браузер користувача, а потім обробляє візуалізацію файлу на основі браузера. Наприклад, якщо цільовий браузер хромований на ipad, ви повинні використовувати об’єкт 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);
}

1
Дякую Скотту, що впіймав ці предмети. Я відремонтував і додав пояснення.
Еркін Джінджієв

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';
};
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.