Not any Web applications need to upload a file, but any serious Web framework must provide some tooling for developers to write file upload functionalities quickly and comfortably. ASP.NET solved the issue by offering the FileUpload server control. Frankly, there’s not much that developers can invent when it comes to file uploads. Browsers offer some core functionality through their implementation of the <input type=file> element. Anything beyond this, though, would require some rich client side capabilities such as an ActiveX control, Silverlight or perhaps Flash. As long as you remain in the Web programming realm you are limited to using the basic browser provided file uploader which is in turn limited to uploading one file at a time.
ASP.NET MVC is only a new framework for ASP.NET programming; so it can’t really offer anything different from what you did with Web Forms. The model binding infrastructure, however, can help a bit to make the whole process of uploading and storing file content seamless.
Multi-part Input Forms
Let’s go through a typical example of file upload. Suppose you are writing a Web interface to let users register to a given community. You expect users to provide their account information and want them to choose a picture. So you have an action method like below that takes users to a registration page:
The registration page shows a classic input form where a new user can specify its own name, email and upload a picture.
HTTP dictates that any form expected to upload the content of a file must have the
enctype attribute set to the
multipart/form-data value. The
enctype attribute specifies the content type used to submit (via post) the form to the server. The
enctype attribute defaults to a value of
application/x-www-form-urlencoded. The value must be changed to
multipart/form-data when the form contains an
<input> of type
file. The reason is that a multipart form is allowed to contain any data type in its multiple parts – whether text, binary, or whatever else. The actual content and type are determined when the data is parsed out. You could even make
multipart/form-data the default value for
enctype in your forms but this would generate a bit more traffic (especially for headers) and is therefore preferable that you bring it in only when it’s strictly needed.
The user interface of the input file field depends on the browser and there’s not much you can do other than making it a bit more stylish via CSS. (Not all browsers let you style input file fields, however.) You are not allowed to write to the field via script; and when you attempt to read its content depending on the browser’s implementation either you get the sole file name (Firefox) or a fully qualified name with fake path (Internet Explorer).
The canonical behavior of the input file field element is showing the user an open-file dialog box, letting the user pick up a file from the local machine, and displaying the full path in the (read-only) text box. Next, when the multi-part form is submitted, the browser will pick up the file name and prepare HTTP packets that upload both plain text and file content – binary or text.
That’s all for the client side. Let’s turn our attention to the server.
Model Binding with Uploaded Files
The preceding form will post its content to the Register action of the User controller. Here’s a possible signature for the action method:
The UserInputModel class indicates a data transfer object that will collect the data being posted to the server. The following template will work well with native ASP.NET MVC model binding.
As expected, the Name and Email members will receive the value of matching input fields. What about the Picture member? The default model binder will try to bind it to the output posted by the Picture field. The Picture uploads the entire content of the selected file. Here’s an excerpt from the request packet:
The content for the Picture field is not a scalar value. ASP.NET MVC will render it through an HttpPostedFileBase object. This means that if you define Picture as a String member (meaning you want it to contain the file name) it will receive instead the output of the
ToString method as defined by the HttpPostedFileBase class. This default behavior can be modified by creating a custom value provider for posted files that resolves the match using a different logic and storing the file name to the matching property. The key point, however, seems to be quite another.
When you upload a form like the one discussed here you want to achieve two main goals. First, you want to know the name of the image and store it in the database. Second, you want to save the actual file content somewhere on the server – either as a server file or into some database table. Modifying the value provider to bind the file name directly to the property is not enough. In addition, you still need to access the ASP.NET representation of the posted file to save it in some way. You still need code like the one shown below:
The code loops through any posted files and for each occurrence builds a server path and saves the uploaded content. The list of uploaded files is reached through the Files collection of the
Request object. This code is exactly the same you would write for a classic ASP.NET application.
What ASP.NET MVC allows you to do with a different style is the retrieval of the files. You can do that through the members of the method parameter. You change the definition of the UserInputModel class as follows:
Now that the Picture member has the matching type the file value provider can easily assign it an instance of the HttpPostedFileBase object created by the ASP.NET runtime. The result is that now you can write the following code to process the action:
The net effect doesn’t really change, but the code is a bit more abstract and in line with the model binding approach.
A Few Things to Keep in Mind
Any uploaded files will be likely saved somewhere on the server. It should be noted that creating files on the Web server is not usually an operation that can be accomplished standing the default permission set. Any ASP.NET application runs under the account of the worker process serving the application pool the application belongs to. Under normal circumstances, this account is NETWORK SERVICE and it isn’t granted the permission to create new files. This means that the previous won’t work unless you either change the account behind the ASP.NET application or elevate the privileges of the default account.
For years, the identity of the application pool has been a fixed identity–the aforementioned NETWORKSERVICE account, a relatively low-privileged built-in identity in Windows. Originally welcomed as an excellent security measure, the use of a single account for a potentially high number of concurrently running services in the end created more troubles than it helped to solve. In a nutshell, services running under the same account could tamper each other. For this reason, in IIS 7.5, worker processes by default run under unique identities automatically and transparently created for each newly created application pool. The underlying technology is known as Virtual Accounts and is currently supported by Windows Server 2008 R2 and Windows 7. For more information, have a look at http://technet.microsoft.com/en-us/library/dd548356(WS.10).aspx.
Another point that may be source of headaches is the maximum size of the request and subsequently the maximum size allowed for your uploads. By default, any ASP.NET request can’t be longer than 4 MB. This amount should include any uploads, headers, body and whatever is being transmitted. The value is configurable at various levels. You do that through the
maxRequestLength entry in the
httpRuntime section of the web.config file:
It goes without saying that the larger a request can be, the more you potentially leave room the hackers to prepare attacks to your site. Finally, note that in a hosting scenario your application level settings may be ignored if the hoster has set a different limit at the domain level and locked down the maxRequestLength property at lower levels.
Multiple File Uploads
What about multiple file uploads? As long as the overall size of all uploads is compatible with the current maximum length of a request, you are allowed to upload multiple files within a single request. However, consider that Web browsers just don’t know how to upload multiple files. All a Web browser can do is uploading a single file and only if you reference it through an input element of type file. To upload multiple files, you can resort to some client side ad hoc component or place multiple INPUT elements in the form. If multiple INPUT elements are placed, and properly named, the following class will bind them all:
The class represents the data posted for a new user with a default picture and a list of alternate pictures. The markup for alternate pictures is below:
In ASP.NET MVC, model binding does the trick!
ASP.NET MVC runs on the same runtime environment of ASP.NET Web Forms. So the way in which some basic tasks are accomplished can’t be significantly different. And if it is different it’s because of a different level of abstraction. File upload is the perfect example to explain this concept. It works like in Web Forms, but model binding can simplify the way in which you write your server code and add a bit of (very welcome) abstraction.