Display Images in a DataGrid Directly from Memory

xbrady
xbrady used Ask the Experts™
on
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

Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®
This is for an ASP.NET web page?  If so, then you cannot directly send image data from memory as part of an Page.  You must send the Page with an img tag (or asp:Image) whose href is another page, whose job it is to pull that data from memory.  This is typically done with an IHttpHandler implementation and will require the following steps:

  1.  Persist the image data to a Dictionary<Guid, Image> object using a unique GUID (Guid.NewGuid())
  2.  Set the href/ImageUrl property of your image to a URL referencing the GUID (e.g. ~/ImageHandler.ashx?guid=xxxxxxxx-xxxx-xxxx-xxxxxxxx
  3.  Create an HTTP Handler class (ImageHandler.ashx) to spool the image data and remove the dictionary entry

Sample code (untested) follows:
-- IN ImageHandler.ashx
public class ImageHandler : IHttpHandler
{
    private static readonly Dictionary<Guid, Image> images = new Dictionary<Guid, Image>();
 
    public static IDictionary<Guid, Image> CachedImages { get { return images; } }
 
    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();
    }
}
 
-- IN YOUR PAGE
protected void Page_Load(object sender, EventArgs e)
{
    Image i = GenerateMyImage();
    Guid g = Guid.NewGuid();
    ImageHandler.CachedImages.Add(g, i);
    myImageControl.ImageUrl = string.Concat("~/ImageHandler.ashx?guid=", g);
}

Open in new window

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.

Author

Commented:
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

Expert Spotlight: Joe Anderson (DatabaseMX)

We’ve posted a new Expert Spotlight!  Joe Anderson (DatabaseMX) has been on Experts Exchange since 2006. Learn more about this database architect, guitar aficionado, and Microsoft MVP.

Author

Commented:
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.

Author

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

Author

Commented:
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!

Author

Commented:
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

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial