Multiple file upload in MVC using Uploadify

UploadifyIn a previous post, I wrote about creating an ftp site and using a custom authentication database for users to upload files to your web server.  Last week I was researching how to upload multiple files in an MVC web application without having to rely on ftp.  There are several benefits to this, the biggest being that it is much more convenient to upload right from the web browser and not having to use a separate ftp client.  I find that many end users are not familiar with ftp and having to log in using a different ftp address, username, and password is a pain.

There are several solutions out there including open source and paid plugins that costs hundreds of dollars.  The simplest solution which doesn’t require any third party plugin is explained in Phil Haak’s blog using the form post to an MVC controller.  This is great for simple uploads especially when the file sizes are small but definitely not what you want to use when dealing with multiple large files.  I’m talking about audio and video over 100 MB.  When uploading such huge files, there must be some feedback to the user regarding the status of the upload.  Otherwise they will think that the browser is just frozen and navigate away.

The solution I ended up with is an elegant one which I will detail here.  It uses a free (yes free!) JQuery plugin called Uploadify (which you might have already heard of).  Uploadify uses a flash front end and turns out to be pretty easy to implement and customize when used with MVC.  There are just several hurdles which I will cover in this post.  The first is the MVC restriction to file upload size limit which is easy to override.  The more difficult problem to deal with however is an incompatibility between Uploadify (Flash to be more precise) and MVC sessions and authentication.  Don’t worry though, I will detail how to get it all working below.

Step 1 – Download JQuery and Uploadify

The first thing to do is to download the Uploadify plugin and JQuery.  I put my jquery.js file in my Scripts directory in my MVC app and the extracted Uploadify files into a subfolder within Scripts called Uploadify to make things simple.  Remember to tell MVC to ignore routing to the Scripts directory or you will not be able to reference the files.  This can be done in the global.asax.cs file by including the following:

// Ignore the assets directory which contains images, js, css & html
            routes.IgnoreRoute("Scripts/{*pathInfo}");

Step 2 – Add Uploadify to your view page

The next step is to actually implement the Uploadify plugin on the MCV view page.  Do so by first adding the following three references:

 
<link href="<%= Url.Content("/Scripts/uploadify/uploadify.css") %>" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="/scripts/jquery-1.4.1.js"></script>
<script type="text/javascript" src="<%= Url.Content("/Scripts/uploadify/jquery.uploadify.v2.1.4.min.js") %>"></script>

Step 3 – Add plugin code

The next step is to add your JQuery code for the plugin:

 
<script type="text/javascript">
$(document).ready(function () {
 
    $("#uploadLink").hide();
 
    // Multiple files - single input
    var auth = "<% = Request.Cookies[FormsAuthentication.FormsCookieName]==null ? string.Empty : Request.Cookies[FormsAuthentication.FormsCookieName].Value %>";
    var ASPSESSID = "<%= Session.SessionID %>";
    var entryID = "<%= ViewData["EntryID"] %>";
 
    $("#file_upload").uploadify({
        'uploader': '<%= Url.Content("/Scripts/uploadify/uploadify.swf") %>',
        'script': '<%= Url.Action("uploadFiles") %>',
        'scriptData': { ASPSESSID: ASPSESSID, AUTHID: auth, entryID: entryID },
        'fileDataName': 'FileData',
        'buttonText': 'Select files',
        'multi': true,
        'width': 250,
        'sizeLimit': 200000000,
        'simUploadLimit': 1,
        'cancelImg': '<%= Url.Content("/Scripts/uploadify/cancel.png") %>',
        'folder': '/Content',
        'auto': false,
        'removeCompleted' : false,
        'onError': function (a, b, c, d) {
            if (d.status == 404)
                alert("Could not find upload script. Use a path relative to: " + "<?= getcwd() ?>");
            else if (d.type === "HTTP")
                alert("error " + d.type + ": " + d.status);
            else if (d.type === "File Size")
                alert(c.name + " " + d.type + " Limit: " + Math.round(d.info / (1024 * 1024)) + "MB");
            else
                alert("error " + d.type + ": " + d.text);
        },
        'onAllComplete' : function(event,data) {
          //alert(data.filesUploaded + ' files uploaded successfully!');
          $("#uploadLink").hide();
          window.location="/Entry/Edit/<%= ViewData["EntryID"] %>";
        },
        'onSelectOnce' : function(event,data) {
          $("#uploadLink").show();
        }
 
    });
 
});
 </script>

