ASP.NET MVC Uploading and Downloading Files

If you come to ASP.NET MVC from a purely ASP.NET Web Forms background, one of the first things you are likely to notice is that all those nice easy Server Controls have disappeared. One of those is the FileUpload, and its absence seems to cause a few problems. This article looks at how to upload files to the server in an MVC world, and how to get them back from the server to the user again.

Entity Framework: If you are looking for information on saving files or their locations to a database using Entity Framework in an ASP.NET MVC Application, please check out my more recent article on the topic: ASP.NET MVC 5 with EF 6 - Working With Files.

In Web Forms, when you drag a FileUpload control on to the designer, something happens when the page is rendered which you probably don't notice. The resulting html form that wraps the entire page is decorated with an extra attribute: enctype="multipart/form-data". The FileUpload itself is rendered as an html input type=file. Within an MVC View, there are a number of ways to set this up. The first is with HTML:

Notice that the <form> tag includes the enctype attribute, and method attribute of post. This is needed because the form by default will be submitted via the HTTP get method. The following approach, using the Html.BeginForm() extension method renders the exact same html when the page is requested:

Notice the name attribute of the <input type="file"> element. We'll come back to that shortly. In the meantime, the resulting page should look rather blandly like this:

OK. So we can now browse to a local file and click the submit button to upload it to the web server. What is needed next is some way to manage the file on the server. When using a FileUpload control, you generally see code that checks to see if a file actually has been uploaded, using the FileUpload.HasFile() method. There isn't the same convenience when you are working with MVC, as you are much closer to the raw HTTP. However, a quick extension method can take care of that:

When you look at Controller class, you see that it has a Request object as a property, which is of type HttpRequestBase. This is a wrapper for an HTTP request, and exposes many properties, including a Files collection (actually a collection of type HttpFileCollectionBase). Each item within the collection is of type HttpPostedFileBase. The extension method checks the item to make sure there's one there, and that it has some content. Essentially, this is identical to the way that the FileUpload.HasFile() method works.

Multiple File Uploading

You might already be ahead of me at this point, and wondering how you might make use of the fact that Request.Files is a collection. That suggests that it can accommodate more than one file, and indeed, it can. If you change the original View to this:

The code in the controller Action already checks all file uploads, so no changes are needed for it to work with multiple file uploads. Notice that each input has a different name attribute. If you need to reference them individually, that is what you use. For example, to reference the third one, you would get at it using Request.Files["FileUpload3"].

Saving to a Database

Before you scream "Separation of Concerns!" at me, the next piece of code is purely illustrative. It features ADO.NET within a controller action. As we all know, this is simply not done. Database access code belongs to your data access layer somewhere inside the Model. However, the code should give people a starting point if they want to save uploaded files to a database. First of all, I have created a database (FileTest) and added a table: FileStore:

The revised code still loops through as many uploads as are on the web page, and checks each one to see if it has file. From there, it extracts 3 pieces of information: the file name, the mime type (what type of file it is) and the actual binary data that is streamed as part of the HTTP Request. The binary data is transferred to a byte array, which is what is stored in the image datatype field in the database. The mime type and name are important for when the file is returned to a user. We shall look at that part next.

Serving Files to the User

How you deliver files back to users will depend on how you have stored them primarily. If you have them stored in a database, you will usually stream the file back to the user. If they are stored on a disk, you can either simply provide a hyperlink to them, or again, stream them. Whenever you need to stream a file to the browser, you will use one of the overloads of the File() method (instead of the View() method that has been used so far in the preceding examples). There are 3 different return types of the File() method: a FilePathResult, FileContentResult and a FileStreamResult. The first streams a file directly from disk; the second sends a byte array back to the client, while the third sends the contents of a Stream object which has been generated and opened.

If you remember, when saving the uploaded files into a database, we sent a byte array to the FileContent field. When we need to get that back, it will be as a byte array again. If you have been keeping up, this means that we can use one of the two overloads of File() that return a FileContentResult. If you want the name of the file to be meaningful, you will use the overload that takes 3 arguments - the byte array, the mime type and the file name:

So what's the difference between FilePathResult and FileStreamResult and which one should you use? The main difference is that FilePathResult uses HttpResponse.TransmitFile to write the file to the http output. This method doesn't buffer the file in memory on the server, so it should be a better option for sending larger files. It's very much like the difference between using a DataReader or a DataSet. On the other hand, you might need to check the server you are hosting your site on, as a bug in TransmitFile may lead to partial delivery of files, or even complete failure. FileStreamResult is a great way of, for example, returning Chart images generated in memory by the ASP.NET Chart Controls without having to save them to disk.

You might also like...

21 Comments

23 December 2009 09:57 - CareySon

hi,mike.great post.very helpful

07 January 2010 10:02 - Stan4th

Hi,thanks for this - I have a partial which is acting as the form which I am using jquery/ajax to put into a section of the current form.I already have the Html.BeginForm construct for this but when I try to add this attribute it throws a runtime.Do you know what is causing this?Cheers

08 January 2010 22:54 - Mike

@Stan4th

I couldn't even begin to guess since you don't say what the runtime error is. My suggestion is that you post a question with more detail about the error you are getting in the forums at www.asp.net.

13 January 2010 18:34 - rob

any chance of adding support for jCrop, and a "ajax" style upload (via iFrame or whatever)? I have a complicated form, and I want to process the files before submit.

13 January 2010 19:48 - Mike

@rob

I've added your request to my To Do list.

21 February 2010 17:50 - Dubb

Great article

22 February 2010 15:56 - mnk

Nice post Mike.

22 February 2010 21:24 - lynn

If you post an empty text file it's content length is 0. You may want to reconsider you check on that.

25 February 2010 19:53 - samah

Hi,

Is there a way to make an action return a file and changing the View at the same time?

Or is there a way we can return a file in a view?

Thanks,

Samah

26 February 2010 18:17 - rickj

Great post haven't seen anyone do this in MVC I posted this problem on the forum about 10 months ago and no one answered it it will be nice to place pictures in a data base with MVC looking forward it playing with this article thanks

26 February 2010 19:07 - Mike

@samah,

You asked that question in the right place: http://forums.asp.net/t/1529962.aspx and it has been answered there.

29 October 2010 22:09 - Tara Walsh

Excellent tutorial. Helped me out a lot :) Well done!

20 October 2012 20:04 - Stuart Pegg

Thanks Mike: Much appreciated.

07 March 2013 07:13 - Hemavathi

Nice post... Helped me a lot

19 November 2013 17:05 - gadam

How do you pass a dropdown value (other form data) along with the file to your action?

26 December 2013 22:44 - sam

You are brillant in asp.net and jquery fusion....awesome man keep it up.....

20 January 2014 08:29 - raja k

Brilliant... Was struggling to get this done for the past 4 days, nights.. huff... what a relief i had now.. thank...

Just extending the questions here.. I can select files, store in db, fetch it back and download as well now... Now i have button to view apart from download. I would like to view the file and decide to download. Any help would be much appreciated. thank s again.