6 Dec 2010

MVC File Upload - Using Uploadify with ASP.NET MVC 2 / 3


NOTE 23 Oct 2012

I no longer recommend the use of Uploadify with MVC. I've found benefit using javascript-only solutions such as DropZone : http://www.dropzonejs.com/

or Valum's File Uploader : https://github.com/Valums-File-Uploader/file-uploader




Uploadify is ace for performing file upload and image upload, but ASP.NET MVC docs were lacking, so I hope this helps someone. Critical points addressed here include:
  • Uploadify flash upload button is ugly so we use CSS to make it invisible and fake an HTML button under it.

  • The Uploadify flash upload doesn't pass the ASP.NET session and authentication cookies naturally, which we want for recognition and authorization, so we handle that.

UPLOADIFY FILES

Put all Uploadify files in a project folder called ClientScript/uploadify

SCRIPT

Put this code on the regular View page that performs the upload.

 <script type="text/javascript" src="<%= Url.Content("~/ClientScript/jquery-1.4.2.min.js")  %>"></script>
 <script type="text/javascript" src="<%= Url.Content("~/ClientScript/uploadify/swfobject.js") %>"></script>
 <script type="text/javascript" src="<%= Url.Content("~/ClientScript/uploadify/jquery.uploadify.v2.1.4.min.js")  %>"></script>
 
 <script type="text/javascript">

    $(function () {

        // Uploadify File Upload System
        // SessionSync data is sent in scriptData for security reasons, see UploadifySessionSync() in global.asax

        var UploadifyAuthCookie = '<% = Request.Cookies[FormsAuthentication.FormsCookieName] == null ? string.Empty : Request.Cookies[FormsAuthentication.FormsCookieName].Value %>'; 
        var UploadifySessionId = '<%= Session.SessionID %>';

        $("#fuFileUploader").uploadify({

            'hideButton'   : true,       // We use a trick below to overlay a fake html upload button with this hidden flash button                         
            'wmode'      : 'transparent', 
            'uploader': '<%= Url.Content("~/ClientScript/uploadify/uploadify.swf") %>',
            'cancelImg': '<%= Url.Content("~/ClientScript/uploadify/cancel.png") %>',
            'buttonText': 'Upload File',
            'script': '<%= Url.Action("FileUpload", "Media") %>',
            'multi': true,
            'auto': true,
            'scriptData' : { RequireUploadifySessionSync: true, SecurityToken: UploadifyAuthCookie, SessionId: UploadifySessionId },
            'onComplete' : function (event, ID, fileObj, response, data) 
                            {
                                response = $.parseJSON(response);
                                if (response.Status == 'OK')
                                {
         // Put your own handler code here instead of the following...
                                    alert('File Uploaded OK!');
                                }
                            }
        }); 
    });

 </script>

HTML

Put this HTML on the same View as the script above.

    <div style="position: relative;">
        
        <input type="button" id="btnUpload" value="Upload File" />

        <div style="position: absolute; top: 4px; left: 3px;">
            <input id="fuFileUploader" name="file_upload" type="file" />
        </div>
    </div>

GLOBAL.ASAX

Add the following bits.

 protected void Application_BeginRequest(Object sender, EventArgs e) 
 {
  if (HttpContext.Current.Request["RequireUploadifySessionSync"] != null)
   UploadifySessionSync();
 }

 /// <summary>
 /// Uploadify uses a Flash object to upload files. This method retrieves and hydrates Auth and Session objects when the Uploadify Flash is calling.
 /// </summary>
 /// <remarks>
 ///     Kudos: http://geekswithblogs.net/apopovsky/archive/2009/05/06/working-around-flash-cookie-bug-in-asp.net-mvc.aspx
 ///     More kudos: http://stackoverflow.com/questions/1729179/uploadify-session-and-authentication-with-asp-net-mvc
 /// </remarks>
 protected void UploadifySessionSync()
 {
  try
  {
   string session_param_name = "SessionId";
   string session_cookie_name = "ASP.NET_SessionId";

   if (HttpContext.Current.Request[session_param_name] != null)
    UploadifyUpdateCookie(session_cookie_name, HttpContext.Current.Request.Form[session_param_name]);
  }
  catch {}

  try
  {
   string auth_param_name = "SecurityToken";
   string auth_cookie_name = FormsAuthentication.FormsCookieName;

   if (HttpContext.Current.Request[auth_param_name] != null)
    UploadifyUpdateCookie(auth_cookie_name, HttpContext.Current.Request.Form[auth_param_name]);
  }
  catch {}
 }

 private void UploadifyUpdateCookie(string cookie_name, string cookie_value)
 {
  HttpCookie cookie = HttpContext.Current.Request.Cookies.Get(cookie_name);
  if (cookie == null)
   cookie = new HttpCookie(cookie_name);
  cookie.Value = cookie_value;
  HttpContext.Current.Request.Cookies.Set(cookie);
 } 

