Generating image thumbnails in ASP.NET

Published: 18 Jun 2008
By: Bertrand Le Roy
Download Sample Code

Bertrand Le Roy explains how to generate thumbnails in ASP.NET

Contents [hide]

Introduction

It seems relatively straightforward to generate image thumbnails from ASP.NET using the System.Drawing APIs but it is more difficult to find the right quality settings, to make the method secure and to ensure good performance of the system using caching. In this article, I’ll describe an end-to-end solution to build secure, good quality thumbnails and to cache them.

Images in HTML

It may seem obvious to some of you, but it really isn’t if you’re not too familiar with how a web page and its associated resources are built: a thumbnail image can’t be generated by sending the bytes of the resized image into the page’s response stream.

Note:

Actually, this is not exactly true anymore, now that most modern browsers enable the embedding of a MIME64-encoded binary resource directly into the page using the “data:” protocol.

See http://www.ietf.org/rfc/rfc2397.txt for more information.

In order to embed a resized image into a page, what you really do is put an img tag in the HTML and point its src attribute to a handler that serves the binary stream of the image. Here’s a simple handler that serves the image whose name is passed on the query string parameter p (without resizing it for the moment):

Listing 1: Serving an image from a handler

Consuming the handler is as simple as writing the following img tag:

One thing to note here is that the handler starts by validating the p parameter: user input is evil until proven otherwise, so just validate it. Here, we’re using a white list, rejecting anything that’s not a letter, a digit, an underscore or a slash. This might seem very strict, but white lists are by definition safer than black lists: if you list what you exclude, you have to know all the potential attacks, whereas if you list what you accept you only have to determine a set of harmless characters. Here, we’re allowing only JPEG files that are under the Photo subdirectory of the site. The dot character is a great example of a character we want to exclude as it could allow an attacker to get access to parent directories, but there might be others, escaped versions, etc.

What the handler is doing for the moment is simply set the content type to JPEG and write out the contents of the file directly to the response stream.

Resizing an image using System.Drawing

In order to get a thumbnail of the image, all we need to do is use the very convenient APIs that System.Drawing provides. But first, we need to determine the dimensions of the thumbnail. To do this, we must determine if the original image has a landscape or portrait format. In both cases, the thumbnail needs to fit into a square of predetermined size. In the case of portrait, the height is this predetermined size, in the case of landscape the width is. The other dimension must be reduced with the same proportions.

Listing 2: Determining the thumbnail dimensions

Note:

There seems to be some potential here for integer overflow, but unless your images are really huge, we should be pretty safe.

The thumbnail size should be fixed in advance by the application developer from a constant or from configuration, but should not be read from another query string or user-provided parameter because that would allow for cache-flooding attacks where a malicious user sends requests for very large thumbnails or for a large number of different sizes.

An alternative to generating a smaller version of the image with the same proportions as we’re doing here is to crop the image into a square thumbnail like Flickr does. This is fairly straightforward, and I won’t go into the details of how to do this, but the code project that accompanies this article contains an alternate version of the handler, SquareThumbnail.ashx, which does exactly that.

Once this is done, actually creating the reduced image is a matter of calling the right APIs with the right parameters. You can easily get ugly thumbnails in a line of code; we’ll do it in a little more but with great quality. In order to do that, we choose high quality bicubic as the resizing algorithm and PNG as the format for the output. The following article justifies this choice in great details:

What InterpolationMode and CompositingQuality to use when generating thumbnails via System.Drawing

Listing 3: Building the resized bitmap

Here, we’re not sending the resized stream directly to the output stream because the PNG implementation in System.Drawing requires the use of a searchable stream, which Response.OutputStream isn’t. That’s why we use an intermediary memory stream, which increases memory pressure, but this will be compensated by the efficient caching that we’ll introduce next.

Caching the images

Resizing an image, especially a large one using a good algorithm, is a heavy operation. You don’t want to do that over and over again every time an image is requested. The first thing we can do is add output caching to the handler:

Listing 4: Output caching

