9
\$\begingroup\$

I am trying to convert a BLOB that is stored in a database to a JPG and then I need to display it on an html form in a C# application.

A little backstory first - this is an older web app that was designed to take a JPG (photo) that was stored as a path in the database, resize it, then show as a BLOB. This functionality needs to stay in place, but now one of the systems is changing and the photo is going to be stored in the database as a BLOB instead of a path to a photo (\servername\images\xyz.jpg).

One system still needs the image to be resized then sent back as a BLOB to display. The other systems that call this web-app will need the BLOB converted to a JPG to display.

The code below works, but I think it might have put an unnecessary load on the server, because this morning the system was laggy and some features were impossible to use. I have a feeling that the .Save is the troublemaker here since I am calling it twice. I know the image doesn't need to be saved anywhere physically, as long as the web-app can call and have it display to the site. Is there anything I can change here to skip saving an image on the server and just display it so it doesn't use up a ton of resources?

protected void Page_Load(object sender, EventArgs e)
{
   byte[] byteImage = null;
    string ID = Request.QueryString["id"];
    string imageFile = "\\\\server\\c$\\inetpub\\wwwroot\\web-app\\images\\" + ID+ ".jpg";
    string server = Request.ServerVariables["server_name"];
    var serverNameList = db.Servers.Select(x => x.ServerName).ToList();

    connection.Open();
    SqlCommand sqlCommand = new SqlCommand("sp", connection);
    sqlCommand.Parameters.Add("@ID", SqlDbType.VarChar);
    sqlCommand.Parameters["@ID"].Value = ID;
    sqlCommand.CommandType = CommandType.StoredProcedure;
    SqlDataReader dr = sqlCommand.ExecuteReader();                               

    while (dr.Read())
    {
        byteImage = (byte[])dr["BLOB"];
    }

    if (byteImage != null)
    {
        using (MemoryStream ms = new MemoryStream(byteImage))
        {
            //saving to jpg image
            Image img = new Bitmap(ms);
            img.Save(imageFile, ImageFormat.Jpeg);

            Response.Clear();
            Response.CacheControl = "public";
            Response.Cache.SetExpires(DateTime.Now.AddDays(1.0));
            Response.ContentType = "Image/jpeg";
               
            if (serverNameList.Contains(server)) // if user is coming from a specific server, resize the image then send back
            {
                byte[] resizedImage = GetResizedImage(imageFile, 300, 400);
                Response.OutputStream.Write(resizedImage, 0, resizedImage.Length);
            }
            else // skip resize and show image as jpg
            {
                Response.BinaryWrite(byteImage);
            }
        }
    }
    else // image is null
    {
        imageFile = HttpRuntime.AppDomainAppPath + "PERSON.GIF";
    }         
}
    
private byte[] GetResizedImage(string path, int width, int height)
{
    Bitmap bitmap1 = new Bitmap(path);
    double height1 = bitmap1.Height;
    double width1 = bitmap1.Width;
    double num = 1.0;
    if (width > 0)
        num = width / width1;
    else if (height > 0)
    num = height / height1;
    MemoryStream memoryStream = new MemoryStream();
    Bitmap bitmap2 = new Bitmap((int)(width1 * num), (int)(height1 * num));
    bitmap2.SetResolution(150f, 150f);
    Graphics graphics = Graphics.FromImage(bitmap2);
    graphics.CompositingQuality = CompositingQuality.HighQuality;
    graphics.Clear(Color.White);
    graphics.DrawImage(bitmap1, new Rectangle(0, 0, (int)(num * width1), (int)(num * height1)), new Rectangle(0, 0, (int)width1, (int)height1), GraphicsUnit.Pixel);
    bitmap2.Save(memoryStream, ImageFormat.Jpeg);
    return memoryStream.ToArray();
}
\$\endgroup\$

2 Answers 2

8
\$\begingroup\$

Welcome to CodeReview! I hope you get some good feedback.

Absolute paths

string ID = Request.QueryString["id"];
string imageFile = "\\\\server\\c$\\inetpub\\wwwroot\\web-app\\images\\" + ID+ ".jpg";

This has a few issues. First, it's typical for a server to be in a "jail" where it shouldn't have access to the filesystem at large, and should only access data from its own local directory. This is for security purposes. Though I haven't done this in a long time, it seems the ASP.NET way of safely accessing such paths is MapPath.

Second: it's crucially important that you do some validation on ID. In its current state a hacker could easily construct a path navigation exploit where the ID contains ..\..\.. (etc) to explore the rest of the filesystem if the server is misconfigured.

Managing your connection

If connection is an SqlConnection, it implements IDisposable, and as such you should put it in a using. The same is true of SqlCommand.

Add-with-value

You should prefer AddWithValue over a separated Add/.Value assignment here;

sqlCommand.Parameters.Add("@ID", SqlDbType.VarChar);
sqlCommand.Parameters["@ID"].Value = ID;

Read-in-loop

This:

while (dr.Read())
{
    byteImage = (byte[])dr["BLOB"];
}

should probably not use a loop. Since it seems you expect only one row returned,

  • Read() once;
  • assert that it returned true;
  • assign byteImage;
  • Read() again;
  • assert that it returned false.

Streamed image creation

GetResizedImage creates a memory stream. It should not. Response already has an OutputStream, and you should be writing to that directly. The one thing to double-check with this approach is that Content-Length is still set correctly, which is probably only possible if BufferOutput is enabled.

Avoiding files

As to your original question, which I never really addressed:

Is there anything I can change here to skip saving an image on the server and just display it so it doesn't use up a ton of resources?

Yes! As long as you get an image from the database, you always Save() it to the disc. However, this file is only used if the server name is approved. Move your save within the name check, and fewer cases will require disc I/O.

Even further: if this is the only code that touches that file, that file should not exist at all and you should not be doing a round trip to the disc. Once you have your img, pass that to GetResizedImage instead of a path string.

Bug?

There is a mechanism here:

imageFile = HttpRuntime.AppDomainAppPath + "PERSON.GIF";

which looks like it's supposed to substitute a default image if none is returned from the database. However, that file is never used. This logic needs to be rearranged so that it is. If you get back a null from the database, load this file into your byteImage.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Thank you! I was able to make all the updates you suggested and the systems are working beautifully together. Can't even tell there is another call there to resize the images. :) \$\endgroup\$
    – Jamie
    Commented Jun 30, 2020 at 17:26
4
\$\begingroup\$

Consider not using System.Drawing to manipulate images in ASP.NET:

Classes within the System.Drawing namespace are not supported for use within a Windows or ASP.NET service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions.1

An alternative could be the new ImageSharp library currently in pre-release or ImageScaler.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks! I will definitely look into using either one of those in the future. I'm not a fan of using this outdated code either. :) \$\endgroup\$
    – Jamie
    Commented Jun 30, 2020 at 17:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.