Using JQuery, I am hiding the upload button until a file has been added to the queue. The scriptData option is very useful if you want to pass additional information to be used in your controller. I have the following in there currently: { ASPSESSID: ASPSESSID, AUTHID: auth, entryID: entryID }. Don’t worry about the ASPSESSID and AUTHID for now. These are passed in to fix the bug which breaks the uploader if you are using session authentication. I am however passing in an EntryID which I will use to insert into my database to associate the file with. You can call it whatever you want or pass in as many variables as you wish. I will show you how to retrieve these values in the controller code.

Step 4 – Add html input tags

These are the html input tags for the file upload:

<input id="file_upload" name="file_upload" type="file" />
<input id="uploadLink" onclick="$('#file_upload').uploadifyUpload();" type="button" value="Upload now" />

That’s all there is to it get get the select and upload button to show up.

Step 5 – Coding the MVC controller

If you look at the script option in the JQuery above, you will see that we are pointing to a function in our MVC controller called uploadFiles:

'script': '<%= Url.Action("uploadFiles") %>',

The following is the code for the uploadFiles function in the controller:

public string uploadFiles(HttpPostedFileBase FileData, FormCollection forms)
      {
          int EntryID = int.Parse(forms.Get("entryID"));
 
          try
          {
              if (FileData.ContentLength > 0)
              {
                  string FileName = System.IO.Path.GetFileName(FileData.FileName);
                  string FileNameExtension = System.IO.Path.GetExtension(FileData.FileName);
                  string FileNameNoExtension = System.IO.Path.GetFileNameWithoutExtension(FileData.FileName);
                  string sDir = "//Extranet3/extranet3/wwwroot/awards/cassies/" + EntryID;
                  int FileSize = FileData.ContentLength/1000;
 
                  System.IO.Directory.CreateDirectory(sDir);
                  FileData.SaveAs(sDir + '/' + FileName);
 
                  awardsDataContext db = new awardsDataContext();
                  awEntryFile f = new awEntryFile
                  {
                      EntryID = EntryID,
                      FileName = FileNameNoExtension,
                      FileExtension = FileNameExtension,
                      Summary = false,
                      Filesize = FileSize
                  };
                  db.awEntryFiles.InsertOnSubmit(f);
                  db.SubmitChanges();
 
                  return "Upload OK! " + EntryID + ' ' + FileName;
              }
              else
              {
                  return "Upload Failed!";
              }
          }
          catch (Exception e)
          {
              //Do your error handling here in my case I'm logging the error to a file
              //_utils.log(e.Message);
          }
          return "Upload Failed!";
      }

You’re almost done! Notice in the controller upload function that my file upload directory is defined by:

string sDir = "//Extranet3/extranet3/wwwroot/awards/cassies/" + EntryID;

You will need to specify your own directory on your server and make sure there is appropriate read and write permissions to it.

Notice how I am retrieving the file name, file extension, file size and the EntryID variable and only saving the file if the size of the file is not 0.

In addition to saving the file, I’m also saving the details of each file to the database so I can easily retrieve this information later. My dataContext is called awardsDataContext so this part you can customize to fit your own database.

Step 6 – Fixing Session authentication bug

At this point, your upload facility is pretty much done. You can run your MVC app and try it out. However, if you are using session authentication (i.e. your controller class has a [Authorize] before declaring the function) you will be extremely disappointed to find out that the upload will fail with an IO error.

This is not really a bug with Uplodify, but really a bug with Flash and the fact that it is unable to retrieve session cookies in MVC. The bug is documented here if you are interested.

To overcome this problem, you will need to make the following changes to your global.asax file. Remember the scriptData ASPSESSID and AUTHID we pass in from the Uploadify code? Well we are passing this into our controller so the auth cookie and session id cookie values are known before the session is retrieved. Just open up your global.asax file and add these functions:

 
protected void Application_BeginRequest(object sender, EventArgs e)
        {
            /* we guess at this point session is not already retrieved by application so we recreate cookie with the session id... */
            try
            {
                string session_param_name = "ASPSESSID";
                string session_cookie_name = "ASP.NET_SessionId";
 
                if (HttpContext.Current.Request.Form[session_param_name] != null)
                {
                    UpdateCookie(session_cookie_name, HttpContext.Current.Request.Form[session_param_name]);
                }
                else if (HttpContext.Current.Request.QueryString[session_param_name] != null)
                {
                    UpdateCookie(session_cookie_name, HttpContext.Current.Request.QueryString[session_param_name]);
                }
            }
            catch
            {
            }
 
            try
            {
                string auth_param_name = "AUTHID";
                string auth_cookie_name = FormsAuthentication.FormsCookieName;
 
                if (HttpContext.Current.Request.Form[auth_param_name] != null)
                {
                    UpdateCookie(auth_cookie_name, HttpContext.Current.Request.Form[auth_param_name]);
                }
                else if (HttpContext.Current.Request.QueryString[auth_param_name] != null)
                {
                    UpdateCookie(auth_cookie_name, HttpContext.Current.Request.QueryString[auth_param_name]);
                }
 
            }
            catch
            {
            }
        }
 
        private void UpdateCookie(string cookie_name, string cookie_value)
        {
            HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(cookie_name);
            if (null == cookie)
            {
                cookie = new HttpCookie(cookie_name);
            }
            cookie.Value = cookie_value;
            HttpContext.Current.Request.Cookies.Set(cookie);
        }

And that’s basically it! Try your upload now and see it work beautifully!

Step 7 – Extending upload timeout and file size limit

If your users are uploading multiple massive files, we need to make sure your page won’t time out. We also need to ensure that the file size isn’t being restricted by IIS. All you need to do is add the following into the system.web and system.webServer sections of the web.config file:

 
<system.web>
    <httpRuntime maxRequestLength="300000" executionTimeout="12000" />
</system.web>
 
<system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="300000000" />
      </requestFiltering>
    </security>
</system.webServer>

The maxRequestLength parameter of 300000 is 300 megabytes and an executionTimeout of 1200 seconds. The maxAllowedContentLength is specified in bytes so 300000000 is roughly 300 megabytes.

And there you have it! You have successfully implemented professional multi uploader in your MVC application that can support large files without having to spend hundreds of dollars on commercial plugins.

This entry was posted in MVC 2.0 and tagged , , , , , . Bookmark the permalink.
  • http://techblog.edwardting.com/2011/06/20/flash-detection-using-swfobject-and-jquery/ Flash detection using SWFObject and JQuery | techblog.edwardting.com

    [...] techblog.edwardting.com Edward Ting, entrepreneur, consultant, it director, tech blogger, professional photographer, father Skip to content HomeAbout ← Multiple file upload in MVC using Uploadify [...]

  • Tomas Lycken

    Awesome post! This is just what we were looking for – a well documented, fairly (but not stupidly) simple tutorial on how to create multiple file upload in ASP.NET MVC =)

    However, I have one question: Why are we swallowing exceptions in the BeginRequest method? What can be thrown here, and why don’t we want to handle it?

  • Anonymous

    I’m glad you enjoyed my post!  In regards to the exception handling, you are correct….I’m currently not doing anything if an error occurs.  I did that only to keep the code simple.  You can add whatever error handling you want in there.  I log all my errors by writing to a log file for instance.

  • Tomas Lycken

    Thanks for your reply! =) We have some custom error handling in place as well, but on a higher level in the application. Thus, I’d like to be able to catch only Uploadify-related exceptions (instead of general exceptions as right now), to wrap them in ApplicationExceptions with friendly messages and rethrow. But to do that, I need to know what exceptions Uploadify will throw – do you have any idea?

  • http://www.facebook.com/jack.marchetti Jack Marchetti

    Just want to point out to future users of this that putting the session ID in the HTML page opens up a security risk.

  • kiran

    Hi Edward,
    I would like to see this post and through out the web they were mentioned only to work with MVC application but can you say me how to do with normal asp.net application.

  • Choa Islam

    soo nice, but i’m having HTTP error, and errorObj.info = 500, any help?

  • Dave

    Saved me a ton of work and frustration getting uploadify working in my MVC project.  Much appreciated.

  • Postonoh

     I understand this awesome, How do I use this with a form to submit data into a database.

  • http://www.thefullertonian.com Mark Stouffer

    What if you encrypted it? Is there a better solution for ajax uploads in MVC?

  • Dobrowolski Adrian86

    I did exactly like you said but all the time i have this error The HTTP verb POST used to access path ‘/Scripts/uploadify/uploadify.swf’ is not allowed for example: POST /Scripts/uploadify/uploadify.swf HTTP/1.1  while Get works for example GET /Scripts/uploadify/uploadify.swf?preventswfcaching=1348056369667 HTTP/1.1