Depending on how many images you keep on the site and on the memory you have available on the web server, this might be enough. If it’s not, we have one more trick up our sleeves, but this will only work in a high or full trust environment (which may be out of the question if your web site is hosted or for security reasons) or if you compile this article’s code into a signed dll and deploy it into the GAC.

What we want to do here in addition to output caching is to cache the thumbnails on disk so that even if the output cache expires, we can still serve the disk cache version if it exists instead of resizing the image again. In order to do that, we need a location on disk where we have writing rights, preferably without having to configure ACLs. It so happens that there is such a location, and that’s the code generation directory for the application. This directory is conveniently accessible through HttpRuntime.CodegenDir.

Writing the resized image to this directory is as simple as this:

And of course, checking the availability of a cached version and serving it is just as easy:

This is really nice as we really have three levels of cache here. First, the browser caches the thumbnails for a year so in many cases your server isn’t even hit. Second, IIS caches them in memory. And finally, the handler has its own disk cache with which to repopulate the first two whenever they both expire. This means that each picture should effectively be resized only once. The disk cache can even survive an application restart. Of course, that also means that if for whatever reason the cache needs to be refreshed, you’d need to manually go and delete the code gen directory for the application, which is slightly inconvenient but is a small price to pay for the scalability you’ll get from this method.

Emptying the disk cache can be made a lot easier by building a small handler that does it for you, without you having to determine where the code generation directory is. I’ve included such a handler, DeleteCache.ashx, in the downloadable project, but be careful to make that handler only accessible to an administration account.

Note:

Client caching will hide the hits to the thumbnails from your web server’s statistics, so if it’s important to you to capture all stats on thumbnails you might want to change the cache policy and remove cachePolicy.SetValidUntilExpires(true).

Conclusion

In this article, we’ve learned how to serve high quality thumbnails in a secure and scalable way. We’ve manipulated images stored on disk, but this of course translates perfectly to database-stored images.

About Bertrand Le Roy

Bertrand Le Roy is a program manager in the ASP.NET team.

View complete profile

Top Articles in this category

JavaScript with ASP.NET 2.0 Pages - Part 1
ASP.NET 2.0 has made quite a few enhancements over ASP.NET 1.x in terms of handling common client-side tasks. It has also created new classes, properties and method of working with JavaScript code. This article explores the enhancements and the various ways of injecting JavaScript programmatically into ASP.NET 2.0 pages.

ASP.NET ComboBox
The ASP.NET ComboBox is an attempt to try and enhance some of the features of the Normal ASP.NET DropDownList.

Upload multiple files using the HtmlInputFile control
In this article, Haissam Abdul Malak will explain how to upload multiple files using several file upload controls. This article will demonstrates how to create a webform with three HtmlInputFile controls which will allow the user to upload three files at a time.

JavaScript with ASP.NET 2.0 Pages - Part 2
ASP.NET provides a number of ways of working with client-side script. This article explores the usage and drawbacks of ASP.NET script callbacks, and briefly presents a bird's view of ASP.NET AJAX.

Using WebParts in ASP.Net 2.0
This article describes various aspects of using webparts in asp.net 2.0.

Top
 
 
 

Discussion


Subject Author Date
placeholder How to Invalidate Image Caching V Patel 5/7/2009 5:35 PM
Love the writing Xun Ding 7/14/2008 10:05 PM
placeholder At last... Thank you! But... Chibi Nopo 7/27/2008 1:31 AM
RE: At last... Thank you! But... Bertrand Le Roy 12/2/2008 1:03 PM
placeholder Better solution with Jpeg support and flexible sizes Nathanael Jones 8/6/2008 5:12 PM
RE: Better solution with Jpeg support and flexible sizes Bertrand Le Roy 12/2/2008 1:00 PM
placeholder Thanks for the post...! Alejandro Gomez 12/2/2008 11:54 AM
Thanks! which approach to deal with multiple files...? Matt Wagner 12/29/2008 3:49 PM
placeholder RE: Thanks! which approach to deal with multiple files...? Bertrand Le Roy 5/7/2009 6:20 PM

Please login to rate or to leave a comment.

Product Spotlight