Link to home
Start Free TrialLog in
Avatar of xbrady
xbrady

asked on

Display Images in a DataGrid Directly from Memory

I'm using the code from the following article found here: http://www.eggheadcafe.com/articles/20050911.asp
I'm using the ImageControl class to create an image generated from memory. When the page first loads it works perfectly. Now I'm trying to place the ImageControl inside an Update Panel and update the image based on what is typed an then submitted from a TextBox. The image on the client isn't being updated to the newly generated bitmap. I'm guessing this is because the URL stays the same. I've dumbed down the example a little bit to make it easier to look and placed it in the code snippet area. I basically just removed the option of storing the bitmap in cache.
When debugging the code gets to the following line:
if(httpRequest.Params["ImageControl_" + UniqueID] != null)
and there is never an ImageControl_UniqueID in the httprequest parameters on postback.
public enum ImageType
	{
         Gif,
		 Jpeg
	}
	 
	[Designer("PAB.WebControls.ImageControlDesigner"),ToolboxDataAttribute("<{0}:ImageControl Runat=\"server\"></{0}:ImageControl>")]
    public class ImageControl : Control
    {
        protected string ImageUrl;		
	 	private ImageType imageType;
		[Description("Image Type")]
		[Category("Data")]
		[DefaultValue("Gif")]
		[Browsable(true)]
		public ImageType ImageType
		{
			get
			{				 
				return imageType;
			}
			set
			{
				imageType = value;
			}
		}
 
 
		private Bitmap _bitmap;
        [Browsable(false)]
        public Bitmap Bitmap
        {
            get
            {
                return (Bitmap)Context.Session[CreateUniqueIDString() + "Bitmap"];
            }
            set
            {
                Context.Session[CreateUniqueIDString() + "Bitmap"] = value;
			}
        }
 
        private string CreateUniqueIDString()
        {			
            String idStr = "__" + Context.Session.SessionID.ToString() + "_";
 
            idStr += UniqueID + "_" + Page.ToString() + "_";		 
            return idStr;
        }
 
        protected override void OnInit(EventArgs e)
        {
            if (!DesignMode)
            {
                HttpRequest httpRequest = Context.Request;
                HttpResponse httpResponse = Context.Response;
                if (httpRequest.Params["ImageControl_" + UniqueID] != null)
                {
                    httpResponse.Clear();
                    if (this.ImageType == ImageType.Gif)
                    {
                        httpResponse.ContentType = "Image/Gif";
                        ImageHandler handler = new ImageHandler(Bitmap, "Image/Gif");
                        handler.ProcessRequest(HttpContext.Current);
                    }
                    else
                    {
                        httpResponse.ContentType = "Image/Jpeg";
                        ImageHandler handler = new ImageHandler(Bitmap, "Image/Jpeg");
                        handler.ProcessRequest(HttpContext.Current);
                    }
                    // httpResponse.End();
                }
                string str = httpRequest.Url.ToString();
                if (str.IndexOf("?") == -1)
                {
                    ImageUrl = str + "?ImageControl_" + UniqueID + "=1";
                }
                else
                {
                    ImageUrl = str + "&ImageControl_" + UniqueID + "=1";
                }
            }
        }
		
        protected override void Render(HtmlTextWriter output)
        {
            output.Write("<img id={0} src={1}>",  this.UniqueID, ImageUrl);
        }
    }
 
	public class ImageControlDesigner: System.Web.UI.Design.ControlDesigner
	{
		public ImageControlDesigner(){}
		public override string GetDesignTimeHtml()
		{
			 return  GetEmptyDesignTimeHtml ();
		}
 
		protected override string GetEmptyDesignTimeHtml()
		{ 
			return CreatePlaceHolderDesignTimeHtml( "<div>[Image is set at runtime. Place control inside <BR>Table TD or DIV for absolute positioning.]</div>");
		}
	}

Open in new window

ASKER CERTIFIED SOLUTION
Avatar of jimbobmcgee
jimbobmcgee
Flag of United Kingdom of Great Britain and Northern Ireland image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
If you are trying to make a self-contained control, you do not need to create a .ashx file.  You will need to create a class that implements IHttpHandler and add a path/handler pair to the web.config.  The ashx file just makes this process a bit simpler.

Note that my code above will need some work towards thread safety, especially if used in a web farm/garden (public static is not guaranteed to be thread or process-safe).  It is intended for demonstration only.

