23

I need to get a csv file from web api controller. I can not get "Save As" dialog to show up. Only text output shows up on the page. I tried both, calling Export from jquery and also plain old html

Controller:

[System.Web.Http.HttpGet]
public HttpResponseMessage Export()
{
    StringBuilder sb = new StringBuilder();
    IEnumerable<CustomerDiscount> list = this.subscriberRepository.GetSubscribers();

    foreach (CustomerDiscount item in list)
    {
        sb.AppendFormat(
            "{0};{1};{2};",
            item.CustomerName,
            item.CustomerNumber,
            Environment.NewLine);
    }

    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write(sb.ToString());
    writer.Flush();
    stream.Position = 0;

    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentType =
        new MediaTypeHeaderValue("text/csv");
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "Export.csv" };
    return result;
}

EDIT: added this line:

result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "Export.csv" };

still doesn't work

I call it like this:

<a id="export" href="/Relay/Billing/Export" class="btn btn-primary">Export</a>

and also tried it like this:

$("#export").click(function () {
    $.post("/Relay/Billing/Export", { type: $("#discountType").val() })
      .done(function (data) {
      });
});

Still no Save As box

ShaneKm
  • 20,823
  • 43
  • 167
  • 296
  • possible duplicate of [How to set downloading file name in ASP.NET MVC Web API](http://stackoverflow.com/questions/12145390/how-to-set-downloading-file-name-in-asp-net-mvc-web-api) – CodeCaster Apr 29 '15 at 15:56
  • 1
    I'm a little confused. A typical *web-api* or *restful-services* return JSON (or some type of serialization). I assume your consumer is not a web browser? – Erik Philips Apr 29 '15 at 15:58
  • Try adding this line before returning result: `result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "some.csv" };` – lbrahim Apr 29 '15 at 18:10
  • If you want to call a method that returns an CSV file you'll be much better off using [a standard MVC controller](http://stackoverflow.com/questions/28994557/download-server-generated-csv-with-asp-net-mvc-and-jquery-ajax-request). Web API is used for returning data (typically) where as controllers return content. I'd say a CSV file (especially the way your calling it) is content not data. – Liam Aug 08 '16 at 12:20

2 Answers2

46

I do not know whether this is proper protocol but this is how I did it before. So you have a web page from which you will invoke an API that will response with the file and it should be handled by browser's save as dialog.

The HTML:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title></title>

    <script type="text/javascript">
        function save() {
            window.open('http://localhost:45719/api/home?id=12', '_blank', '');
        }
    </script>
</head>
<body>
    <a class="btn btn-primary" onclick="save()">Export</a>
</body>
</html>

The action:

public HttpResponseMessage Get(int id)
{
    MemoryStream stream = new MemoryStream();
    StreamWriter writer = new StreamWriter(stream);
    writer.Write("Hello, World!");
    writer.Flush();
    stream.Position = 0;

    HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
    result.Content = new StreamContent(stream);
    result.Content.Headers.ContentType = new MediaTypeHeaderValue("text/csv");
    result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment") { FileName = "Export.csv" };
    return result;
}

This is working for me in both Chrome and Firefox latest.

lbrahim
  • 3,710
  • 12
  • 57
  • 95
  • it's opening a new browser window but not save as dialog – ShaneKm Apr 29 '15 at 18:49
  • @ShaneKm Did you check whether any of your browser setting is preventing this? Because as mentioned above I am getting it in Chrome and Firefox like any Save As. – lbrahim Apr 29 '15 at 20:12
  • 2
    this works fine when it's MVC controller. but not for web api – ShaneKm Apr 29 '15 at 21:09
  • Something like this will work but it is *not* proper. Note that in this situation you are writing entirely into a MemoryStream, not the response stream. Consider a situation where you want to return the first 1million primes comma separated. You don't actually need to allocate memory for all of them, instead you should be able to write them to the response stream one at a time and never have to allocate more memory than just whatever it takes to find the *next* one. This technique essentially uses streams as a single buffer, but not as streams. – George Mauer Mar 30 '17 at 20:44
  • 6
    Building on what @GeorgeMauer stated, the whole first half of this action in this answer can be replaced with simply `result.Content = new StringContent("Hello, World!");` – Grinn Apr 05 '17 at 13:31
0

You cannot have a download dialog if you call your API from an AJAX call. Either make it a regular link (for APIs using GET) or, if you need it to be a POST, have it to post to a new window using a regular form with target="_blank".

So, either <a href="/Relay/Billing/Export?type=...">click here</a>, or

<form method="post" target="_blank" action="/Relay/Billing/Export">
<input id="discountType" name="type"/>
<input type="submit">Export</input>
</form>
ulu
  • 5,872
  • 4
  • 42
  • 51