Monday, September 27, 2010

How to show the selected image file without saving file in the disk before upload


Demo:
 
I was thinking a way to show images before actually uploading them to server. I would say to preview images using javascript. Obviously asp.net file input control does not like post-backs when it has a selected file. This idea is to preview the selected image by saving the file as byte[] array in the session and assigning the file to a src (ImageUrl) of a image element(asp.net Image) in a subsequent request. I have used jQuery to simplify the battle with asp.net rendered domain model. First hurdle, simple jQuery ajax didnt posted my selected image file. So I needed to find a way to post my selected file, then found a nice little plugin so called jQuery AjaxForm. This plugin nicely did the trick. I manage to change the form action for a bit and post the form to GenericHanlder. As soon I found the posted file, I get the converted file in to a System.Drawing.Image and saved it in the session. Finally carefully reverted the asp.net from action to it's original value.
HttpPostedFile file = context.Request.Files[0];
image = Bitmap.FromStream(file.InputStream);
context.Session["image"] = image;
If we request GenericHanlder again, it can retreive the image from session and serve as an image. So what I did was in the success event of jQuery AjaxForm post, assign the GenericHandler.ashx to src of the preview image. As a result, there will be a second subsequent request just after the first form post to the Generic Hander. Now I distinguish the two requests and for the second request I serve the image from the session after converting image object in to a byte[] array using a momery stream.
MemoryStream stream = new MemoryStream();
image.Save(stream, format);
context.Response.BinaryWrite(stream.ToArray());
Download jQuery Ajax Form
Download jQuery
Memeory Consumption with heavy load:
Saving the image in the session is only between two consecutive requests. First request creates the image and save it in the session. Then the second request consumes the image, remove the image from session and dispose it. So memory use of the server is instantaneous. Producer consumer fashion.

Markup:
<%@ Page Language="C#" %>
<html>
<head id="Head2" runat="server">
    <script src="Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
    <script src="Scripts/jquery-form.js" type="text/javascript"></script>
    <script language="javascript" type="text/javascript">
        function ShowFile() {
            var formId = '<%= this.Form.ClientID %>';
            var imageId = '<%=this.imageView.ClientID %>';
            var fileUploadId = '<%=fuFileUpload.UniqueID %>';
            var r = Math.floor(Math.random() * 999999);
            var action = $('#' + formId).attr('action');
            $('#' + formId).attr('action''GenericHandler.ashx?f=' + fileUploadId);
            $('#' + formId).ajaxForm(function () {
                $('#' + imageId).attr('src''GenericHandler.ashx?s=' + fileUploadId + '&r=' + r);
                $('#' + imageId).show();
                $('#' + formId).attr('action', action);
            });
            $('#' + formId).submit();
        }
    </script>
</head>
<body>
    <form id="form2" runat="server">
    <asp:FileUpload runat="server" ID="fuFileUpload" onchange="javascript:ShowFile();" />
    <asp:Button ID="Button1" runat="server" Text="Save" />
    <hr />
    <asp:Image ID="imageView" runat="server" alt="Thumbnail" Style="displaynone" />
    </form>
</body>
</html>

