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.

<<  Previous Article Continue reading and see our next or previous articles Next Article >>

About Bertrand Le Roy

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

This author has published 3 articles on DotNetSlackers. View other articles or the complete profile here.

Other articles in this category


Code First Approach using Entity Framework 4.1, Inversion of Control, Unity Framework, Repository and Unit of Work Patterns, and MVC3 Razor View
A detailed introduction about the code first approach using Entity Framework 4.1, Inversion of Contr...
jQuery Mobile ListView
In this article, we're going to look at what JQuery Mobile uses to represent lists, and how capable ...
Exception Handling and .Net (A practical approach)
Error Handling has always been crucial for an application in a number of ways. It may affect the exe...
JQuery Mobile Widgets Overview
An overview of widgets in jQuery Mobile.
Book Review: SignalR: Real-time Application Development
A book review of SignalR by Simone.

You might also be interested in the following related blog posts


Memory usage increases for a .NET Framework 2.0-based application when you use the ImageList.ImageCollection.Item property to obtain an image or when you call the ImageList.ImageCollection.GetEnumerator method read more
Data-binding Telerik CoverFlow for Silverlight + some Routed Commands goodness read more
Quick and painless Byte Array to Image converter for RadGridView for Silverlight read more
I-Load: Asp.Net Image Upload-Crop-Resize Control - 'sCountDown Promotion read more
Using Microsoft's Chart Controls In An ASP.NET Application: Rendering the Chart read more
Silverlight 3 read more
Using ImageMagick to Create Previews and Thumbnails from Uploaded Images read more
ASP.NET AJAX Release History : Q2 2009 BETA (version 2009.2.616) read more
Are you caching your images and scripts? IIS SEO can tell you read more
NDepend and CruiseControl.NET read more
Top
 
 
 

Discussion


Subject Author Date
placeholder How to Invalidate Image Caching V Patel 5/7/2009 5:35 PM
Well, many ways you can do that. Bertrand Le Roy 2/15/2010 5:36 PM
placeholder Using Uploadify to produce thumbnails Kiran Volety 9/6/2011 7:53 PM
Expiring Thumbnails Martin Dawson 6/6/2012 2:10 PM
placeholder Love the writing Xun Ding 7/14/2008 10:05 PM
At last... Thank you! But... Chibi Nopo 7/27/2008 1:31 AM
placeholder RE: At last... Thank you! But... Bertrand Le Roy 12/2/2008 1:03 PM
Better solution with Jpeg support and flexible sizes Nathanael Jones 8/6/2008 5:12 PM
placeholder RE: Better solution with Jpeg support and flexible sizes Bertrand Le Roy 12/2/2008 1:00 PM
Thanks for the post...! Alejandro Gomez 12/2/2008 11:54 AM
placeholder Thanks! which approach to deal with multiple files...? Matt Wagner 12/29/2008 3:49 PM
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.