【问题标题】:Download file from an ASP.NET Web API method using AngularJS使用 AngularJS 从 ASP.NET Web API 方法下载文件
【发布时间】:2014-07-27 14:48:20
【问题描述】:

在我的 Angular JS 项目中,我有一个 <a> 锚标记,单击该标记时会向返回文件的 WebAPI 方法发出 HTTP GET 请求。

现在,我希望在请求成功后将文件下载给用户。我该怎么做?

锚标记:

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

【问题讨论】:

  • 文件类型是什么?只有图片?
  • @RashminJaviya 可以是 .jpg、.doc、.xlsx、.docx、.txt 或 .pdf。
  • 您使用的是哪个 .Net 框架?
  • @RashminJaviya .net 4.5
  • @Kurkula 你应该使用 System.IO.File 文件而不是来自控制器

标签: c# javascript html angularjs asp.net-web-api


【解决方案1】:

使用ajax下载二进制文件的支持不是很好,还是under development as working drafts

简单下载方法:

你可以通过下面的代码让浏览器下载请求的文件,所有浏览器都支持这个,显然会触发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 标头,以便浏览器知道数据格式。

我希望这会有所帮助。

【讨论】:

  • 嗨@Scott我使用了你的方法并且它有效,但是浏览器将文件保存为html类型而不是pdf。我将 content-type 设置为 application/pdf 并且当我在 chrome 中签入开发人员工具时,响应类型设置为 application/pdf 但是当我保存文件时它显示为 html,它可以工作,当我打开它时文件是以 pdf 格式打开,但在浏览器中打开,并且我的浏览器具有默认图标。你知道我会做错什么吗?
  • :-( 抱歉。我错过了。顺便说一句,这很好用。甚至比 filesaver.js 更好
  • 当我尝试通过此方法下载 Microsoft 可执行文件时,我得到的 blob 大小约为实际文件大小的 1.5 倍。下载的文件的 blob 大小不正确。关于为什么会发生这种情况的任何想法?基于查看提琴手,响应的大小是正确的,但是将内容转换为 blob 会以某种方式增加它。
  • 终于找到问题所在了……我把服务器代码从post改成了get,但是$http.get的参数我没有改。所以响应类型永远不会被设置为 arraybuffer,因为它是作为第三个参数而不是第二个参数传入的。
  • @RobertGoldwein 你可以这样做,但假设是如果你使用 angularjs 应用程序,你希望用户留在应用程序中,下载开始后的状态和使用功能的能力得到维护。如果您直接导航到下载,则无法保证应用程序将保持活动状态,因为浏览器可能无法按照我们预期的方式处理下载。想象一下,如果服务器 500s 或 404s 请求。用户现在退出了 Angular 应用程序。建议使用window.open 在新窗口中打开链接的最简单建议。
【解决方案2】:

C# WebApi PDF 下载所有使用 Angular JS 身份验证

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 控制器调用服务

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>

这将被重构,现在只分享代码希望它可以帮助某人,因为我花了一段时间才让它工作。

【讨论】:

【解决方案3】:

对我来说,Web API 是 Rails 和客户端 Angular,与 RestangularFileSaver.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 的?你是如何实现的?
【解决方案4】:

我们还必须开发一种解决方案,该解决方案甚至可以与需要身份验证的 API 一起使用(请参阅 this article

简而言之,我们就是这样使用 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(注意:后端必须以 Base64 编码提供 PDF!)并放入 href。该按钮变为绿色并将文本切换到保存。用户可以再次单击,将看到文件 my-awesome.pdf 的标准下载文件对话框。

【讨论】:

    【解决方案5】:

    将您的文件作为 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,除了 atob,您的解决方案对我有用。这对我来说不是必需的。
    【解决方案6】:

    您可以实现一个 showfile 函数,该函数接受从 WEBApi 返回的数据的参数,以及您尝试下载的文件的文件名。我所做的是创建一个单独的浏览器服务来识别用户的浏览器,然后根据浏览器处理文件的呈现。例如,如果目标浏览器是 ipad 上的 chrome,则必须使用 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);
    }
    

    【讨论】:

    • 感谢 Scott 抓到这些物品。我已经重构并添加了解释。
    【解决方案7】:

    我已经经历了一系列的解决方案,这对我来说非常有效。

    就我而言,我需要发送带有一些凭据的发布请求。 小的开销是在脚本中添加 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();
    
        }
    

    【讨论】:

      【解决方案8】:

      在您的组件中,即 Angular js 代码:

      function getthefile (){
      window.location.href='http://localhost:1036/CourseRegConfirm/getfile';
      };
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-11
        • 2021-02-13
        • 1970-01-01
        • 2012-08-30
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多