download csv file from web api in angular js
Asked Answered
S

10

48

my API controller is returning a csv file as seen below:

    [HttpPost]
    public HttpResponseMessage GenerateCSV(FieldParameters fieldParams)
    {
        var output = new byte[] { };
        if (fieldParams!= null)
        {
            using (var stream = new MemoryStream())
            {
                this.SerializeSetting(fieldParams, stream);
                stream.Flush();
                output = stream.ToArray();
            }
        }
        var result = new HttpResponseMessage(HttpStatusCode.OK) { Content = new ByteArrayContent(output) };
        result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
        {
            FileName = "File.csv"
        };
        return result;
    }

and my angularjs that will send and receive the csv file is shown below:

$scope.save = function () {
            var csvInput= extractDetails();

            // File is an angular resource. We call its save method here which
            // accesses the api above which should return the content of csv
            File.save(csvInput, function (content) {
                var dataUrl = 'data:text/csv;utf-8,' + encodeURI(content);
                var hiddenElement = document.createElement('a');
                hiddenElement.setAttribute('href', dataUrl);
                hiddenElement.click();
            });
        };

In chrome, it downloads a file which is called document but has no file type extension. The content of the file is [Object object].

In IE10, nothing is downloaded.

What could i do to fix this?

UPDATE: This might work for you guys out there with the same problem: link

Sudorific answered 30/11, 2013 at 12:58 Comment(1)
DOM manipulation shouldn't be done in controllersJanitress
T
62

Try it like :

File.save(csvInput, function (content) {
    var hiddenElement = document.createElement('a');

    hiddenElement.href = 'data:attachment/csv,' + encodeURI(content);
    hiddenElement.target = '_blank';
    hiddenElement.download = 'myFile.csv';
    hiddenElement.click();
});

based on the most excellent answer in this question

Tint answered 30/11, 2013 at 13:4 Comment(18)
it downloads the file with the .csv extension, but the content inside are not texts but the word: [object Object]Sudorific
Then content is an object, and not a csv string. console.log content and see what you get.Tint
it was an array of charactersSudorific
Then you have to convert it to a string somehow, if it's an array, and not an object, you can just use join('')Tint
It looks like it's still a byteArray on the serverside, and you should probably convert it there before sending it to Angular.Tint
i think its a json object: Resource {0: "C", 1: "u", 2: "s", 3: "t",...} with a $promise and $resolve properties alsoSudorific
Were you able to resolve this issue? We are getting the binary contents of the excel file in a json object also.Inconvincible
Same here -- is there a solution for this?Single
should use $http instead of $resource to get csv string; $resource is expected to work well with objects to jsonTamatamable
@Tamatamable is correct, see similar answer here: https://mcmap.net/q/247351/-how-to-handle-plain-text-server-responseNkrumah
Hey any idea why I am getting file result as a file named download without csv extension, it totally ignores hidden.Element = 'myFile.csv' and this does not seem to work on FirefoxJarry
@Jarry - the file name is given in the download attribute, so hiddenElement.download = 'myFile.csv'; sets the file name, not hiddenElement = 'myFile.csv'Tint
@Tint sorry, but I am not understanding what I am doing wrong.Jarry
@Jarry - You're right, this used to work just fine not too long ago, but it no longer does, unfortunately. It has something to do with mime type, as a regular anchor with just a string as href will keep the filename, but it will also fail to download (jsfiddle.net/nkm2b/180), but as soon as data: is used it tries to validate the mime type or something, and then downloads the content but renames the file for some strange reason.Tint
With the target attribute set to _blank, this does't work in Safari. And Safari does not support the download attribute, so the file name will not work too, it downloads a file with name 'Unknown'.Lightness
How do we go about .xlsx instead of .csv (data:attachment/csv,)Millhon
@MohitSehgal - probably the same way to create files, but xlsx content is probably harder to create with JS.Tint
Anyone having issue with downloading large files with this code?Arsenious
M
10

I used the below solution and it worked for me.

 if (window.navigator.msSaveOrOpenBlob) {
   var blob = new Blob([decodeURIComponent(encodeURI(result.data))], {
     type: "text/csv;charset=utf-8;"
   });
   navigator.msSaveBlob(blob, 'filename.csv');
 } else {
   var a = document.createElement('a');
   a.href = 'data:attachment/csv;charset=utf-8,' + encodeURI(result.data);
   a.target = '_blank';
   a.download = 'filename.csv';
   document.body.appendChild(a);
   a.click();
 }
Mantic answered 30/12, 2014 at 2:54 Comment(0)
R
4

