Building A Custom ActionResult in MVC 2 To Download Excel Files

Posted by: Malcolm Sheridan , on 3/26/2010, in Category ASP.NET MVC
Views: 70260
Abstract: The following article demonstrates how to create a custom ActionResult to allow users to download Microsoft Excel files using ASP.NET MVC 2.
Building A Custom ActionResult in MVC 2 To Download Excel Files
 
I've seen this question asked several times on the forums, and that is how do you download files when you're working with MVC. Because of URL routing, you shouldn't point the user to your file, you should point them to a URL route and let the controller do the work for you. One way you can do this is to create your own custom ActionResult simply by creating a class that inherits from ActionResult. By the end of this article you'll have an MVC action that look like this:
C#
public ExcelResult GetExcelFile()
{
      return new ExcelResult
                  {
                        FileName = "sample.xls", Path = "~/Content/sample.xls"
                  };
}
VB.NET
Public Function GetExcelFile() As ExcelResult
      Return New ExcelResult
                               "sample.xls", Path = "~/Content/sample.xls"
                               FileName = "sample.xls", Path
End Function
And the end result to the user will look like this. They'll have the option to either save or open the file:

 

 
OpeningTests
Ok let's get started. Create a new MVC web application. For this sample I'm using MVC2. If you haven't got it you can download it from here. I want to create a new custom class that will change the content-disposition of the response. This will cause the pop-up to appear. I've called my class ExcelResult. The two things you need to do is inherit from ActionResult and override the ExecuteResult method:
C#
public class ExcelResult : ActionResult
{
      public string FileName { get; set; }
      public string Path { get;set; }
 
      public override void ExecuteResult(ControllerContext context)
      {
            context.HttpContext.Response.Buffer = true;
            context.HttpContext.Response.Clear();
            context.HttpContext.Response.AddHeader("content-disposition", "attachment; filename=" + FileName);
            context.HttpContext.Response.ContentType = "application/vnd.ms-excel";
            context.HttpContext.Response.WriteFile(context.HttpContext.Server.MapPath(Path));   
      }
}
VB.NET (Converted code)
Public Class ExcelResult
      Inherits ActionResult
      Private privateFileName As String
      Public Property FileName() As String
            Get
                  Return privateFileName
            End Get
            Set(ByVal value As String)
                  privateFileName = value
            End Set
      End Property
       Private privatePath As String
       Public Property Path() As String
             Get
                   Return privatePath
             End Get
             Set(ByVal value As String)
                   privatePath = value
             End Set
       End Property
 
       Public Overrides Sub ExecuteResult(ByVal context As ControllerContext)
             context.HttpContext.Response.Buffer = True
                  context.HttpContext.Response.Clear()
                  context.HttpContext.Response.AddHeader("content-disposition", "attachment; filename=" & FileName)
                  context.HttpContext.Response.ContentType = "application/vnd.ms-excel"
                  context.HttpContext.Response.WriteFile(context.HttpContext.Server.MapPath(Path))
       End Sub
End Class
 
The code above basically means that when the return type of an action is ExcelResult, it will change the content-disposition and write the contents of the file to the HTTP response output stream. To see this code in action, I've created an MVC action and set the return type to ExcelResult:
C#
public ExcelResult GetExcelFile()
{
      return new ExcelResult
                  {
                        FileName = "sample.xls", Path = "~/Content/sample.xls"
                  };
}
VB.NET (Converted code)
Public Function GetExcelFile() As ExcelResult
      Return New ExcelResult
                               "sample.xls", Path = "~/Content/sample.xls"
                               FileName = "sample.xls", Path
End Function
 
In my view I have created an ActionLink to call this action:
 
<%= Html.ActionLink("Download Excel", "GetExcelFile", "Home")%>
 
If you ran the project now and click on the Download Excel link, the open file dialog will appear asking if you want to save or open the file. 
 
This will work for any type of file. All you need to change is the ContentType of the response object. You can find the full list of content types here.
 

This is one way of implementing this functionality. As with anything there are multiple ways of doing this, but it works for me. The entire source code of this article can be downloaded over here

Give a +1 to this article if you think it was well written. Thanks!
Recommended Articles
Malcolm Sheridan is a Microsoft awarded MVP in ASP.NET, a Telerik Insider and a regular presenter at conferences and user groups throughout Australia and New Zealand. Being an ASP.NET guy, his focus is on web technologies and has been for the past 10 years. He loves working with ASP.NET MVC these days and also loves getting his hands dirty with jQuery and JavaScript. He also writes technical articles on ASP.NET for SitePoint and other various websites. Follow him on twitter @malcolmsheridan


Page copy protected against web site content infringement by Copyscape


User Feedback
Comment posted by Paul Blamire on Friday, March 26, 2010 1:40 PM
Hi there,

Just pointing out that there is a filepathresult built in for this kind of task, unless I'm missing a subtlety of your implementation.
Useful article for a lot of people new to MVC though who maybe don't understand what goes on under the hood.

Kind regards,
Paul

http://msdn.microsoft.com/en-us/library/system.web.mvc.filepathresult.aspx

FileResult
   FileContentResult
   FilePathResult
   FileStreamResult
Comment posted by shawty on Friday, March 26, 2010 3:34 PM
Nice tute Mal,

Intresting stuff.
Comment posted by Malcolm Sheridan on Friday, March 26, 2010 5:35 PM
@Paul
Yes you're right Paul, there's that already.  This hopefully explained to people how to build a custom result when they need it.
Comment posted by Sujith on Wednesday, July 21, 2010 12:00 PM
It looks great;

How to change the data in excel file dynamically?? I dont want excel file to be static. I need to change my headers in excel file resulting fron the model?
Comment posted by Malcolm Sheridan on Tuesday, July 27, 2010 10:25 PM
@Sujith  
You'd need to create the excel file yourself for this.  Try the below site for examples:

http://tinyurl.com/378j2vc
Comment posted by Mark Vandersteen on Tuesday, August 24, 2010 1:58 AM
Hi Mal,

Whats the go with large record sets will it work for +65000 rows. Which is an issue with generating large csv files in the past in asp.net
Comment posted by Malcolm Sheridan on Thursday, August 26, 2010 10:31 PM
@mark
Try it with the code as I haven't tried it with that amount of data.
Comment posted by Matt Frear on Friday, January 7, 2011 8:21 AM
Very helpful article, thanks. I was already using the built-in Controller.File to return a FilePathResult, but I used your code to write my own PDFResult class.

By writing my own class I was able to remove the content-disposition header, as that header was causing an error with IE6 users and Acrobat Reader.

Thanks
Comment posted by A on Friday, May 27, 2011 1:57 AM
aaa
<br>

Post your comment
Name:  
E-mail: (Will not be displayed)
Comment:
Insert Cancel