18

I am working on how to download CSV file from ASP.NET Web Api from jQuery ajax call. The CSV file is generated dynamically from Web API server based on custom CsvFormatter.

Ajax from jQuery:

   $.ajax({
        type: "GET",
        headers: {
            Accept: "text/csv; charset=utf-8",
        },
        url: "/api/employees",
        success: function (data) {
        }
    });

On the server, the EmployeeCsvFormatter is implemented similar with below article, derived from BufferedMediaTypeFormatter:

http://www.asp.net/web-api/overview/formats-and-model-binding/media-formatters

public class EmployeeCsvFormatter : BufferedMediaTypeFormatter
{
    public EmployeeCsvFormatter()
    {
        SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/csv"));
    }
    ...
}

I also added override method to indicate that I would like to download like normal way to download file (can see the download file in download tab):

 public override void SetDefaultContentHeaders(Type type, 
    HttpContentHeaders headers, MediaTypeHeaderValue mediaType)       
{
    base.SetDefaultContentHeaders(type, headers, mediaType);
    headers.Add("Content-Disposition", "attachment; filename=yourname.csv");
    headers.ContentType =  new MediaTypeHeaderValue("application/octet-stream");
}

But it does not work, the download file does not showed in status bar or in download tab on Chrome, even though from Fiddler, I see it the response seems correct:

HTTP/1.1 200 OK
Server: ASP.NET Development Server/11.0.0.0
Date: Mon, 11 Mar 2013 08:19:35 GMT
X-AspNet-Version: 4.0.30319
Content-Disposition: attachment; filename=yourname.csv
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: application/octet-stream
Content-Length: 26
Connection: Close

1,Cuong,123
1,Trung,123

My method from ApiController:

public EmployeeDto[] Get()
{
    var result = new[]
               {
                   new EmployeeDto() {Id = 1, Name = "Cuong", Address = "123"},
                   new EmployeeDto() {Id = 1, Name = "Trung", Address = "123"},
               };

    return result;
}

It must be wrong somewhere which I have not figured out. How can I make it work by downloading CSV file like normal way?

3
  • 2
    can't download to file with AJAX. Just redirect user to url window.location=url, if server forces download they won't leave current page Commented Mar 11, 2013 at 9:09
  • @charlietfl: how can I set Accept header with this way? Commented Mar 11, 2013 at 9:12
  • what do you expect with accept header? Can't force user to accept anything with download Commented Mar 11, 2013 at 13:29

3 Answers 3

15

jQuery Plugin for Requesting Ajax-like File Downloads does it without Ajax using a simple form submit.

Internally:

jQuery('<form action="'+ url +'" method="'+ (method||'post') +'">'+inputs+'</form>')
    .appendTo('body').submit().remove()

It has an ajax-style interface so from outside you can call it like this

$.download('/api/employees','format=csv');

Another simple approach using:

$('#btnDownload').click(function (e) {
    e.preventDefault();
    window.location = "/api/employees?format=csv";
});

On the server, MediaTypeMappings must be used by adding one more constructor:

    public EmployeeCsvFormatter(MediaTypeMapping mediaTypeMapping)
        : this()
    {
        MediaTypeMappings.Add(mediaTypeMapping);
    }

Then, add this formatter into Web Api configuration:

   configuration.Formatters.Add(new EmployeeCsvFormatter
            (new QueryStringMapping("format", "csv", "text/csv")));
Sign up to request clarification or add additional context in comments.

5 Comments

How do I add Accept header on this way?
Good point. The browser will add one, I fear. You could put the format in the URL, like this $.download('/api/employees/csv',''); or as a parameter: $.download('/api/employees/','format=csv');
Ah, it works out of the box? sometimes life is less complicated than one fears! Will update the answer.
oh, me silly, now I read the queryStringMapper is needed. Please do update with the proper mapper.
if the Get() or Post() accepts parameters, how would you be able to accomodate that using jquery download?
3

As an alternative approach, one can offer the download on click of an anchor () or button with the URL requested set to the API method that responds with the attachment.

In the CsvMediaTypeFormatter (Derived from System.Net.Http.Formatting.MediaTypeFormatter), in addition to mapping the text/csv MIME type as shown in the answers above, I found the following needs to be done:

public override void SetDefaultContentHeaders(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
        {
            base.SetDefaultContentHeaders(type, headers, mediaType);
            headers.ContentDisposition = new ContentDispositionHeaderValue("attachment")
            {
                FileName = string.Concat("data", DateTime.UtcNow.ToString("yyyyMMddhhmmss"), ".csv")
            };

            headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
        }

Note: When writing the stream, set the Content-Length header and remember to Flush the stream. I found that without a call to Flush(), the returned response does not make it back to the client successfully even when the correct content length has been set.

Comments

1

You can also set the request header in beforeSend by modifying the xhr.

    $.ajax({
    url: "/api/employees",
    type: "GET",
    beforeSend: function (xhr) {
        xhr.setRequestHeader("Accept", "text/csv");
    },
    success: function (data) {
            }
    });

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.