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;
}




Thursday, February 14, 2008

Putting HTML code within HTML code

While posting previous entry, I came across the issue of posting HTML code in the blog. Just copying and pasting didn't work quite that well, which was expected.
I ended up putting the code block within < xmp></xmp> tag pair to display the HTML.

Detect if JavaScript is enabled.

I needed a way to detect if Javascript was enabled in the browser, and display a warning if Javascript was disabled. After looking through tons of blog posts, how-to's and documentation on Javascript - I came up with fairly easy solution: to use <noscript> tag.
The code looks as follows:
<script type="text/javascript"></script><noscript>Javascript is disabled.</noscript>Good thing about it - you can put it pretty much anywhere in the page to display the warning.

BizTalk duplicates files in FTP receive adapter.

I ran into the problem of BizTalk duplicating files during FTP transfer, which caused a lot of grief on my side. I thought, our partners were dropping duplicates onto the FTP server, but it turned out BizTalk was duplicating them, during transfer if the file was still being written on the FTP server at the same time as BizTalk tries to pick it up.
It is still rather unclear on behind the scene of how this is happening, but this is something that needs to be addressed. Either by enabling Service Window in the BizTalk FTP receive port, or by storing data in temp file while writing out to FTP, and then renaming it for BizTalk pick up as the last step of the process.

Wednesday, February 13, 2008

BizTalk 2006 Ghost Exceptions

Quite for some time now, I was getting NULL exceptions, which didn't include any messages or exception data whatsoever. This, in its turn was causing the exception handling to fail and throw an unhandled exception. Since it was rather unclear on what was causing this kind of behavior in the first place, it took me a while to figure out the root cause of it.
It turned out - this was caused by the transaction timeout expiring on the "caller" orchestration, before the "callee" orchestration finished its processing.
What I mean by that:
I would usually have one "wrapper"/"caller" orchestration with the long-running transaction in it (timeout set to X seconds), from which I would call the "processing"/"callee" orchestration. "Callee" orchestration would have a long-running transaction in it as well with the timeout set to Y seconds. When I set X too small and/or less than Y, ”caller" orchestration sometimes would force the "callee" to fail with the null exception being thrown, which propagates into "wrapper" orchestration as well. This makes it next to impossible to figure out the cause of failure. I called these exceptions - "ghost exceptions", as they seemed to appear from nowhere.
Another symptom for this is the Error Description - "The instance completed without consuming all of its messages. The instance and its unconsumed messages have been suspended." In this case, orchestration is forced to quit, before all of the messages/responses have arrived.