Wednesday, April 9, 2008

ASP.NET - Javascript Image Upload/Preview without flicker.

UPDATE:: I see a lot of people are struggling with the same kind of
problem that I had, and there is really no working solution on the internet for
the Image Upload Preview, that is also readily available. I had to revisit Image
Upload Preview recently and ended up creating User Control for it. You can read
up on it and download a working solution
here.



---
Recently I had to Preview and Upload images without doing a postback. Majority of solutions that I had found on the web - were crippled, as they worked only on local development servers, and as soon, as the application was moved to the hosting server, it stoped working. No wonder! For the preview to work - the image should be stored "locally" on the hosting server, and most of the solutions - just pulled the URI for image from file upload control and assumed that file was already loaded on the hosting server. It happened to be a wrong assumption!

To solve:
1) I put a hidden iFrame control on the page in order to upload the image file with the "background" postback;
2) Then displayed the uploaded image using Javascript.

ASP.NET code behind uploads the file and returns the file URI to the page for the preview.

Here's a step by step description on how I did it:

At first I added to the page an iFrame control embedded within DIV tag with display set to NONE.
< src="empty.htm" id="hiddenFrame" name="hiddenFrame">< /iframe >

Then attached javascript function to onChange event for the Upload control. All that function does - is reassigns action and target on the form and performs submit():

document.getElementById(form).action = "default.aspx?ImgPreview=true";
document.getElementById(form).target = frameName;
document.getElementById(form).submit();

And then restores the defaults:

document.getElementById("ProfileManager").action = "default.aspx";
document.getElementById("ProfileManager").target = "_self";