Generic Handler:
public class GenericHandler : IHttpHandlerIRequiresSessionState
{
    public void ProcessRequest(HttpContext context)
    {
        string f = context.Request.QueryString.Get("f");
        string s = context.Request.QueryString.Get("s");
        if (!string.IsNullOrEmpty(f))
        {
            HttpPostedFile file = context.Request.Files[f];
            if (file == null)
                HttpContext.Current.ApplicationInstance.CompleteRequest();
            else
            {
                List<string> keys = new List<string>();
                foreach (string key in context.Session.Keys) if (key.StartsWith(f)) keys.Add(key);
                foreach (string key in keys) context.Session.Remove(key);
                System.Drawing.Image image = Bitmap.FromStream(file.InputStream);
                context.Session[f + "image"] = image;
                context.Session[f + "contextType"] = context.Request.Files[0].ContentType;
                context.Response.Flush();
                HttpContext.Current.ApplicationInstance.CompleteRequest();
            }
        }
        else if (!string.IsNullOrEmpty(s))
        {
            string ck = s + "contextType";
            string ik = s + "image";
            if (context.Session[ck] == null || context.Session[ik] == null)
            {
                HttpContext.Current.ApplicationInstance.CompleteRequest();
                if (context.Session[ck] != null) context.Session.Remove(ck);
                if (context.Session[ik] != null) context.Session.Remove(ik);
            }
            else
            {
                using (System.Drawing.Image image = (System.Drawing.Image)context.Session[ik])
                {
                    context.Response.Clear();
                    context.Response.ClearHeaders();
                    string type = context.Session[ck].ToString().ToLower();
                    System.Drawing.Imaging.ImageFormat format = 
                                                            System.Drawing.Imaging.ImageFormat.Gif;
                    bool isValid = true;
                    if (type.Contains("bmp")) format = System.Drawing.Imaging.ImageFormat.Bmp;
                    else if (type.Contains("jpg") || type.Contains("jpeg")) format = 
                                                            System.Drawing.Imaging.ImageFormat.Jpeg;
                    else if (type.Contains("png")) format = System.Drawing.Imaging.ImageFormat.Png;
                    else if (type.Contains("gif")) format = System.Drawing.Imaging.ImageFormat.Gif;
                    else if (type.Contains("wmf")) format = System.Drawing.Imaging.ImageFormat.Wmf;
                    else if (type.Contains("tiff") || type.Contains("tif")) format = 
                                                            System.Drawing.Imaging.ImageFormat.Tiff;
                    else if (type.Contains("exif")) format = System.Drawing.Imaging.ImageFormat.Exif;
                    else isValid = false;
                    if (isValid)
                    {
                        using (MemoryStream stream = new MemoryStream())
                        {
                            image.Save(stream, format);
                            context.Response.BinaryWrite(stream.ToArray());
                            context.Response.ContentType = type;
                        }
                    }
                    context.Session.Remove(ck);
                    context.Session.Remove(ik);
                    HttpContext.Current.ApplicationInstance.CompleteRequest();
                }
            }
        }
    }
    public bool IsReusable
    {
        get
        {
            return false;
        }
    }
}

References:
Many thanks to following people for their work, without them I would not have achive this.
  1. jQuery Form:
  2. Converting image to a byte stream:
  3. Generating random number usign java script:

14 comments:

Allan said...

Only have one problem, when the image is more 100k not it display

Luke said...

You are the man, thanks for this awesome code!

Jao said...

I'm using ASP.NET MVC and I wanted to have the same functionality. Could you lend me a helping hand?

Charith Shyam Gunasekara said...

Sure why not, will pushlish the same article with MVC - please contact me with your email, then will let you know when it's done. - Thanks

Jao said...

You could use my gmail account. agentjao@gmail.com Please don't mind my email prefix. It's just that I'm fond of secret agent movies, haha.

Thanks in advance.

Nelson said...

Good evening friend I hope you are well.

Your post is excellent, I really would like to implement. however
I'm following your post and there are some things that I could not solve, I
displays the image.

Can you please attach the file of the demo?
or you can send them to ngomezleal@hotmail.com?

I appreciate all the support.

Nelson said...

Hello friend I hope you are well.

Friend excellent post you have posted.
I have been following and trying to do what you have accomplished but to no avail
optimal.
Can you please send me this little demo you've done to my address
mail? ngomezleal@hotmail.com

Or you can attach the demo to download?

I await your valuable feedback friend.

happy Night

Nelson said...

Amigo he creado un GenericHandler.ashx
dentro del mismo agregue todo el codigo que tienes en la pagina.

Pero al ejecutarlo me da un error aqui:

foreach (string key in context.Session.Keys) if (key.StartsWith(f)) keys.Add(key);

Especificamente en:

context.Session.Keys

Me dice:
Referencia a objeto no establecida como instancia de un objeto.

Que esta pasando??

Nelson said...

I created a GenericHandler.ashx Friend
within the same add all the code you have on the page.

But when I run I get an error here:

foreach (string key in context.Session.Keys) if (key.StartsWith (f)) keys.Add (key);

Specifically in:

context.Session.Keys

He says:
Object reference not set to an instance of an object.

What's going on?

Ran Fa said...

Thank you for the code, very useful.

Lavakumar said...

Hi Charith,

This helped me a lot.

But once that Javascript function onchange="javascript:ShowFile();" is called. Mu current page is not rendering for further operations. Can u suggest something to overcome this?

Lavakumar

Anonymous said...

I love reading a post that can make men and women think.
Also, thank you for allowing me to comment!
Also see my page :: a2 poster printing

carlosnoe_20 said...

Thank you, very good!!

carlosnoe_20 said...

thank you, very, very good!!

iPhone Launch Screen Sizes

iPhone Portrait iOS 8 Retina HT 5.5 = 1242 X 2208 Retna HD 4.7 = 750 X 1134 iPhone Landscape iOS 8 Retina HD 5.5  2208 X 1242 iPho...