J.
Avatar of xbrady
xbrady

ASKER

Sorry, I didn't post all of the code that I'm using. I'm also using an IHttpHandler class. I'm using the one right from the article:
public class ImageHandler : IHttpHandler
    {
	    private Bitmap _bmp;
	    private string _contentType;
 
        public ImageHandler(){}
 
	    public ImageHandler(Bitmap bmp, string contentType)
	    {
		    this._bmp = bmp;
		    this._contentType = contentType;
	    }
 
	    public bool IsReusable
	    {
		    get
		    { 
                return true; 
            }
	    }
 
        public void ProcessRequest(HttpContext context)
        {
            if (_bmp != null)
            {
                context.Response.ContentType = this._contentType;
                ImageFormat fmt = ImageFormat.Jpeg;
                if (this._contentType == "Image/Gif") fmt = ImageFormat.Gif;
                this._bmp.Save(context.Response.OutputStream, fmt);
                context.Response.End();
            }
        }
    }

Open in new window

Avatar of xbrady

ASKER

One other piece of information. I titled my question wrong. All I have on the form right now is a textbox, a button and then that custom image control. I type something in the textbox and press the button and the code behind generates a new image and is supposed to update on the page. All of this is in an UpdatePanel. I put datagrid in the title just because that was in the title of the article. Sorry for the confusion.
The principal is the same, you need an HttpHandler to handle the request for a memory image.  Whether the textbox, button and Image control are in an UpdatePanel or not should make no difference.

You will need to move the code I specified in Page_Load to the Load event handler for your UpdatePanel, but that's about it.

When the button is clicked, the UpdatePanel is submitted and calls its server-side Load method, which generates another Image, stores it in the ImageHandler.CachedImages collection and updates the Image control's ImageUrl property to reference the handler/GUID combination.
If you look at it, you should see similarities between my code and the code you are using.  I don't know, however, where your code is storing the in-memory image data.  It appears to expect that the image is passed to it in the constructor.  I'm not sure how you would get it to do that.

I would replace your ImageControl derivation with a normal <asp:Image> tag and do all of the caching work in the HttpHandler.  If you use an ASHX file (Add, New Item, Generic Handler) it may be easier for you to register it, rather than getting into web.config manipulation.
Avatar of xbrady

ASKER

Thanks, I've got to go but I'll try this in a few hours when I get back.
Avatar of xbrady

ASKER

Wow, thank you very much. Your first example worked perfectly! How should I modify that example so it isn't using an un-threadsafe approach? Thanks again!
Avatar of xbrady

ASKER

Thanks. I honestly didn't think I was going to get this problem resolved.
>> How should I modify that example so it isn't using an un-threadsafe approach?

At a minimum, you will want to lock access to the underlying Dictionary<Guid, Image> object.  You may want to replace the Dictionary<Guid, Image> object itself, with a Hashtable.Synchronized(new Hashtable()) as this is supposed to be a thread-safe version.

If you stick with the Dictionary<Guid, Image>, I'd remove the public getter and create AddImage, RemoveImage and GetImage methods directly in your handler.

Thread-safety is one of those dark arts that I still haven't fully mastered yet, so YMMV, but I reckon it would look something like this:
public class ImageHandler : IHttpHandler
{
    private static readonly object syncObj = new object();
    private static readonly Dictionary<Guid, Image> images = new Dictionary<Guid, Image>();
 
    public static void AddImage(Guid g, Image i)
    {
        lock (syncObj) { images[g] = i; }
    }
    public static void RemoveImage(Guid g)
    {
        lock (syncObj) { images.Remove(g); }
    }
    public static Image GetImage(Guid g)
    {
        lock (syncObj) { return images[g]; }
    }
 
    public void ProcessRequest(HttpContext context)
    {
        if (!string.isNullOrEmpty(context.Request.QueryString["guid"]))
        {
            context.Response.ContentType = "image/jpeg";
            Guid g = new Guid(context.Request.QueryString["guid"]);
            if (CachedImages.ContainsKey(g))
            {
                Image i = CachedImages[g];
                i.Save(context.Response.OutputStream, ImageFormat.Jpeg);
                i.Dispose();
                CachedImages.Remove(g);
            }
            else context.Response.StatusCode = 404;
        }
        else context.Response.StatusCode = 500;
        context.Response.End();
    }
}

Open in new window