Models/MediaAssetUploadModel.cs
    public class MediaAssetUploadModel
    {
        public HttpPostedFileBase fileData { get; set; }
        public string SecurityToken { get; set; }
        public string Filename { get; set; }
    }

Controllers/MediaController.cs
 [Authorize]
 public ActionResult FileUpload(MediaAssetUploadModel uploadedFileMeta)
 {
  string fullFilePath = SaveUploadedFile(uploadedFileMeta);
  //TODO: Error handling
  return Json(new { Status = "OK" });
 }  

 //TODO: move this into a manager/repository class
 private string SaveUploadedFile(MediaAssetUploadModel uploadedFileMeta)
 {
  string fileName = Guid.NewGuid() + System.IO.Path.GetExtension(uploadedFileMeta.Filename);
  string fullSavePath = Path.Combine(ConfigurationManager.AppSettings["MediaAssetFolder"], fileName);
  uploadedFileMeta.fileData.SaveAs(fullSavePath);

  return fullSavePath;
 } 

Web.config

Make following changes to upload files to c:\temp , with a max file size of 100Mb.
Change these to suit yourself - c:\temp will get automatically cleared so you have been warned.
If you create your own upload folder, remember you will need to give write perms to the ASP.NET process.
 <configuration>
   <appSettings>
  <add key="MediaAssetFolder" value="c:\temp"/>
   </appSettings> 
   <system.web>
  <httpRuntime maxRequestLength="100000" /> 
   </system.web>
 </configuration>

