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

NEW UPDATE: There is no longer need for this custom ActionResult because ASP.NET MVC now includes one in the box.

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);
    }
}

I removed the download since this code is no longer needed nor relevant.

Technorati Tags: ,

What others have said

Requesting Gravatar... Jonathan Carter May 10, 2008 9:31 AM
# 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 12: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 1: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 1: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 10, 2008 6:50 PM
# 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 10, 2008 8:11 PM
# 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 12: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 5:06 AM
# 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 1: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 11, 2008 4:09 PM
# 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 11, 2008 7:28 PM
# 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 12, 2008 10:32 PM
# 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)
Requesting Gravatar... ganesh May 22, 2008 6:37 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
pls tell the code
Requesting Gravatar... Jason Dec 16, 2008 3:46 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Excellent, just the answer I was looking for. I was expecting this to be a lot more complicated - I can finally get to bed!
Requesting Gravatar... Jason Dec 16, 2008 8:05 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Just when I though I was going to get an early night...seems I needed to add 'Content-type' to the headers as well otherwise Google Chrome and Firefox kept adding ".htm" on the the end of my file names! Seems to be part of a long-standing browser argument over whether the browser should pay much attention to 'Content-disposition'. Anyhow, this addition solved the issue for me:

context.HttpContext.Response.AddHeader("Content-type", "application/force-download");

Don't know if anyone else has come across this? Now I'm going to be all tired and grumpy tomorrow :(
Requesting Gravatar... Avi Feb 27, 2009 8:02 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
How to download files from web server as well as from remote file server using asp.net and C#. Visit following post:

www.etechplanet.com/.../...using-aspnet-and-C.aspx
Requesting Gravatar... Edmund Herbert Feb 05, 2010 4:02 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Hi I am trying to down load sample above, get error not valid archive can you email me source, thanks edmund
Requesting Gravatar... KarlZ Feb 15, 2010 9:16 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
I too am getting the "not a valid archive" error when I try to download the .zip file. Is there an issue with that file? Or the way that IE 8 downloads it?
Thanks,
Karl
Requesting Gravatar... WGAO Feb 24, 2010 6:10 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Does anyone know how to use httpcontext.response.transmitfile or any other httpcontext function to download a file from a network driver instead of local driver? When I tried to use context.Response.TransmitFile(DownloadPath) to download files, the DownloadPath has to be local driver. If it's a network driver, the download failed. Thanks in advance...
Requesting Gravatar... Hrishikesh Ratnakar Kulkarni Mar 04, 2010 10:52 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Hi, I just want to ask that how can I write a code to download a file in asp.net at Client side(The Client will be downloading the Txt file).Just like "Question Bank" Uploaded by professors can be downloaded by a student .Actually we are developing a "Students networking site"for our final year project.....

pls let me know how can i achieve it before our last date of our submission comes it is the last module remaining
Requesting Gravatar... Efwoiar Jul 14, 2010 10:43 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Have you gotten the code?
Would you like to give me one?
Thanks!
Requesting Gravatar... LW Sep 17, 2010 12:49 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Hi, can you send me the code?
Requesting Gravatar... csac Oct 24, 2010 4:13 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
vrev
Requesting Gravatar... roche plando Nov 11, 2010 10:40 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
pls send me the code. i tried clicking the link for the source code but it fails.
Requesting Gravatar... Patrick barry Nov 14, 2010 7:40 PM
# Creating a zip then download it. using ICSharpCode.SharpZipLib.Zip
public class DownLoadZipFile : ActionResult
{

public string FileDownloadName { get; set; }

public List<string> ZipFileList { get; set; }

public DownLoadZipFile()
{
}

public DownLoadZipFile(string fileDownloadName, List<string> zipFileList)
{
FileDownloadName = fileDownloadName;
ZipFileList = zipFileList;
}



public override void ExecuteResult(ControllerContext context)
{
if (!String.IsNullOrEmpty(FileDownloadName))
{
var response = context.HttpContext.Response;
response.Clear();
response.AddHeader("content-disposition", "attachment; filename=" + this.FileDownloadName);
response.ContentType = "application/zip";
//response.ContentType = "application/octet-stream";
response.CacheControl = "Private";
response.Cache.SetExpires(DateTime.Now.AddMinutes(3));
}
DownloadZipToBrowser(context);
}

public void DownloadZipToBrowser(ControllerContext context)
{
ZipOutputStream zipOutputStream = null;
var response = context.HttpContext.Response;
try
{
byte[] buffer = new byte[4096];
zipOutputStream = new ZipOutputStream(response.OutputStream);
zipOutputStream.SetLevel(3); //0-9, 9 being the highest level of compression
foreach (string fileName in ZipFileList)
{
Stream fs = File.OpenRead(fileName);
ZipEntry entry = new ZipEntry(Path.GetFileName(fileName));
entry.Size = fs.Length;
zipOutputStream.PutNextEntry(entry);
int count = fs.Read(buffer, 0, buffer.Length);
while (count > 0)
{
zipOutputStream.Write(buffer, 0, count);
count = fs.Read(buffer, 0, buffer.Length);
if (!response.IsClientConnected)
{
break;
}
response.Flush();
}
fs.Close();
}

}
catch (Exception ex)
{
Logging.WriteError(ex);
}
finally
{
if (zipOutputStream != null)
zipOutputStream.Close();
response.Flush();
response.End();
}
}
}
Requesting Gravatar... femi Jan 03, 2011 3:33 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
the sample code download is not working...
Requesting Gravatar... mim May 17, 2011 2:40 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Hi, The link haacked.com/.../FileNotFound.aspx is not working. I am not able to download the demo
Requesting Gravatar... pujie May 24, 2011 6:22 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
great Tutorial, thanks a lot. It works !!!
Requesting Gravatar... David Aug 30, 2011 1:11 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
Now it's integrated in asp.net MVC.
Just use :

return File(FileStreamOrPath, ContentType, fileDownloadName);
Requesting Gravatar... Jeff Circeo Oct 01, 2011 10:07 PM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
You could expand and improve your code by adding client side caching, below is an article in ASP.NET and Delphi that does part of this (I haven't tried it)... porting this code looks simple but who knows :)

http://edn.embarcadero.com/article/38123
Requesting Gravatar... geo Dec 09, 2011 2:18 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
File(FileStreamOrPath, ContentType, fileDownloadName) always specify "Content-Disposition: attachment; filename=fileDownloadName" in the header.
What if I need to specify: "Content-Disposition: inline; filename=fileDownloadName" in the header?
Requesting Gravatar... haacked Dec 09, 2011 10:15 AM
# re: Writing A Custom File Download Action Result For ASP.NET MVC
@geo the current helpers don't support that. You could return a custom FileResult and override ExecuteResult to do what you want.

What do you have to say?

(will show your gravatar)
Please add 6 and 7 and type the answer here: