views:

42

answers:

2

Whenever I try to get a bitmap I have cached from the context I am getting an argument exception. The bitmap is cast from the cached object but it's contents are corrupted.

The exception is thrown on the line

ImageFormat imageFormat = bitmap.RawFormat;

bitmap.RawFormat' threw an exception of type 'System.ArgumentException'

Which just gives me the message 'Parameter not valid.'

When I stick a breakpoint in the code look at the bitmap created from the cache all the properties report back the same exception.

Here's the process request from my handler....

    /// <summary>
    /// Processes the image request.
    /// </summary>
    /// <param name="context">The httpContext handling the request.</param>
    public void ProcessRequest(HttpContext context)
    {
        //write your handler implementation here.
        if (!string.IsNullOrEmpty(context.Request.QueryString["file"]))
        {
            string file = context.Request.QueryString["file"];
            bool thumbnail = Convert.ToBoolean(context.Request.QueryString["thumb"]);
            // Throw in some code to check width and height.
            int width = Convert.ToInt32(context.Request.QueryString["width"]);
            int height = Convert.ToInt32(context.Request.QueryString["height"]);

            // Store our context key.
            string key = file;

            // Strip away our cache data value.
            file = file.Substring(0, file.LastIndexOf("_", StringComparison.OrdinalIgnoreCase));

            OnServing(file);

            try
            {
                //Check the cache for a file.
                Bitmap bitmap = (Bitmap)context.Cache[key];
                if (bitmap != null)
                {
                    ImageFormat imageFormat = bitmap.RawFormat;
                    // We've found something so lets serve that.
                    using (MemoryStream ms = new MemoryStream())
                    {
                        bitmap.Save(ms, imageFormat);
                        context.Response.BinaryWrite(ms.ToArray());
                    }

                }
                else
                {
                    // Nothing found lets create a file.
                    // Lock the file to prevent access errors.
                    lock (this.syncRoot)
                    {
                        string path = HostingEnvironment.MapPath(String.Format("~/Images/{0}", file));
                        FileInfo fi = new FileInfo(path);
                        if (fi.Exists)
                        {
                            using (Bitmap img = (Bitmap)Bitmap.FromFile(path))
                            {
                                ImageFormat imageFormat = img.RawFormat;
                                if (thumbnail)
                                {
                                    ImageEditor imageEditor = new ImageEditor(img);
                                    Size size = new Size(width, height);
                                    imageEditor.Resize(size, true);
                                    imageEditor.FixGifColors();

                                    using (MemoryStream ms = new MemoryStream())
                                    {
                                        imageEditor.Image.Save(ms, imageFormat);

                                        // Add the file to the cache.
                                        context.Cache.Insert(key, img, new System.Web.Caching.CacheDependency(path));
                                        imageEditor.Dispose();
                                        context.Response.BinaryWrite(ms.ToArray());
                                    }
                                }
                                else
                                {
                                    using (MemoryStream ms = new MemoryStream())
                                    {
                                        img.Save(ms, imageFormat);

                                        // Add the file to the cache.
                                        context.Cache.Insert(key, img, new System.Web.Caching.CacheDependency(path));
                                        context.Response.BinaryWrite(ms.ToArray());
                                    }
                                }
                                OnServed(file);
                            }
                        }
                        else
                        {
                            OnBadRequest(file);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
                // OnBadRequest(ex.Message);
                // return a default empty file here.                    
            }

        }

    }

Any help would be greatly appreciated.

+1  A: 

When you cache the image object you are doing so inside a using block:

using (Bitmap img = (Bitmap)Bitmap.FromFile(path))
{
    // ... lots of code

    // Add the file to the cache.
    context.Cache.Insert(key, img, new System.Web.Caching.CacheDependency(path));

    // ... other code
}

At the end of this using block, your bitmap will be disposed. Therefore you can't use it again. You need to stop disposing the bitmap if you want to use it again from the cache.

However, given that you just want to return the same image again if it's cached, it might be simpler and more efficient to just cache the MemoryStream - this doesn't suffer any invisible GDI+ ties and can be returned with almost no processing on a cache hit.

Simon Steele
That doesn't seem to have fixed it. I changed the code and cleared the cache but I'm still getting the same error.
James South
If the Bitmap object doesn't like being cached, instead try caching the MemoryStream - that's probably better anyway.
Simon Steele
That's better. I changed it to cache the memory stream and removed the using statement for the stream my only worry now is that the streams are un-disposed. Do you think the garbage collector will handle that or would a refactor be necessary?
James South
The garbage collector will eventually take care of the MemoryStream, but if you want to take more control over the process then add a callback in Cache.Insert that calls Dispose on the MemoryStream when it's expired from the cache.
Simon Steele
A: 

I can recommend not holding onto resources for longer than they're needed.

As for the code above, the issue is that you aren't creating a new bitmap when you get a saved image - you're retrieving an existing one from the cache, which has probably already been disposed of. Try using the following instead:

// When saving to the cache, use a MemoryStream to save off the bitmap as an array of bytes
using (MemoryStream ms = new MemoryStream()) {
    bitmap.Save(ms, bitmap.RawFormat);
    context.Cache.Insert(key, (byte[])ms.ToArray(), ...
    ...
}

...

// When retrieving, create a new Bitmap based on the bytes retrieved from the cached array
if (context.Cache[key] != null) {
    using (MemoryStream ms = new MemoryStream((byte[])context.Cache[key])) {
        using (Bitmap bmp = new Bitmap(ms)) {
            bmp.Save(context.Response.OutputStream ...
...
Dave R.
That's got it. Working a treat now. Cheers
James South
Excellent! Glad you've got it working :D
Dave R.