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`
Why did the content
received in the angularjs contain characters
already instead of a byte of array?
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?
What is the proper way of generating a csv if what I am doing is
wrong? :|
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.
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.