29 comments:

  1. Anonymous7:02 pm

    Thanks for posting this - the write up was spot on. You saved me an enormous amount of time piecing this all together.

    ReplyDelete
  2. Anonymous3:11 am

    Great tip. Took me all of 2 minutes from start to finish and now my MVC 2 has async file uploads.

    Thank you!

    ReplyDelete
  3. Anonymous4:25 pm

    Oh I wish this post had been around a few months ago!! This has to be the most complete coverage around of how to get Uploadify and MVC2 to play nice together. Really great stuff. Thanks so much for this.

    ReplyDelete
  4. Anonymous5:29 pm

    we tried, but it is not working... dont know why?

    ReplyDelete
  5. Anonymous3:45 am

    After some small modifications to accomodate Razor and to save images direct to a database, this worked a treat.. Thanks very much!

    ReplyDelete
  6. Great post! Is this solution ajax based? Or does it cause the page to refresh?

    Thanks,
    Calum

    ReplyDelete
  7. Yeah, Uploadify does the upload asynchronously and calls the 'onComplete' javascript function as seen in the code above. You can then hook in any other AJAX / jQuery magic you like to handle the completion event.

    No nasty refreshes! :D

    ReplyDelete
  8. Cheers James, good to know... My page kept refreshing must of not copied and pastied your project well enough :)

    ReplyDelete
  9. The value for Request.Cookies[FormsAuthentication.FormsCookieName] always seems to be null, so the controller is never called when the [authorize] tag is applied. Do I have to set something in the web.config for this cookie to be written? Thanks

    ReplyDelete
  10. Hmm, have you put breakpoints into global.asax to make sure uploadify is sending through the scriptdata correctly?

    Specifically, is HttpContext.Current.Request["RequireUploadifySessionSync"] being set in Application_BeginRequest?

    Also, Firebug/Fiddler is your friend - check the cookies coming in and out.

    ReplyDelete
  11. Anonymous11:04 pm

    Congratulations the code to fix the session lost it works very good, thank you

    ReplyDelete
  12. Anonymous2:46 pm

    Anyway of getting this javascript in a separate file?

    ReplyDelete
  13. Anonymous5:08 pm

    I hope you are as good a Daddy as you are at Code, this is so helpful! :)

    I did have some questions though, hopefully you are still taking responses to this post. I was curious as to why the security tokens, cookies, etc. I am not an expert programmer, and I am learning, but I wanted to know why that was needed to upload images?

    I plan on using this code to upload images to a web server, store the image paths in a database, and use them in a slideshow on a public website. Are there any caveats I need to be aware of that aren't listed here? Thanks for your time!

    ReplyDelete
  14. Dear James,
    When i used the uploadify on ajax/jquery dialog i can't use html button instead of flash upload button.

    ReplyDelete
  15. Hi, maybe you can help me, I am using same code, but after some time the page have been loaded, upoloadify uses new session(comparing "HttpContext.Session.SessionID" in controller and "ASP.NET_SessionId" in cookie)
    Any ideas? Can I invoke old session with older id in controller?

    ReplyDelete
  16. Anonymous11:31 am

    You are a hero, thanks for the good post

    ReplyDelete
  17. Hi, I'm having troubles getting this to work with Windows Authentication and MVC3, I'm still getting the same old "HTTP Error" before it even hits my action...

    I would really appreciate help on this please!

    ReplyDelete
  18. Anonymous4:52 pm

    Thanks.. This is it.. Daddycode!.

    ReplyDelete
  19. i keep getting http error ...
    need help

    ReplyDelete
  20. cool its working now

    ReplyDelete
  21. Anonymous11:14 am

    Great Tutorial. But how can I do it with Windows Authentification?

    ReplyDelete
  22. I found this solution very smart. I am curious if anyone had tried this solution in web farm scenarios (without sessioMode as outProc).

    Any help will be much apppreciated

    ReplyDelete
  23. I was able to get this to work however I had to put the parameters from the scriptData option in the URL instead since it appears that the scriptData option is no longer available in the latest version of Uploadify.

    ReplyDelete
  24. Anonymous7:46 pm

    Hi James,

    For me HttpContext.Current.Request["RequireUploadifySessionSync"] always comes null. So no desired optput. Nothing happen on upload button click.

    Please let me know where or how to set some value to:
    HttpContext.Current.Request["RequireUploadifySessionSync"]

    ReplyDelete
  25. Really great post! Thanks for sharing it with us!

    "The Uploadify flash upload doesn't pass the ASP.NET session and authentication cookies naturally, which we want for recognition and authorization, so we handle that." -- It works fine in IE and not in FF. So, I was just curious to know -- how IE handles session? And why It works with IE? Can you please help me to understand it?

    Kiran

    ReplyDelete
  26. Excellent post - thanks for that!

    ReplyDelete
  27. Can you elaborate more on why you don't recomment Uploadify?

    ReplyDelete
    Replies
    1. Hi Liam, although I haven't done anything with Uploadify recently, various people in the Comments here are saying the latest version doesn't have the ScriptData option anymore. I haven't verified that, but if true, it makes the authentication workaround difficult.

      More important to me however is the fact that Uploadify relies on Flash, which is a dying technology with limited support in the mobile / tablet space. That means its time is up. HTML5 / JavaScript solutions are the way forward now.

      Delete
  28. Hello, this is not safe. Session cookies are usually protected with a flag called HTTPOnly. This flag prevents cross site scripting because it makes the cookie inaccessible for javascript. Your script publishes the cookie value as part of the page content and therefor it is accessible for javascript and so for cross site scripting.

    ReplyDelete

Comments are very welcome unless you're a spammer, in which case you should probably kill yourself.

If I helped you out today, you can buy me a beer below. Cheers!