Uploading and downloading files are common functions you’ll see in most websites and apps. Fortunately, it’s easy to write code to upload and download files using ASP.NET MVC. To start, we need a view and controller pair to upload a file. This is the same HTML and MVC code that you already know. However, there is no need for the model portion of the MVC pattern if you are only uploading files to disk and not working with a database. Otherwise, files can be a property of a model, for example, a profile picture.
The upload view
First, we must start in the view by creatingan HTML <form> element. You can create the form with the Html.BeginForm helper and pass in the following arguments, in the following order (for this particular overload signature):
- Action method name : Action method that processes uploaded file
- Controller name : Controller that belongs to above action method
- FormMethod : Any acceptable HTTP verb
- enctype : Appropriate encoding type
Using Helpers is a great way to take advantage of built in features of the ASP.NET MVC framework such as accessing routing information to create a link or using strongly typed objects to render HTML. In the case of the Html.BeginForm helper, we are rendering a <form> element. The Html.BeginForm helper expects an enctype attribute that designates that the form can send binary data as well as textual data in the HTTP POST request. Since the default encoding type is “text-plain” the HTTP Request will not send binary data (that means files!) to the server along with the usual textual form data, so you must set the enctype attribute to multipart/form data or it will not work!
Below is a complete sample of an MVC view containing a form with a file input and submit button:
@using (Html.BeginForm("Upload", "Home", FormMethod.Post, new { enctype = "multipart/form-data" } )) { @Html.AntiForgeryToken() <fieldset> <legend>Upload a file</legend> <div class="editor-field"> @Html.TextBox("file", "", new { type = "file" }) </div> <div class="editor-field"> <input type="submit" value="Upload" /> </div> </fieldset> }
ASP.NET Web Forms has a FileUpload Control, but in ASP.NET MVC our options are either a plain HTML element such as <input type=”file” /> or the TextBox and TextBoxFor Html Helpers (or any helper that outputs a file field). Any Html Helper that outputs an <input type=”file”> tag works. In the example above, the type, id, and name attributes of this tag are all set to “file”. A quick look in the browser using the IE Developer Tools illustrates what the Html.BeginForm and TextBox helpers from the above code sample have rendered:
When the user clicks the submit button, it causes an HTTP POST submission to /Home/Upload, which maps to the Home Controller’s Upload action method. The Upload action method receives the file and form data and from there you can perform the actual upload and save the file to disk or put in the cloud.
The upload action method
Action methods of MVC Controllers accept incoming HTTP requests for processing. Each HTTP request has a certain way, or method (POST GET PUT DELETE etc..) that it sends data to the server. The receiving action method must expect that type of request in order to process it. Since HTTP GET is the default, you must apply the HttpPost attribute to the Upload action method. This, of course, matches the method=”post” attribute of the HTML <form> element.
You can access uploaded files in the action method by querying the argument that is of type HttpPostedFileBase . The name of this argument, “file” in the below sample, must match the name of its corresponding file input in the HTML form (i.e., <input type=”file” name=”file”>). This allows ASP.NET MVC’s model binding to occur for file uploads. Ensuring the names match is using a concept called Convention over Configuration and leads to more readable code.
[HttpPost] public ActionResult Upload(HttpPostedFileBase file) { try { if (file.ContentLength > 0) { var fileName = Path.GetFileName(file.FileName); var path = Path.Combine(Server.MapPath("~/App_Data/Images"), fileName); file.SaveAs(path); } ViewBag.Message = "Upload successful"; return RedirectToAction("Index"); } catch { ViewBag.Message = "Upload failed"; return RedirectToAction("Uploads"); } }
If you need to upload multiple files, use IEnumerable<HttpPostedFileBase> instead of a single HttpPostedFile and loop through the collection to access individual files. When completing an upload, be sure that you have the correct permissions to write to the directory you want, then use classes from System.IO to access the file system and save the file.
System.IO.Path.GetFileName retrieves a file name including its extension, and System.IO.Path.Combine combines two or more strings to create a complete path. Because we’re working with an uploaded file from HTTP, we need Server.MapPath to map a virtual path (HTTP) to a physical directory (C:) . In this case the mapping is from the webroot, i.e., “~/” to the <project root>App_DataImages directory. I’m saving files to the App_Data directory to demonstrate concepts, but in real world sites it is unlikely that internet users would have privileges to save files there, so check with your sys admin or Web hoster as to where you should upload and download files.
The sample code above also overwrites any existing files, so if that’s an issue you can generate a unique file name by spinning up a Guid and using that as the name, as shown here:
var guid = Guid.NewGuid();
If the file to upload is accompanying form data, the controller action method signature should contain the model type and an HttpPostedFileBase type for the file. For instance, if the file is a user’s profile picture and part of a Person model, the action method signature should look like the following:
public ActionResult Upload( Person Person, HttpPostedFileBase file ) {… }
If you are using Convention over Configuration then model binding will take care of everything and all you do is access the method’s arguments.
In order to download, we need the same MVC components – a view and a controller. We’ll start at the controller.
The Download Action Method
Usually the UI presents a list or grid of links or images in which the user can click to download an image or file. What we’ll use for this example is a simple list of links of images to download. We can do this by creating a List<string> to store the file names in hyperlinks for download. The code below shows obtaining the directory and file information and adding the file names to the list of strings. The method then returns the list of strings as a model to the view.
public FileActionResult Downloads() { var dir = new System.IO.DirectoryInfo(Server.MapPath("~/App_Data/Images/")); System.IO.FileInfo[] fileNames = dir.GetFiles("*.*"); List<string> items = new List<string>(); foreach (var file in fileNames) { items.Add(file.Name); } return View(items); }
The view accepts the list of strings and displays them in the page.
The Download View
Notice that the model for this view is a List<string> type instead of a custom object as usual. Of course, this the view consists of a simple for loop that cycles through a list of strings containing the file names and displays them in <label> and <a> tags. The hyperlinks render so that they point to the Home controller’s Download action method, and pass in the name of the file as a URL parameter. Cycling through a list of strings means that you can use array syntax (i.e., @Model[i]) to access each member in the list.
@model List<string> <h2>Downloads</h2> <table> <tr> <th>File Name</th> <th>Link</th> </tr> @for (var i =0; i <= Model.Count -1; i++) { <tr> <td> @Model[i].ToString() </td> <td> @Html.ActionLink("Download", "Download", new { ImageName=@Model[i].ToString() }) </td> </tr> } </table>
The image below is what the download view produces. The ActionLink Helper renders links that point to the Home controller’s Download action method, passing in the image name as a query string. I’m using the defaults but you might want to consider configuring Routing so that the URL looks friendlier.
As users click on the download link, the Download action method returns a FileResult:
public FileResult Download(string ImageName)
{
return File(“<your path>” + ImageName, System.Net.Mime.MediaTypeNames.Application.Octet);
}
However, you can also return a FileContentResult, FilePathResult, or FileStreamResult (don’t forget to match the action method’s return type to the type you will use):
FileContentResult : Use this when want to use a byte array to access the file. In this scenario, you might have obtained the file as a byte array from a database call.
return new FileContentResult(byteArray, “image/jpeg”);
FilePathResult : Returns a file on disk when you must access via a file path. In the FilePathResult you can return a File type or a FileStreamResult.
return new FilePathResult(“~/App_Data/Images/” + ImageName, System.Net.Mime.MediaTypeNames.Application.Octet);
FileStreamResult : Sends a stream out to the response.
return new FileStreamResult(new FileStream(“<your path>”, FileMode.Open), “image/jpeg”);
The various ways to return a file are very similar but allow sufficient customizations to meet the needs of most scenarios. Sometimes the file is from a stream or byte array, or sometimes you only have a simple file path. Media types as well as the System.Net.Mime.MediaTypeNames enum are available so you can designate the file type for download, as the above samples demonstrate.
Summary
Code for uploading and downloading files are mandatory tasks for Web developers at some point in time. We want to allow users to safely upload and download files to go along with other data, and using ASP.NET MVC means you can use Html Helpers as an easy way to provide this functionality.
More on the dev tools:
Profile & Tune Web Pages Using IE Developer Tools
Investigate Web Pages Using IE Developer Tools
More on the Razor view engine: