Tuesday, November 15, 2011

file upload with Preview in ASP.NET MVC

  Introduction :

        Previewing an image is a great way to improve the UI of your site. Also it is always best to check the file type, size and see a preview before submitting the whole form. There are some ways to do this using simple JavaScript but not work in all browsers (like FF3).In this Article I will show you how do this using ASP.NET MVC application. You also see how this will work in case of nested form.
  Description :

         Create a new ASP.NET MVC project and then add a file upload and image control into your View.
<form id="form1" method="post" action="NerdDinner/ImagePreview/AjaxSubmit">
            <table>
                <tr>
                    <td>
                        <input type="file" name="imageLoad1" id="imageLoad1"  onchange="ChangeImage(this,'#imgThumbnail')" />
                    </td>
                </tr>
                <tr>
                    <td align="center">
                        <img src="images/TempImage.gif" mce_src="images/TempImage.gif" id="imgThumbnail" height="200px" width="200px">
                    </td>
                </tr>
            </table>
        </form>
 
         Note that here NerdDinner is refers to the virtual directory name, ImagePreview is the Controller and ImageLoad is the action name which you will see shortly
         I will use the most popular jQuery form plug-in, that turns a form into an AJAX form with very little code. Therefore you must get these from Jquery site and then add these files into your page.
 
        <script src="NerdDinner/Scripts/jquery-1.3.2.js" mce_src="NerdDinner/Scripts/jquery-1.3.2.js" type="text/javascript"></script>
        <script src="NerdDinner/Scripts/jquery.form.js" mce_src="NerdDinner/Scripts/jquery.form.js" type="text/javascript"></script>
 
          Then add the javascript function. 
<script type="text/javascript">
function ChangeImage(fileId,imageId){
$("#form1").ajaxSubmit({success: function(responseText){
var d=new Date();
$(imageId)[0].src="NerdDinner/ImagePreview/ImageLoad?a=" mce_src="NerdDinner/ImagePreview/ImageLoad?a="+d.getTime();
}
});
}
</script>

          This function simply submit the form named form1 asynchronously to ImagePreviewController's method AjaxSubmit and after successfully receiving the response, it will set the image src property to the action method ImageLoad. Here I am also adding querystring, preventing the browser to serve the cached image.
          Now I will create a new Controller named ImagePreviewController.
 public class ImagePreviewController : Controller
{
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult AjaxSubmit(int? id)
{
Session["ContentLength"] = Request.Files[0].ContentLength;
Session["ContentType"] = Request.Files[0].ContentType;
byte[] b = new byte[Request.Files[0].ContentLength];
Request.Files[0].InputStream.Read(b, 0, Request.Files[0].ContentLength);
Session["ContentStream"] = b;
return Content( Request.Files[0].ContentType+";"+ Request.Files[0].ContentLength );
}
public ActionResult ImageLoad(int? id)
{
byte[] b = (byte[])Session["ContentStream"];
int length = (int)Session["ContentLength"];
string type = (string)Session["ContentType"];
Response.Buffer = true;
Response.Charset = "";
Response.Cache.SetCacheability(HttpCacheability.NoCache);
Response.ContentType = type;
Response.BinaryWrite(b);
Response.Flush();
Session["ContentLength"] = null;
Session["ContentType"] = null;
Session["ContentStream"] = null;
Response.End();
return Content("");
}
}

          The AjaxSubmit action method will save the image in Session and return content type and content length in response. ImageLoad action method will return the contents of image in response.Then clear these Sessions.
          Just run your application and see the effect.

Checking Size and Content Type of File:

         You may notice that AjaxSubmit action method is returning both content type and content length. You can check both properties before submitting your complete form.

  $(myform).ajaxSubmit({success: function(responseText)
            {               
                var contentType=responseText.substring(0,responseText.indexOf(';'));
                var contentLength=responseText.substring(responseText.indexOf(';')+1);
                // Here you can do your validation
                var d=new Date();
                $(imageId)[0].src=" /NerdDinner/ImagePreview/ImageLoad?a="+d.getTime();
            }
        });

Handling Nested Form Case:

         The above code will work if you have only one form. But this is not the case always.You may have a form control which wraps all the controls and you do not want to submit the whole form, just for getting a preview effect.
          In this case you need to create a dynamic form control using JavaScript, and then add file upload control to this form and submit the form asynchronously
function ChangeImage(fileId,imageId) {
var myform=document.createElement("form");
myform.style.display="none";
myform.action="/ImagePreview/AjaxSubmit";
myform.enctype="multipart/form-data";
myform.method="post";
var imageLoad;
var imageLoadParent;
var is_chrome = /chrome/.test( navigator.userAgent.toLowerCase() );
if(is_chrome && document.getElementById(fileId).value=='')
return;//Chrome bug onchange cancel
if(document.all || is_chrome){//IE
imageLoad=document.getElementById(fileId);
imageLoadParent=document.getElementById(fileId).parentNode;
myform.appendChild(imageLoad);
document.body.appendChild(myform);
}
else{//FF
imageLoad=document.getElementById(fileId).cloneNode(true);
myform.appendChild(imageLoad);
document.body.appendChild(myform);
}
$(myform).ajaxSubmit({success:
function(responseText){
var d=new Date();
$(imageId)[0].src="http://weblogs.asp.net/ImagePreview/ImageLoad?a=" mce_src="http://weblogs.asp.net/ImagePreview/ImageLoad?a="+d.getMilliseconds();
if(document.all || is_chrome)//IE
imageLoadParent.appendChild(myform.firstChild);
else//FF
document.body.removeChild(myform);
}
});
}

         You also need append the child in order to send request and remove them after receiving response.