5

I have the following code:

Response.ClearContent();
Response.AddHeader( "Content-type", "application/vnd.ms-excel");
Response.AddHeader("content-disposition", "attachment;filename=test.xls");
Response.ContentType = "application/excel";
var swr = new StringWriter();
var tw = new HtmlTextWriter(swr);
grd.RenderControl(tw);
Response.Write(swr.ToString());
Response.Flush();
Response.End();
tw.Close();
swr.Close();

This action is triggered from the following bit of jquery code:

   <img src="../../../../Content/images/Excel-icon.png" 
      onclick = "alert ($('#Filter').serialize());
                    $.ajax({
                         type: 'POST',
                         url: '<%=Url.Action( "Excel") %>',
                         data: $('#Filter').serialize(),
                         success : function (data, textStatus, jqXHR) 
                              { alert (data);},
                         error: function (jqXHR, textStatus, errorThrown)
                              { alert (textStatus + ' ' + errorThrown);}
                         });" />

I have confirmed with fiddler that the headers have the expected values. I can also see the data in fiddler using the Web View.

Also, when I show the contents of data from the success function, it has the raw html for the table that I am trying to export to excel.

Thanks in advance for your help.

Nick Harrison
  • 921
  • 9
  • 17
  • 3
    This has nothing to do with your problem, but you really shouldn't do inline click events like that. It makes me want to dig out my eyeballs. – Cᴏʀʏ Jan 11 '12 at 18:31
  • So your problem is that your browser isn't offering to save this but displaying it instead? Which browser? And you really ought not be writing an HTML table as a fake Excel document - Excel will cope, but other programs e.g. the free Excel viewer won't. You can use [one of these libraries](http://stackoverflow.com/questions/151005/create-excel-xls-and-xlsx-file-from-c-sharp) to generate a real excel file easily enough. – Rup Jan 11 '12 at 18:32
  • ... and why are you writing your own content type header and also using the ASP.NET mechanism? Also 'Content-Disposition' usually has a capital 'C' and 'D', but [RFC2616](http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html) says that the field names are treated case-insensitively so that's not the problem after all. – Rup Jan 11 '12 at 18:34
  • Nothing is displayed back to the browser. The only way that I know I get data back is fiddler and then I see it when I display the data argument to the success function. I have tried this in both chrome and IE. – Nick Harrison Jan 11 '12 at 18:35
  • 1
    D'oh, yes, Darin's right - it's because you're asking jQuery to consume the download as an AJAX result instead. – Rup Jan 11 '12 at 18:36

2 Answers2

5

Oh, no, you cannot use AJAX to download files. The AJAX call is executed, the file is streamed to the client, your success callback is executed and passed the contents of the file that's where you will get stuck. For obvious security reasons you cannot do much with this data. You cannot save it to the client computer. You cannot show a prompt to Save As at this stage.

So remove your javascript AJAX call and invoke your controller action using a normal GET or POST request.

Darin Dimitrov
  • 1,023,142
  • 271
  • 3,287
  • 2,928
3

I would suggest not putting that code directly in an Action method -- that's not a very clean way to use MVC, and you certainly can't trigger a file download from an AJAX request.

You could derive a new action result -- call it ExcelResult -- from the FileResult class, an override its ExecuteResult method to force the content into the Response.

The button click should simply hit the action through a link (navigate to it). The response from the server and the browser will work together to force the file at a user without browsing to a new page.

Example:

public class ExcelResult : FileResult
{
    public override void ExecuteResult(ControllerContext context)
    {
        string file;

        using (var swr = new StringWriter())
        {
            using (var tw = new HtmlTextWriter(swr))
            {
                // Not sure where your grd object comes from
                grd.RenderControl(tw);
                file = swr.ToString();
            }
        }

        var response = context.HttpContext.Response;

        response.Buffer = true;
        response.Clear();
        response.ClearHeaders();
        response.ContentType = "application/excel";
        response.CacheControl = "public";
        response.AddHeader("Pragma", "Public");
        response.AddHeader("Expires", "0");
        response.AddHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
        response.AddHeader("Content-Description", "describe your file");
        response.AddHeader("Content-Disposition","attachment; filename=excel.xls");
        response.Write(file);
        response.Flush();
        response.End();
    }

}

And your Action is simply:

public ExcelResult Excel()
{
    return new ExcelResult(/* whatever you need to pass */);
}
Cᴏʀʏ
  • 105,112
  • 20
  • 162
  • 194