AngularJS를 사용하여 ASP.NET 웹 API 메소드에서 파일 다운로드


132

내 Angular JS 프로젝트에서 <a>클릭하면 GET파일을 반환하는 WebAPI 메서드에 HTTP 요청을 하는 앵커 태그 가 있습니다.

이제 요청이 성공하면 파일을 사용자에게 다운로드하고 싶습니다. 어떻게합니까?

앵커 태그 :

<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 일 수 있습니다.
whereDragonsDwell

어떤 .Net 프레임 워크를 사용하고 있습니까?
Rashmin Javiya

@RashminJaviya .net 4.5
whereDragonsDwell 2016 년

1
@ Kurkula 컨트롤러가 아닌 File.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에만 존재하지만 브라우저가 여는 BLOB 데이터 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응답에 대한 MIME 헤더도 설정해야 브라우저가 데이터 형식을 알 수 있습니다.

이게 도움이 되길 바란다.


안녕 @ 스콧 나는 당신의 방법을 사용하고 작동하지만 브라우저는 파일을 pdf가 아닌 html 형식으로 파일을 저장합니다. 콘텐츠 유형을 application / pdf로 설정하고 크롬에서 개발자 도구를 체크인하면 응답 유형이 application / pdf로 설정되지만 파일을 저장할 때 html로 표시되며 파일을 열면 작동합니다. pdf로 열었지만 브라우저에서 열었고 내 브라우저의 아이콘 기본값이 있습니다. 내가 뭘 잘못 할 수 있는지 알아?
Bartosz Bialecki

1
:-( 죄송합니다.보고 싶었습니다. BTW이 기능이 크게 작동하고 있습니다. filesaver.js보다 우수
Jeeva Jsb

1
이 방법을 통해 Microsoft 실행 파일을 다운로드하려고하면 실제 파일 크기의 약 1.5 배인 블롭 크기를 다시 얻습니다. 다운로드 된 파일의 크기가 잘못되었습니다. 왜 이런 일이 일어날 지 생각하십니까? 피들러를 보면 응답의 크기는 정확하지만 내용을 얼룩으로 변환하면 어떻게 든 증가합니다.
user3517454

1
마지막으로 문제를 알아 냈습니다 ... 게시물에서 서버 코드를 변경했지만 $ http.get의 매개 변수는 변경하지 않았습니다. 따라서 응답 유형은 두 번째 인수가 아닌 세 번째 인수로 전달되었으므로 arraybuffer로 설정되지 않았습니다.
user3517454

1
@RobertGoldwein 그렇게 할 수 있지만, angularjs 응용 프로그램을 사용하는 경우 다운로드가 시작된 후 기능을 사용할 수있는 상태와 기능이 유지되는 응용 프로그램에 사용자가 남아 있기를 원한다고 가정합니다. 다운로드로 직접 이동하면 브라우저가 다운로드를 예상 한대로 처리하지 못할 수 있으므로 응용 프로그램이 계속 활성 상태로 유지된다는 보장이 없습니다. 서버가 요청을 500 또는 404로 가정합니다. 사용자가 이제 Angular 앱을 벗어났습니다. 사용하여 새 창에서 링크를 여는 가장 간단한 제안 window.open이 제안됩니다.
Scott

10

C # WebApi PDF 다운로드 Angular JS 인증으로 모든 작업

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

어느 쪽이든

서비스를 호출하는 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

나를 위해 웹 API는 RestangularFileSaver.js 와 함께 사용되는 Rails 및 클라이언트 측 Angular 였습니다.

웹 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에 포함됩니다. 버튼이 녹색으로 바뀌고 텍스트가 저장으로 전환됩니다 . 사용자는 다시 클릭하면 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();

Firefox에서 attr 메소드가 작동하지 않는 경우 javaScript setAttribute 메소드를 사용할 수도 있습니다.


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

PPB에게 감사합니다. atob을 제외한 솔루션이 저에게 효과적이었습니다. 그것은 나에게 필요하지 않았습니다.
Larry Flewwelling 23.32에

0

WEBApi에서 리턴 된 데이터의 매개 변수 및 다운로드하려는 파일의 파일 이름을 취하는 showfile 함수를 구현할 수 있습니다. 내가 한 것은 별도의 브라우저 서비스를 만들어 사용자의 브라우저를 식별 한 다음 브라우저를 기반으로 파일 렌더링을 처리하는 것입니다. 예를 들어 대상 브라우저가 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.