Create An Ajax Style File Upload
|If you visit Asp.net Ajax Forum, you will find hundreds of request on File Upload Control that it does not give Ajax version of the page when it is placed in an UpdatePanel. Certainly, it is not possible, as the XMLHTTPRequest object that is used internally to post the form does not support file upload. In this post I will show you how to create Ajax like version of the file upload. The trick is very simple, I will use an Iframe to upload the file so the whole page does not get refreshed and when the post is in progress it will show a dummy progress. The following shows you the screenshots of the solutions:
Now lets discuss the implementation part, starting with the Main page markup.
<fieldset> <legend>Photo Upload Demo</legend> <div id="divFrame"> <iframe id="ifrPhoto" onload="initPhotoUpload()" scrolling="no" frameborder="0" hidefocus="true" style="text-align:center;vertical-align:middle;border-style:none;margin:0px;width:100%;height:55px" src="PhotoUpload.aspx"></iframe> </div> <div id="divUploadMessage" style="padding-top:4px;display:none"></div> <div id="divUploadProgress" style="padding-top:4px;display:none"> <span style="font-size:smaller">Uploading photo...</span> <div> <table border="0" cellpadding="0" cellspacing="2" style="width:100%"> <tbody> <tr> <td id="tdProgress1"> </td> <td id="tdProgress2"> </td> <td id="tdProgress3"> </td> <td id="tdProgress4"> </td> <td id="tdProgress5"> </td> <td id="tdProgress6"> </td> <td id="tdProgress7"> </td> <td id="tdProgress8"> </td> <td id="tdProgress9"> </td> <td id="tdProgress10"> </td> </tr> </tbody> </table> </div> </div> </fieldset> |
As you see that we are using few divs to show and hide the upload form and the progress bar. The important thing is we are binding the initPhotoUpload in the frame load event. Now let us check the iframe source page markup. <form id="photoUpload" enctype="multipart/form-data" runat="server"> <div> <input id="filPhoto" type="file" runat="server"/> </div> <div id="divUpload" style="padding-top:4px"> <input id="btnUpload" type="button" value="Upload Photo" /> </div> </form> |
The iframe source page contains only a file upload control and a button to post the form. Now lets get back to the main page initPhotoUpload function and lets check how it is implemented:
function initPhotoUpload() { _divFrame = document.getElementById('divFrame'); _divUploadMessage = document.getElementById('divUploadMessage'); _divUploadProgress = document.getElementById('divUploadProgress'); _ifrPhoto = document.getElementById('ifrPhoto'); var btnUpload = _ifrPhoto.contentWindow.document.getElementById('btnUpload'); btnUpload.onclick = function(event) { var filPhoto = _ifrPhoto.contentWindow.document.getElementById('filPhoto'); //Baisic validation for Photo _divUploadMessage.style.display = 'none'; if (filPhoto.value.length == 0) { _divUploadMessage.innerHTML = '<span style=\"color:#ff0000\">Please specify the file.</span>'; _divUploadMessage.style.display = ''; filPhoto.focus(); return; } var regExp = /^(([a-zA-Z]:)|(\\{2}\w+)\$?)(\\(\w[\w].*))(.jpg|.JPG|.gif|.GIF|.png|.PNG|.bmp|.BMP)$/; if (!regExp.test(filPhoto.value)) //Somehow the expression does not work in Opera { _divUploadMessage.innerHTML = '<span style=\"color:#ff0000\">Invalid file type. Only supports jpg, gif, png and bmp.</span>'; _divUploadMessage.style.display = ''; filPhoto.focus(); return; } beginPhotoUploadProgress(); _ifrPhoto.contentWindow.document.getElementById('photoUpload').submit(); _divFrame.style.display = 'none'; } } |
Once the iframe is loaded we are setting few module level variables (All module level variables are prefixed with _ (underscore) character in this example) to DOM elements, we are also getting the reference of the Upload button which is in the iframe and creating its click handler. In the click handler first we are doing few basic validation such as the file upload control is not empty or the file that is specified has a valid image file extension. Once the validation qualifies then we are calling the beginPhotoUploadProgress function (discussed next) and submitting the iframe form. The beginPhotoUploadProgress function is the first of three functions which are used to show the dummy progress bar. Let us see how these functions are written:
function beginPhotoUploadProgress() { _divUploadProgress.style.display = ''; clearPhotoUploadProgress(); _photoUploadProgressTimer = setTimeout(updatePhotoUploadProgress, PROGRESS_INTERVAL); } function clearPhotoUploadProgress() { for (var i = 1; i <= _maxLoop; i++) { document.getElementById('tdProgress' + i).style.backgroundColor = 'transparent'; } document.getElementById('tdProgress1').style.backgroundColor = PROGRESS_COLOR; _loopCounter = 1; } function updatePhotoUploadProgress() { _loopCounter += 1; if (_loopCounter <= _maxLoop) { document.getElementById('tdProgress' + _loopCounter).style.backgroundColor = PROGRESS_COLOR; } else { clearPhotoUploadProgress(); } if (_photoUploadProgressTimer) { clearTimeout(_photoUploadProgressTimer); } _photoUploadProgressTimer = setTimeout(updatePhotoUploadProgress, PROGRESS_INTERVAL); } |
As we can see we are basically using the timer (window.setTimeout) to show the dummy progress in these functions. Now lets see what happens in the server side when the iframe page posts.
private const string SCRIPT_TEMPLATE = "<" + "script " + "type=\"text/javascript\">window.parent.photoUploadComplete('{0}', {1});" + "<" + "/script" + ">"; private void Page_Load(object sender, EventArgs e) { if (IsPostBack) { //Sleeping for 10 seconds, fake delay, You should not it try at home. System.Threading.Thread.Sleep(10 * 1000); UploadPhoto(); } } private void UploadPhoto() { string script = string.Empty; if ((filPhoto.PostedFile != null) && (filPhoto.PostedFile.ContentLength > 0)) { if (!IsValidImageFile(filPhoto)) { script = string.Format(SCRIPT_TEMPLATE, "The uploaded file is not a valid image file.", "true"); } } else { script = string.Format(SCRIPT_TEMPLATE, "Please specify a valid file.", "true"); } if (string.IsNullOrEmpty(script)) { //Uploaded file is valid, now we can do whatever we like to do, copying it file system, //saving it in db etc. //Your Logic goes here script = string.Format(SCRIPT_TEMPLATE, "Photo uploaded.", "false"); } //Now inject the script which will fire when the page is refreshed. ClientScript.RegisterStartupScript(this.GetType(), "uploadNotify", script); } private static bool IsValidImageFile(HtmlInputFile file) { try { using (Bitmap bmp = new Bitmap(file.PostedFile.InputStream)) { return true; } } catch (ArgumentException) { //throws exception if not valid image } return false; } |
In the server side we are first doing some basic validation such as the file upload control has file and the file is a valid image file. Once the validation is done we are injecting some javascript which will be executed when the page is loaded in the browser. The javascript calls the main page photoUploadComplete function which shows the success/failure or the validation message of the upload. Lets see the javascript function photoUploadComplete of the main page.
function photoUploadComplete(message, isError) { clearPhotoUploadProgress(); if (_photoUploadProgressTimer) { clearTimeout(_photoUploadProgressTimer); } _divUploadProgress.style.display = 'none'; _divUploadMessage.style.display = 'none'; _divFrame.style.display = ''; if (message.length) { var color = (isError) ? '#ff0000' : '#008000'; _divUploadMessage.innerHTML = '<span style=\"color:' + color + '\;font-weight:bold">' + message + '</span>'; _divUploadMessage.style.display = ''; if (isError) { _ifrPhoto.contentWindow.document.getElementById('filPhoto').focus(); } } } |
The function simply hides the progress bar and shows the upload control then it shows the message in colored text which is sent from the server.
I hope you can easily implment it in Ajax UpdatePanel, if not please let me know, I will show it in next post.