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

Community
  • 1
  • 1
raberana
  • 11,739
  • 18
  • 69
  • 95

10 Answers10

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

Community
  • 1
  • 1
adeneo
  • 312,895
  • 29
  • 395
  • 388
  • it downloads the file with the .csv extension, but the content inside are not texts but the word: [object Object] – raberana Nov 30 '13 at 13:12
  • 1
    Then `content` is an object, and not a csv string. console.log `content` and see what you get. – adeneo Nov 30 '13 at 13:14
  • 2
    it was an array of characters – raberana Nov 30 '13 at 13:22
  • Then you have to convert it to a string somehow, if it's an array, and not an object, you can just use `join('')` – adeneo Nov 30 '13 at 13:24
  • It looks like it's still a byteArray on the serverside, and you should probably convert it there before sending it to Angular. – adeneo Nov 30 '13 at 13:27
  • i think its a json object: Resource {0: "C", 1: "u", 2: "s", 3: "t",...} with a $promise and $resolve properties also – raberana Nov 30 '13 at 13:31
  • 1
    Were you able to resolve this issue? We are getting the binary contents of the excel file in a json object also. – Andrew Thomas Feb 01 '14 at 00:19
  • Same here -- is there a solution for this? – htxryan Feb 26 '14 at 19:13
  • 2
    should use $http instead of $resource to get csv string; $resource is expected to work well with objects to json – Krishna Mar 18 '14 at 16:48
  • 2
    @Krishna is correct, see similar answer here: http://stackoverflow.com/a/17888624/1848999 – justin Apr 04 '14 at 06:21
  • 1
    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 Firefox – eugenekgn May 27 '14 at 12:33
  • @eugenekgn - the file name is given in the download attribute, so `hiddenElement.download = 'myFile.csv';` sets the file name, not `hiddenElement = 'myFile.csv'` – adeneo May 27 '14 at 12:39
  • @adeneo sorry, but I am not understanding what I am doing wrong. – eugenekgn May 27 '14 at 13:06
  • 1
    @eugenekgn - 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 (http://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. – adeneo May 27 '14 at 15:56
  • 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'. – Primoz990 Feb 05 '15 at 11:06
  • How do we go about .xlsx instead of .csv (data:attachment/csv,) – Mohit Sehgal Mar 04 '18 at 14:00
  • @MohitSehgal - probably the same way to create files, but xlsx content is probably harder to create with JS. – adeneo Mar 05 '18 at 00:16
  • Anyone having issue with downloading large files with this code? – Razel Soco Apr 11 '18 at 04:46
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();
 }
Manu Sharma
  • 671
  • 7
  • 11
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

Community
  • 1
  • 1
prsnca
  • 236
  • 2
  • 3
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();
    });
  }
malix
  • 3,566
  • 1
  • 31
  • 41
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();
Ricky Boy
  • 723
  • 7
  • 7
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>
Paul Taylor
  • 5,651
  • 5
  • 44
  • 68
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;
    }
}
user2601995
  • 6,463
  • 8
  • 37
  • 41
0

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

Nick Jacobs
  • 579
  • 2
  • 9
  • 29
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"));
    });
Fadi Nouh
  • 324
  • 2
  • 12
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);
  }
Tejashree
  • 750
  • 12
  • 14