The last answer worked for me for a few months, then stopped recognizing the filename, as adeneo commented ...

@Scott's answer here is working for me:

Download file from an ASP.NET Web API method using AngularJS

Raimundo answered 10/6, 2014 at 13:37 Comment(0)
S
4

None of those worked for me in Chrome 42...

Instead my directive now uses this link function (base64 made it work):

  link: function(scope, element, attrs) {
    var downloadFile = function downloadFile() {
      var filename = scope.getFilename();
      var link = angular.element('<a/>');
      link.attr({
        href: 'data:attachment/csv;base64,' + encodeURI($window.btoa(scope.csv)),
        target: '_blank',
        download: filename
      })[0].click();
      $timeout(function(){
        link.remove();
      }, 50);
    };

    element.bind('click', function(e) {
      scope.buildCSV().then(function(csv) {
        downloadFile();
      });
      scope.$apply();
    });
  }
Safier answered 8/4, 2015 at 3:56 Comment(1)
That's what it's for! :)Safier
G
2

I had to implement this recently. Thought of sharing what I had figured out;

To make it work in Safari, I had to set target: '_self',. Don't worry about filename in Safari. Looks like it's not supported as mentioned here; https://github.com/konklone/json/issues/56 (http://caniuse.com/#search=download)

The below code works fine for me in Mozilla, Chrome & Safari;

  var anchor = angular.element('<a/>');
  anchor.css({display: 'none'});
  angular.element(document.body).append(anchor);
  anchor.attr({
    href: 'data:attachment/csv;charset=utf-8,' + encodeURIComponent(data),
    target: '_self',
    download: 'data.csv'
  })[0].click();
  anchor.remove();
Gregoor answered 13/9, 2016 at 0:58 Comment(0)
F
1

Rather than use Ajax / XMLHttpRequest / $http to invoke your WebApi method, use an html form. That way the browser saves the file using the filename and content type information in the response headers, and you don't need to work around javascript's limitations on file handling. You might also use a GET method rather than a POST as the method returns data. Here's an example form:

<form name="export" action="/MyController/Export" method="get" novalidate>
    <input name="id" type="id" ng-model="id" placeholder="ID" />
    <input name="fileName" type="text" ng-model="filename" placeholder="file name" required />
    <span class="error" ng-show="export.fileName.$error.required">Filename is required!</span>
    <button type="submit" ng-disabled="export.$invalid">Export</button>
</form>
Feck answered 18/11, 2015 at 11:44 Comment(0)
B
1

In Angular 1.5, use the $window service to download a file.

angular.module('app.csv').factory('csvService', csvService);

csvService.$inject = ['$window'];

function csvService($window) {
    function downloadCSV(urlToCSV) {
        $window.location = urlToCSV;
    }
}
Bobbybobbye answered 28/2, 2017 at 18:54 Comment(0)
S
0

The a.download is not supported by IE. At least at the HTML5 "supported" pages. :(

Stearic answered 30/6, 2015 at 17:58 Comment(0)
C
0

I think the best way to download any file generated by REST call is to use window.location example :

    $http({
        url: url,
        method: 'GET'
    })
    .then(function scb(response) {
        var dataResponse = response.data;
        //if response.data for example is : localhost/export/data.csv
        
        //the following will download the file without changing the current page location
        window.location = 'http://'+ response.data
    }, function(response) {
      showWarningNotification($filter('translate')("global.errorGetDataServer"));
    });
Choiseul answered 12/7, 2017 at 8:12 Comment(0)
W
0

Workable solution:

downloadCSV(data){   
 const newBlob = new Blob([decodeURIComponent(encodeURI(data))], { type: 'text/csv;charset=utf-8;' });

        // IE doesn't allow using a blob object directly as link href
        // instead it is necessary to use msSaveOrOpenBlob
        if (window.navigator && window.navigator.msSaveOrOpenBlob) {
          window.navigator.msSaveOrOpenBlob(newBlob);
          return;
        }

        // For other browsers:
        // Create a link pointing to the ObjectURL containing the blob.
        const fileData = window.URL.createObjectURL(newBlob);

        const link = document.createElement('a');
        link.href = fileData;
        link.download = `Usecase-Unprocessed.csv`;
        // this is necessary as link.click() does not work on the latest firefox
        link.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window }));

        setTimeout(function () {
          // For Firefox it is necessary to delay revoking the ObjectURL
          window.URL.revokeObjectURL(fileData);
          link.remove();
        }, 5000);
  }
Weitzman answered 12/10, 2020 at 10:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.