ImgPreview query string is passed to ASPX page on the postback, to indicate when to perform a file upload (as we are doing a postback to the same page, there's a need to distinguish image_upload_postback vs. normal_page_display_postback).
In the code behind for that page I put the following in the Page_Load routine

//Upload file on "hidden" postback
if (Page.Request.QueryString["ImgPreview"] != null)
{
DoFileUpload();
}
else
{
//do regular page_load routine
}

DoFileUpload() operates on imgUpload.PostedFile to upload file to the server. File extension, as well as file size verifications can be implemented within DoFileUpload() method.

To get full path to the place where to store file on the server:
//ImageUpload is web folder within the website
string path = Server.MapPath("ImageUpload/");

If upload successful I generate return string as follows:
result = "Result::ImageUpload/" + fileName;

Otherwise:
result = "Error::Unsupported file format (only *.jpeg, *.jpg, *.gif are allowed).";

Then, there are several ways to pass the result string back to the page. Either by using ASP.NET Session variable and Callbacks, or by simply calling Javascript function in parent window(relative to iframe).
Later is a much simpler way: just use Response.Write at the end of DoFileUpload() function to call javascript:

Response.Write("< type="'text/javascript'" language="'javascript'">");
Response.Write(String.Format("parent.FinishImagePreview('{0}');", result));
Response.Write("< /script >");

On the client side Javascript function FinishImageUpload() parses returned string and in case of success - it uses the path to preview the image, or displays an error otherwise.

Here's a listing for DoFileUpload():




protected void DoFileUpload()
{
byte[] buffer = null;
FileStream fs = null;
Bitmap bmp = null;
string result = "";
try
{
deletePreviewImageFromServer(); //delete any previously previewed images from the server
if (imgUpload.PostedFile != null)//make sure posted file is NOT null
{
if (imgUpload.PostedFile.FileName != null && imgUpload.PostedFile.FileName != "") //check for filename
{
string fileExtension = System.IO.Path.GetExtension(imgUpload.PostedFile.FileName); //get extension
if (fileExtension.Equals(".gif") fileExtension.Equals(".jpg") fileExtension.Equals(".jpeg")) //only jpg and gif are allowed
{
string fileName = System.DateTime.Now.ToFileTimeUtc().ToString() + System.IO.Path.GetFileName(imgUpload.PostedFile.FileName); //prefix filename with datetime, to make it unique
string path = Server.MapPath("ImageUpload/"); //prefix path to ImageUpload folder on the server
string fullPath = path + fileName; //set full path
//Write a new file on the server, for later preview from the client
HttpPostedFile File = imgUpload.PostedFile; //posted file handle
int len = File.ContentLength;
if (len <= maxFileSize)//verify that file doesn't exceed max allowed
{
//read file into the buffer
buffer = new Byte[File.ContentLength];
File.InputStream.Read(buffer, 0, File.ContentLength);
fs = new FileStream(fullPath, FileMode.Create);
fs.Write(buffer, 0, len);
bmp = new Bitmap(fs); //read buffer into bitmap
if (fileExtension.Equals(".gif"))
{
bmp.Save(fs, ImageFormat.Gif);//save gif
}
else
{
bmp.Save(fs, ImageFormat.Jpeg);//save jpeg
}
result = "Result::ImageUpload/" + fileName; //generate string that will be returned to the client
Session["localFileName"] = fullPath;
}
else
{
result = String.Format("Error::File exceeds maximum allowed size ({0}Kb).", (maxFileSize / 1024));
Session["localFileName"] = null;
}
}
else
{
result = "Error::Unsupported file format (only *.jpeg, *.jpg, *.gif are allowed).";
Session["localFileName"] = null;
}
}
}
}
catch (Exception ex)
{
result = String.Format("Error::{0}", ex.Message);
}
finally
{
if (bmp != null) bmp.Dispose();
if (fs != null)
{
fs.Dispose();
fs.Close();
}
//write response, fire FinishImagePreview from the parent, result is passed as a string
//of the follwoing format STATUS::MESSAGE,
//in case of success - Result::[Path to the Uploaded File]
//in case of failure - Error::[Error Message]
Response.Write("<script type='text/javascript' language='javascript'>");
Response.Write(String.Format("parent.FinishImagePreview('{0}');", result));
Response.Write("</script>");
}
}


Previewing the image should be relatively simple - it is just a matter of resizing the image and then reassigning the SRC attribute of <> tag. In Javascript - it should eventually come to something like this:
//filename should have the path pointing to the location on the server, NOT client!
function PreviewImage(filename){
var oldImage = document.getElementById(imageHolder)//imageHolder is image tag id
var newImage = new Image();
newImage.src = filename;
var x = parseInt(newImage.width);
var y = parseInt(newImage.height);
if (x>maxWidth) {
y*=maxWidth/x;
x=maxWidth;
}
if (y>maxHeight) {
x*=maxHeight/y;
y=maxHeight;
}
oldImage.src = newImage.src;
oldImage.width = x;
oldImage.height = y;
}




13 comments:

Unknown said...

Is it require write permission to sever directory?
Is it possible to do preview without save to server disk?

kazuLjudi said...

Oleg I loved your article though cause I lack knowledge of JS couldn't apply it as you wrote and have ideas but still on client side with JS cannot trigger my C# methods to read FileStream file and with GenericHandler to write it back on Change...
Like beginner I am ,I did put it inside in_put...JS on Change triggering myC#Method reading it to binaryarray and writting to Data Base again reading it and writting to the page...
but still nothing, if it does not bothers you , please replay!!!
Thanks in advance...
Igor

Oleg said...

As much as I can recall - application identity should have write permissions to the Image folder.
Unfortunately I'm not aware of the method to preview without uploading the image on the server first.
Now, if every web page was allowed to pull file from you client machine and try to preview it (or some other funny stuff)- I doubt you'd very happy about it.

Oleg said...

kazuljudi: I couldn't follow - where the problem was. I probably misreading, but you cannot directly envoke a code behind method from the Javascript.

Here's step by step:
1. Embed iFrame:
< div id="fileUpload" style="display: none;">
< iframe src="empty.htm" id="hiddenFrame" name="hiddenFrame">< /iframe >
< /div >
2. Attach onchange handler to imgUpload - HtmlInputFile control:
imgUpload.Attributes.Add("onchange", "DoFileUpload();");
3. In aspx page add Javascript for DoFileUpload() function:
document.getElementById(form).action = "default.aspx?ImgPreview=true";
document.getElementById(form).target = frameName;
document.getElementById(form).submit();

document.getElementById("ProfileManager").action = "default.aspx";
document.getElementById("ProfileManager").target = "_self";
4. Handle DoFileUpload (listing is in the original post)
5. Add Javascript in aspx page to handle FinishImagePreview(status) function. a) validate returned status, b) parse the status string for path to the picture on the server, c)depending on the status, either preview, or display error:
function FinishImagePreview(status){

var result =status;
var rsltArray = new Array();
rsltArray = result.split("::"); //split result string
if (rsltArray[0] == "Result"){ //Result OK
var newPath = rsltArray[1];
if (!isFirefox){ //Fading in/out only works in IE
preview(newPath); //preview image, using server path returned by postback
}else{
document.getElementById(outImage).src=newPath;
}

}else if (rsltArray[0] == "Error"){ //Result Error
var uploadE=document.getElementById("imgUpload");
if (isFirefox)
{
document.getElementById("lblImageUploadWarning").textContent = rsltArray[1];
}
else
{ //clear the upload input
uploadE.parentElement.replaceChild(uploadE.cloneNode(true),uploadE);
document.getElementById("lblImageUploadWarning").innerText = rsltArray[1];
}
}

nrctnkill said...

can u upload the sample project...to nrctnkill@gmail.com

SeM said...

Hi, I'm using the same technique for file uploads. But sometimes regular postback goes before postback for iframe. Did you have the same behaviour?

Oleg said...

No I haven't had that problem. Are you setting TARGET to the hidden fram correctly? Are you kicking out postback in any other places?

You can download example solution from my new post: ASP.NET Image Upload Preview (Take 2)

sandeep said...

In my registration page i have fileupload and image preview functionality but that page is cheild page because in my application i am using master page functionality so i am not getting what i have to mention in the place "TargetFormID=......" that's whay i am getting the error "hform is null" please give me some suggestion waht to do...
thanks.

Oleg said...

I'm not following - where exactly the problem is? Did you try using the above Custom Control?
I haven't tested this method with Master pages, so I wouldn't know how it'd behave in this case.

sandeep said...

Yes i am doing the same way as you mention in your block means using the Custom Control.And i tested this code in other application where no master and cheild pages ,then it works fine but when i use the master page in my form then am getting error "hform is null". And i mentioned the form name like this "TargetFormID=MasterFormName".
So what to do for this issue please suggest me.

Thanks.

sandeep said...

Hi,
From last few days i am stuck with my problem could you please help me for my above problem because i need it.because my java script is not working in firefox it is working with only below the IE6 so i need it because this is working in both the browser but in case of master page this is not working so please help me....
thanks.

krishna kumar said...

Can u just help me out on how can I crop the images selected and then send it to the server to save it.
Probably only the selected area is what I require.
Any help on this.
Thanks
and a very great control

mohnish said...

I am trying to download the code file but not able to do the same. Could you send the source code in my mail id mohnish8683@gmail.com

Thanks,