HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Why is loading images taking so long?

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
whylongloadingimagestaking

Problem

When Page_Load is executed I assign the ImageUrl which is a IHTTPHandler:

imgView.ImageUrl = "ImageHandler.ashx" + "?rezeptId=" + rezeptId.ToString() +"&imageId=0";


Which looks like this:

public class ImageHandler : IHttpHandler {

    public void ProcessRequest (HttpContext context) {

        int rezeptId = Int32.Parse(context.Request.QueryString["rezeptId"]);
        int imageId = Int32.Parse(context.Request.QueryString["imageId"]);

        List images = DBManager.GetRezeptImages(rezeptId);
        context.Response.ContentType = "image/png";
        context.Response.BinaryWrite(Helper.ImageToByteArray(images[imageId]));
    }

    public bool IsReusable {
        get {
            return false;
        }
    }

}


The handler call is a static method from DBManager, a custom static class which provides LINQ-To-SQL queries. The method is called GetRezeptImages():

public static List GetRezeptImages(int rezeptId)
    {
        using (CookBookDataContext ctx = new CookBookDataContext(Resources.ResourceFile.DBConnection))
        {
            IEnumerable bilder = from b in ctx.RezeptBilders where b.FKRezept == rezeptId select b;
            List imageList = new List();

            foreach (RezeptBilder b in bilder)
            {
                imageList.Add(Helper.ByteArrayToImage(b.Bild.ToArray()));
            }

            return imageList;
        }
    }


This method simply loads image binaries from the database and converts them to Images:

public static Image ByteArrayToImage(byte[] byteArrayIn)
{
    using (MemoryStream ms = new MemoryStream(byteArrayIn))
    {
        Image returnImage = Image.FromStream(ms);
        return returnImage;
    }
}


So basically this is all that happens when loading images. It is possible that GetRezeptImages() will return a List with more than one image but it is always only one that is loaded to image control.

But now I am experiencing some delays when loading an image:

Now i wo

Solution

So, let us see what you are doing.

You query your database for IEnumerable and iterate over the enumeration where you are converting the items to Image and add it to a List.

Then you are converting the Image back to a Byte[] to write it into the response stream.

Byte[] -> Image -> Byte[] isn't really necessary.

As you don't use methods of the List but rather of the interface IList we will return an IList as it is always better to code against an interface instead of an implementation.

public static IList GetRezeptImages(int rezeptId)
{
    using (CookBookDataContext ctx = new CookBookDataContext(Resources.ResourceFile.DBConnection))
    {
        IEnumerable bilder = from b in ctx.RezeptBilders where b.FKRezept == rezeptId select b;
        IList imageList = new List();

        foreach (RezeptBilder b in bilder)
        {
            imageList.Add(b.Bild.ToArray());
        }

        return imageList;
    }
}


and to be called

public void ProcessRequest (HttpContext context) 
{
    int rezeptId = Int32.Parse(context.Request.QueryString["rezeptId"]);
    int imageId = Int32.Parse(context.Request.QueryString["imageId"]);

    IList images = DBManager.GetRezeptImages(rezeptId);
    context.Response.ContentType = "image/png";
    context.Response.BinaryWrite(images[imageId]);
}


But this shouldn't fix your issue. Taking 28 seconds for accessing 1.3 MB comes down to about 48 Kbyte/s which is quit slow.

To check where the most time is used, you need to measure the time it takes to execute the call to GetRezeptImages().

Smaller images will for sure speed the whole thing up and therefor makes the user of the website happy.

General

What will happen if a user of the site is changing the image url inside the address bar like ImageHandler.ashx?rezeptId=testForTryParse&imageId=0 ?

Your ProcessRequest() method would throw an exception. You should carefully validate the input. This can be done in this case by using Int32.TryParse() instead of Int32.Parse().

Also you will get an exception, if the requested imageId is greater than the count of the IList.

public class ImageHandler : IHttpHandler 
{

    public void ProcessRequest (HttpContext context) 
    {
        int rezeptId = 0;
        int imageId = 0;

        if( Int32.TryParse(context.Request.QueryString["rezeptId"], out rezeptId) &&
             Int32.TryParse(context.Request.QueryString["imageId"], out imageId))
        {

            IList images = DBManager.GetRezeptImages(rezeptId);
            if (images.Count > imageId)
            {
                context.Response.ContentType = "image/png";
                context.Response.BinaryWrite(images[imageId]);
                return;
            }
        }
        // a proper defined error response here    
    }

    public bool IsReusable 
    {
        get 
        {
            return false;
        }
    }

}


Style

You should be consistent with your coding style. It is a matter of taste where to place the opening braces {. Most do it on a new line, some place it on the same line. But you should stick to it.

Code Snippets

public static IList<Byte[]> GetRezeptImages(int rezeptId)
{
    using (CookBookDataContext ctx = new CookBookDataContext(Resources.ResourceFile.DBConnection))
    {
        IEnumerable<RezeptBilder> bilder = from b in ctx.RezeptBilders where b.FKRezept == rezeptId select b;
        IList<Byte[]> imageList = new List<Byte[]>();

        foreach (RezeptBilder b in bilder)
        {
            imageList.Add(b.Bild.ToArray());
        }

        return imageList;
    }
}
public void ProcessRequest (HttpContext context) 
{
    int rezeptId = Int32.Parse(context.Request.QueryString["rezeptId"]);
    int imageId = Int32.Parse(context.Request.QueryString["imageId"]);

    IList<Byte[]> images = DBManager.GetRezeptImages(rezeptId);
    context.Response.ContentType = "image/png";
    context.Response.BinaryWrite(images[imageId]);
}
public class ImageHandler : IHttpHandler 
{

    public void ProcessRequest (HttpContext context) 
    {
        int rezeptId = 0;
        int imageId = 0;

        if( Int32.TryParse(context.Request.QueryString["rezeptId"], out rezeptId) &&
             Int32.TryParse(context.Request.QueryString["imageId"], out imageId))
        {

            IList<Byte[]> images = DBManager.GetRezeptImages(rezeptId);
            if (images.Count > imageId)
            {
                context.Response.ContentType = "image/png";
                context.Response.BinaryWrite(images[imageId]);
                return;
            }
        }
        // a proper defined error response here    
    }

    public bool IsReusable 
    {
        get 
        {
            return false;
        }
    }

}

Context

StackExchange Code Review Q#70496, answer score: 3

Revisions (0)

No revisions yet.