Tuesday, October 11, 2011
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:
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
<%= 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