Wednesday, July 18, 2012

Upload Profile Picture Using AsyncFileUpload Control in ASP.NET

This article shows how to preview the image before uploading the image to the server so that it can be saved into the database later. User will always like to see the image which has to be uploaded. This has been done using AsyncFileUpload(Ajax Control Toolkit) and HTTPHandler.

Here I am not interested to display the file upload control below the image control. Just these should be an image where user has to click on that to change/upload profile picture.

Below is the css for the desired UI

1. Write the below style under <head> section of asp.net page

<style type="text/css">
.FileUploadClass
{
font-size: 5px;
z-index: 500;
position: relative;
z-index: 10;
}
.FileUploadClass input[type=file]
{
background: transparent;
border: Dashed 2px #000000;
opacity: 0;
filter: alpha(opacity = 0);
width: 150px;
height: 150px;
}
.FakeFileUpload
{
position: relative;
border: Solid 1px #000000;
width: 150px;
height: 150px;
z-index: 1;
}
.FakeFileUploadDiv
{
width: 150px;
height: 150px;
position: absolute;
opacity: 1.0;
-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
filter: alpha(opacity=100);
z-index: 5;
}
</style>


2. Below code should be placed under <body> section of asp.net page


<asp:UpdatePanel ID="UpdatePanel1" UpdateMode="Always" runat="server">
<ContentTemplate>
<div class="FakeFileUpload">
<div class="FakeFileUploadDiv">
<asp:Image ID="imgPhoto" runat="server" ImageUrl="~/images/Employee.png" alt="Employee Photo"
Height="150px" Width="150px" />
</div>
<ajaxToolkit:AsyncFileUpload ID="AsyncFileUpload1" runat="server" ThrobberID="loader"
CssClass="FileUploadClass" OnClientUploadComplete="OnClientAsyncFileUploadComplete"
OnUploadedComplete="AsyncFileUpload1_UploadedComplete" Width="150" />
<asp:Image ID="loader" runat="server" ImageUrl="~/images/loading.gif" />
</div>
</ContentTemplate>
<asp:UpdatePanel>



3. So this is UI part. Next we need to write some server side code for “OnUploadComplete” event of AsyncFileUpload control to save the uploaded file in a “temp” folder.



   1: protected void AsyncFileUpload1_UploadedComplete(object sender, AjaxControlToolkit.AsyncFileUploadEventArgs e)
   2:     {
   3:         if (AsyncFileUpload1.PostedFile != null)
   4:         {
   5:             string uploadedFileName = Path.GetFileName(AsyncFileUpload1.FileName);
   6:             AsyncFileUpload1.SaveAs(Server.MapPath("~/temp/") + uploadedFileName);
   7:             imgPhoto.ImageUrl = "~/temp/" + uploadedFileName;
   8:             HttpPostedFile file = AsyncFileUpload1.PostedFile;
   9:  
  10:             byte[] data = ReadFile(file);
  11:             //Store the image in the session.
  12:             Session["STORED_IMAGE"] = data;
  13:         }
  14:     }


In the above code, the user selected file will be stored in server temp folder and also image control’s “IamgeUrl” is also set to the uploaded file.


Problem: Here there is no problem with file uploading, but the uploaded image will not be displayed to the user even image control’s path is changed. This is because of AJAX AsyncFileUpload control.


Solution: Image Preview before uploading


After the file is uploaded instead of saving it in the location in the server instead HttpHandler can be used which can write the uploaded image directly into the response. so no storage and less headaches.


So we don’t need lines from 5 to 8 in the above snippet. We need to have only lines 10 to 12.


i. Store the uploaded image into Session in binary format.


if (asyncFileUpload.PostedFile != null)
{
HttpPostedFile file = asyncFileUpload.PostedFile;

byte[] data = ReadFile(file);
//Store the image in the session.
Session["STORED_IMAGE"] = data;
}


Below is the code for converting image into binary format.


private byte[] ReadFile(HttpPostedFile file)
{
byte[] data = new Byte[file.ContentLength];
file.InputStream.Read(data, 0, file.ContentLength);
return data;
}



ii. After image is uploaded need to call an image handler to generate image preview. For this I am using client side event “OnClientUploadComplete” of AsyncFileUpload control.


Below is the javascript


<script language="javascript" type="text/javascript">
function getRandomNumber() {
var randomnumber = Math.random(10000);
return randomnumber;
}

function OnClientAsyncFileUploadComplete(sender, args) {
var handlerPage = '<%= Page.ResolveClientUrl("~/ImageHandler.ashx")%>';
var queryString = '?randomno=' + getRandomNumber();
var src = handlerPage + queryString;

var clientId = '<%=imgPhoto.ClientID %>';
document.getElementById(clientId).setAttribute("src", src);
}
</script>


Note: Use the randomnumber in the querystring of the handler otherwise, browser caches the first image and returns the same image again and again.


iii. Create the HttpHandler which is using (ImageHandler.ashx) in the above javascript


public class ImageRequestHandler : IHttpHandler, IRequiresSessionState
{
public void ProcessRequest(HttpContext context)
{
context.Response.Clear();

if (context.Request.QueryString.Count != 0)
{
//Get the stored image and write in the response.
var storedImage = context.Session["STORED_IMAGE"] as byte[];
if (storedImage != null)
{
Image image = GetImage(storedImage);
if (image != null)
{
context.Response.ContentType = "image/jpeg";
image.Save(context.Response.OutputStream, ImageFormat.Jpeg);
}
}
}
}

private Image GetImage(byte[] storedImage)
{
var stream = new MemoryStream(storedImage);
return Image.FromStream(stream);
}

public bool IsReusable
{
get { return false; }
}
}


Note: Use the IRequiresSessionState to access the session variables in the Handler.

In the above code image preview will be generated based on the binary data stored in Session variable (step 3).

Happy coding…

1 comment:

  1. Thank you so much for this.
    Is it possible to retain the image in the image control after clicking any other button?(I mean on postback)

    ReplyDelete