Writing A Custom File Download Action Result For ASP.NET MVC

UPDATE: I’ve updated the sample to include a new lambda based action result. This also fixes an issue with the original download in which I included the wrong assembly.

The April CodePlex source drop of ASP.NET MVC introduces the concept of returning an ActionResult instance from action methods. ScottGu wrote about this change on his blog.

In this post, I’ll walk through building a custom action result for downloading files. As you’ll see, they are extremely easy to build. Let’s start at the end and see what the end-user behavior of this new result will be.

Here's a page that contains a link to an action method named Download. This action method returns this new DownloadResult action result.

File Download HomePage

Clicking on the link then pops up this dialog, prompting you to download and save the file.

File Download

The code for this action is pretty simple.

public ActionResult Download() 
{
  return new DownloadResult 
    { VirtualPath="~/content/site.css", FileDownloadName = "TheSiteCss.css" };
}

Notice that you just need to give the result two pieces of information, the virtual path to the file to send to the browser and the default filename to save the file as on the browser.

The virtual path is set via VirtualPath property (surprise surprise!). Note that I could have chosen to make this parameter accept the full file path instead of a virtual path, but I didn’t want to force users of this class to fake out a Server.MapPath call in a unit test. In any case, the change is trivial for those who prefer that approach. I might add overloads that accept a Stream, etc...

The file download name is set via the FileDownloadName property. Notice that this is the filename that the user is prompted with.

If the FileDownloadName property is set, the ExecuteResult method makes sure to add the correct content-disposition header which causes the browser to prompt the user to save the file.

For those familiar with Design Patterns, action results follow the pattern commonly known as the Command Pattern. An action method returns an instance that embodies an command that the framework needs to perform next. This provides a means for delaying the execution of framework/pipeline code until after your action method is complete, rather than from within your action method, which makes unit testing much nicer.

Speaking of unit tests, here’s the unit test for that download action method I wrote. As you can see, it is quite simple.

[TestMethod]
public void DownloadActionSendsCorrectFile() {
  var controller = new HomeController();

  var result = controller.Download() as DownloadResult;

  Assert.AreEqual("TheSiteCss.css", result.FileDownloadName);
  Assert.AreEqual("~/content/site.css", result.VirtualPath);
}

Here’s the code for the DownloadResult class. This is the class that does all the work (not that there is much work to do). I do have unit tests of this class in the included source code which demonstrate how to unit test a custom action result.

public class DownloadResult : ActionResult {

  public DownloadResult() {
  }

  public DownloadResult(string virtualPath) {
    this.VirtualPath = virtualPath;
  }

  public string VirtualPath {
    get;
    set;
  }

  public string FileDownloadName {
    get;
    set;
  }

  public override void ExecuteResult(ControllerContext context) {
    if (!String.IsNullOrEmpty(FileDownloadName)) {
      context.HttpContext.Response.AddHeader("content-disposition", 
        "attachment; filename=" + this.FileDownloadName)
    }

    string filePath = context.HttpContext.Server.MapPath(this.VirtualPath);
    context.HttpContext.Response.TransmitFile(filePath);
    }
}

To see all the code, download the source. It includes a simple demo web app and all unit tests.

Technorati Tags: ,
[ad] Free Bug Tracking & Project Management Software Axosoft’s OnTime 2007 allows software development teams to collaborate on software projects by tracking everything from defects to enhancements to helpdesk incidents in one easy-to-use database driven by an intuitive Windows, Web or VS.NET Integrated UI. Get a Free Single-User License ($200 Value!)

What others have said

Requesting Gravatar... Jonathan Carter May 10, 2008 5:31 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Nice example dude. The ActionResult possibilities are endless!
Requesting Gravatar... Steve May 10, 2008 8:15 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
I haven't downloaded these bits yet, I'm using preview 2.

So I wonder - how is this 'return ActionResult' going to effect things where in several instances I use a response.write from an ajax call, or where I create an excel spreadsheet and push it back to the client with response.end, etc...

Will I have to write a unique actionresult for each of these?

Requesting Gravatar... Steve May 10, 2008 9:13 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
I get this error running the code:

Could not load file or assembly 'System.Web.Mvc' or one of its dependencies. Strong name signature could not be verified. The assembly may have been tampered with, or it was delay signed but not fully signed with the correct private key. (Exception from HRESULT: 0x80131045)
Requesting Gravatar... Zack Owens May 10, 2008 9:37 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
ActionResult is pretty addicting. It fills the void where an ActionFilterAttribute could not go. Whoever thought of it deserves a raise ;)

This sample saves some time for sure! Thanks Phil!!!
Requesting Gravatar... caoge May 11, 2008 2:50 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
我来自中国(I'm from China).Thanks for your sample```
Requesting Gravatar... Lamin Barrow May 11, 2008 4:11 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Hi Phil,
I know it is typical to write a different public controller method for each view you want to render. I have noticed you have hard coded the the DownloadResult properties in your ActionResult method and i have been wondering ... do i have to write a separate render method to handle lets say two file download links on my page using your approach?
Requesting Gravatar... Haacked May 11, 2008 8:58 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
@Steve sorry about that, I included the wrong assembly. I've updated the sample to include a new DelegatingActionResult which allows you to pass a delegate in. That way, you don't have to write a custom action result for everything.

@Lamin I could've made the Download action method parameterized. That way there would've been no need to hardcode anything.
Requesting Gravatar... Steve May 11, 2008 1:06 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
I must be missing something - still get that same error ?

I'm saving this code though, will try it with Preview 3 - great stuff!
Requesting Gravatar... Shiju Varghese May 11, 2008 9:17 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Hi Phil,
Nice example it shows the opportunities of ActionResult.

Thanks,
Shiju
Requesting Gravatar... json May 12, 2008 12:09 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
And all this is so much better than using an ashx-handler, because...???? I'm sorry but all this just adds code, and code added means more work later - I strive to reduce the "more work later" part of programming.
Requesting Gravatar... Anastasiosyal May 12, 2008 3:28 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
ActionResult seems simple yet powerful.

Just a note on the "content-disposition" header: I'd wrap the FileDownloadName in quotes. The reason being is that Firefox will truncate the filename to the first space if the file name includes spaces and is not quoted (as per spec).

So you would have:

context.HttpContext.Response.AddHeader("content-disposition",
"attachment; filename=\"" + this.FileDownloadName + "\"")
Requesting Gravatar... Maarten Balliauw May 13, 2008 6:32 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Nice example! I actually did something similar as an alternative to writing ASP.NET HttpHandlers for rendering images. (http://blog.maartenballiauw.be/post/2008/05/ASPNET-MVC-custom-ActionResult.aspx)

What do you have to say?

(will show your gravatar)
Please add 5 and 2 and type the answer here: