4

I am trying to generate a CSV file from my web api and receive that file through angularjs. I have an API controller like below:

 [HttpPost]
    public HttpResponseMessage GenerateCSV(FieldParameters fieldParams)
    {
        var output = new byte[] { };
        if (fieldParams!= null)
        {
            using (var stream = new MemoryStream())
            {
                this.Serialize(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 = "Fields.csv"
        };
        return result;
    }

In my angularjs, i have this:

$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) {
            console.log(content);

            // only creates a csv file with "[object Object]" written in it
            var hiddenElement = document.createElement('a');
            hiddenElement.href = 'data:text/csv;charset=utf-8,\uFEFF' + encodeURI(content.Parameters);
            hiddenElement.target = '_blank';
            hiddenElement.download = 'myFile.csv';
            hiddenElement.click();
        });
    };

Lets say for example, in my API controller, the content of response is

output

{byte[152]}

[0]: 83

[1]: 101

[2]: 44

[3]: 67

[4]: 10

When I receive this in angularjs and I put the value of content in the console log (chrome), this is what I get:

{Parameters: Array[1], $promise: Object, $resolved: true, $get: function, $save: function…}

0:"S"

1: "e"

2: ","

3: "C"

4: "↵"

$promise: object

$resolved: true`

  1. Why did the content received in the angularjs contain characters already instead of a byte of array?

  2. How can I control the content in such a way that I will only use the csv related data and remove $promise and $resolved? Why are they included in the first place? How to remove them?

  3. What is the proper way of generating a csv if what I am doing is wrong? :|

2 Answers 2

5

Forgot to update this, but i now found a way to solve this:

There will be two API's, one (POST) will remember the data to be used in the processing and another one (GET) which will dispense the file.

POST:

    [HttpPost]
    public async Task<HttpResponseMessage> BuildFile(FileParameters fileParams)
    {
        var guid = Guid.NewGuid().ToString();
        if (fileParams!= null)
        {
            await Task.Run(() => FileContents.Add(guid, fileParams));
            return this.Request.CreateResponse(HttpStatusCode.OK, new { Value = guid });
        }
        return this.Request.CreateErrorResponse(HttpStatusCode.BadRequest, "Invalid data");
    }

In AngularJs, remember the guid returned and pass this to another api:

 location.href = '/api/file/generatefile' + '?guid=' + generatedGuidFromAPI + '&reportName=' + $scope.reportName;

And here is the generatefile API controller in MVC:

GET:

  [HttpGet]
    public async Task<HttpResponseMessage> GenerateFile(string guid, string reportName)
    {
        byte[] output = null;
        if (FileContents.ContainsKey(guid))
        {
            await Task.Run(() =>
            {
                using (var stream = new MemoryStream())
                {
                    this.CreateFile(FileContents[guid], stream);
                    stream.Flush();
                    output = stream.ToArray();
                }
            });
        }

        FileContents.Remove(guid);
        if (output != null)
        {
            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 = reportName + ".csv"
            };
            return result;
        }

        return this.Request.CreateErrorResponse(HttpStatusCode.NoContent, "No record found");
    }

using location.href will cause the browser to automatically download the file, asking you whether to save it or not.

Sign up to request clarification or add additional context in comments.

1 Comment

but this uses server memory... any other answers??
0

Here's how I do it: (tested in chrome)

    // WebAPI controller action
    public IHttpActionResult Get(string rpt, DateTime date)
    {
        List<DailyMIReportViewModel> list = new List<DailyMIReportViewModel>();

        // Do some stuff to generate list of items

        // Download Requested
        if (rpt == "dailymidl")
        {
            // Create byte array of csv
            byte[] csvData = WriteCsvWithHeaderToMemory(list);
            // create FileContentResult of cdv byte array
            FileContentResult result = new FileContentResult(csvData, "application/octet-stream");
            // set filename in FileContentResult
            result.FileDownloadName = "Report.csv";

            return Ok(result);
        }
        // Data Requested
        return Ok(list);

        // Client-side angularjs
        // Called on button click
        $scope.generateMIDownload = function (forDate) {
            // Using $resource to return promise with FileContentResult payload
            reportsRepository.dailymidl(forDate).$promise.then(
            function (data) {
                //ok
                // NOTE: the base64 part is what got it working

                var dataUrl = 'data:application/octet-stream;base64,' + data.FileContents
                var link = document.createElement('a');
                angular.element(link)
                  .attr('href', dataUrl)
                  .attr('download', data.FileDownloadName)
                  .attr('target','_blank')
                link.click();
            },
            function (response) {
                //not ok
            });
        }

        // Reports Repository (for ref)
        angular.module('msgnr').factory('reportsRepository', function ($resource) {
            return {
                dailymidl: function (date) {
                    return $resource('/api/Report/', { rpt: 'dailymidl', date: date, toDate: date }).get();
            }
        }
    });

Incase it helps anyone else.

2 Comments

You are doing an async server call using $resource and getting the csv/excel data in the successfunction and then when the anchor's click is invoked, it DOES NOT make a server call at that point, it just shows the file???? will it work without the target=_blank since popup blockers may fuss about that? I have not used $resource before and our angular infrustructure is abstracted away, but I"m sure I could get to it if I wanted to. Is there any length restriction on this dataUrl? Can long ones slow down the browser? any code you can show regarding the reportsRepository
You are correct re the async call to the server using $resource and the anchors click does not make another call it just presents the file. It works fine without target=_blank. There are length restrictions on a data url but its determined by the browser. I believe Chrome is 2Mb. I've edited my prev response to include the reportsRepository

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.