Monday, May 18, 2009

ASP NET MVC - Print excel file using aspx template

My friend met a problem with creating excel report using ASPX as view in ASP.NET MVC. He already built an action to view printer friendly report and wanted to make use of that view for excel output.
To do that, he decide to get string output from the printer friendly action (by combine model + aspx view) and return the output content as binary with a little bit change in content type ("application/vnd.ms-excel")

Unfortunately, ASP NET MVC does not provide any official way to build string content from a View + Model data.

To support him, I read many articles and found a helpful one on Stackoverflow:

http://stackoverflow.com/questions/483091/render-a-view-as-a-string

However, solutions there are not good ways. Why? Actually, they try to use current context to render the view+model. After that, if you try to Redirect or return a FileContentResult or StreamContentResult, you will meet this exception:

Server cannot set content type after HTTP headers have been sent

Or

Server cannot redirect to action after HTTP headers have been sent

So, how to solve this issue? Finally, I found a very stupid but simple solution:

1. Create 2 actions: 1 action for returning Stream content, 1 action to return HTML content from aspx page + model
2. In the returning Stream action, create a HTTP request to the URL of the action that render the HTML for report by System.Net.WebClient => get return HTML
2. Using the return HTML to create the stream + set content type and return to browser

Below is the code

[AcceptVerbs("GET", "POST")]
public ActionResult ViewExcelReport(int? month, int? year)
{
   string excelFileStringContent = DownloadReportHTMLContent(month.Value, 
                                   year.Value);
   byte[] excelFileBytesContent = this.Response.ContentEncoding.GetBytes (excelFileStringContent);
   FileContentResult excelFileContentResult = new FileContentResult(
                                        excelFileBytesContent,
                                       "application/vnd.ms-excel");
   return excelFileContentResult;
}
        
[AcceptVerbs("GET", "POST")]
public ActionResult ViewHTMLReport(int? month, int? year) {
   PrepareReportData(month.Value, year.Value);
   return View();
}

private string DownloadReportHTMLContent(int month, int year)
{
    string linkFormat = Url.Action("ViewHTMLReport", 
                                   "MyReport", 
                                    null, 
                                   "http");
   linkFormat += "?month={0}&year={1}";
   System.Net.WebClient webClient = new System.Net.WebClient();
   webClient.Encoding = Encoding.UTF8;
   return webClient.DownloadString(string.Format(linkFormat, month, year));
}

Is that simple?