Загрузите файл из метода ASP.NET Web API, используя 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
Какой будет тип файла? только изображение?
Рашмин Джавия 09

@RashminJaviya Может быть .jpg, .doc, .xlsx, .docx, .txt или .pdf.
whereDragonsDwell 09

Какую платформу .Net вы используете?
Рашмин Джавия 09

@RashminJaviya .net 4.5
whereDragonsDwell 09

1
@Kurkula, вы должны использовать файл System.IO.File не с контроллера
Явиск

Ответы:


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, и когда я проверяю инструменты разработчика в chrome, тип ответа устанавливается на application / pdf, но когда я сохраняю файл, он отображается как html, он работает, когда я открываю его, файл открыт как pdf, но в браузере и имеет значок по умолчанию для моего браузера. Вы знаете, что я могу сделать не так?
Bartosz Bialecki

1
:-( извините. Я пропустил это посмотреть. Кстати, это отлично работает. Даже лучше, чем filesaver.js
Jeeva Jsb

1
Когда я пытаюсь загрузить исполняемый файл Microsoft этим методом, я получаю размер большого двоичного объекта примерно в 1,5 раза больше фактического размера файла. Загружаемый файл имеет неправильный размер большого двоичного объекта. Есть мысли о том, почему это может происходить? Если посмотреть на скрипач, размер ответа правильный, но преобразование содержимого в большой двоичный объект каким-то образом увеличивает его.
user3517454

1
Наконец-то разобрался с проблемой ... Я изменил код сервера с поста на получение, но я не изменил параметры для $ http.get. Таким образом, тип ответа никогда не устанавливался как буфер массива, поскольку он передавался в качестве третьего аргумента, а не второго.
user3517454

1
@RobertGoldwein Вы можете это сделать, но предполагается, что если вы используете приложение angularjs, вы хотите, чтобы пользователь оставался в приложении, где сохраняется состояние и возможность использовать функциональность после начала загрузки. Если вы перейдете непосредственно к загрузке, нет гарантии, что приложение останется активным, так как браузер может не обрабатывать загрузку так, как мы ожидаем. Представьте, что сервер отправляет 500 или 404 запроса. Пользователь сейчас вне приложения 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;
    }

Сервис 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 и на стороне клиента Угловая используется с 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": "attachment / csv; charset = utf-8;"}); saveAs (blob, 'имя файла');
PPB

Спасибо PPB, у меня ваше решение сработало кроме атоба. Мне этого не требовалось.
Ларри Флевеллинг,

0

Вы можете реализовать функцию showfile, которая принимает параметры данных, возвращаемых из WEBApi, и имя файла для файла, который вы пытаетесь загрузить. Я создал отдельную службу браузера, которая идентифицирует браузер пользователя и затем обрабатывает рендеринг файла на основе браузера. Например, если целевой браузер на ipad является chrome, вы должны использовать объект 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();

    }

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.