Download Source Code
Creating a small thumbnail image of a website can be a useful task. This is particularly the case with sites that are directories of websites. A thumbnail helps give the user some idea about the content of a site that they might click to visit.
Although there are a couple of gotchas, doing this from ASP.NET is fairly straight forward. The process involves creating a WebBrowser control to load and render the website image, and write that image to a bitmap.
Using the WebBrowser Control to Create a Thumbnail
The WebBrowser control is an ActiveX control. Normally, you create one of these by placing it on a Windows Form. Here, we'll just create one dynamically without any form. One problem is that this control can only be created in a single-threaded apartment. This is the tricky part of this code. We need to create a new thread, set its apartment state to single-threaded apartment, and then run it. This worker thread will create the control and generate the thumbnail. Listing 1 shows my code for the WebsiteThumbnail class.
Listing 1. WebsiteThumbnail Class
public class WebsiteThumbnail
protected string _url;
protected int _width, _height;
protected int _thumbWidth, _thumbHeight;
protected Bitmap _bmp;
/// Generates a website thumbnail for the given URL
/// <param name="url">Address of website from which to generate the
/// <param name="width">Browser width</param>
/// <param name="height">Browser height</param>
/// <param name="thumbWidth">Width of generated thumbnail</param>
/// <param name="thumbHeight">Height of generated thumbnail</param>
public static Bitmap GetThumbnail(string url, int width, int height,
int thumbWidth, int thumbHeight)
WebsiteThumbnail thumbnail = new WebsiteThumbnail(url, width, height,
/// Protected constructor
protected WebsiteThumbnail(string url, int width, int height,
int thumbWidth, int thumbHeight)
_url = url;
_width = width;
_height = height;
_thumbWidth = thumbWidth;
_thumbHeight = thumbHeight;
/// Returns a thumbnail for the current member values
/// <returns>Thumbnail bitmap</returns>
protected Bitmap GetThumbnail()
// WebBrowser is an ActiveX control that must be run in a
// single-threaded apartment so create a thread to create the
// control and generate the thumbnail
Thread thread = new Thread(new ThreadStart(GetThumbnailWorker));
return _bmp.GetThumbnailImage(_thumbWidth, _thumbHeight,
null, IntPtr.Zero) as Bitmap;
/// Creates a WebBrowser control to generate the thumbnail image
/// Must be called from a single-threaded apartment
protected void GetThumbnailWorker()
using (WebBrowser browser = new WebBrowser())
browser.ClientSize = new Size(_width, _height);
browser.ScrollBarsEnabled = false;
browser.ScriptErrorsSuppressed = true;
// Wait for control to load page
while (browser.ReadyState != WebBrowserReadyState.Complete)
// Render browser content to bitmap
_bmp = new Bitmap(_width, _height);
browser.DrawToBitmap(_bmp, new Rectangle(0, 0,
Using the Class
Initially, this class required you to create an instance of it and then call the GetThumbnail() method on that instance. However, I changed it to have a single static GetThumbnail() method to simplify its usage. (I've made all the other members protected.)
To generate a thumbnail image, just call WebsiteThumbnail.GetThumbnail(). The first argument is the URL of the website to generate the thumbnail from. Next are the dimensions of the browser window. This affects how large of an area will be included in the image. And, finally, the last two arguments specify the dimensions of the thumbnail. The method returns a bitmap, which you can save to a file or write to a database.
ASP.NET must have write access to the folder where the thumbnail is written in order for your code to work.
This code works on my development machine and also on a CrystalTech shared hosting account. However, it fails on a GoDaddy grid hosting account. The application appears to simply die where I call thread.Join(). It also appears to restart the event that initiated the call. I will just assume this is some sort of restriction on some hosting accounts that ASP.NET 4 cannot detect, handle, or recover from. If anyone has more information about what might be happening in this case, please let me know.
Although there are a number of services on the web for performing this task, it's nice to be able to perform it right within your application. Hopefully, this code will help you out.
Use of this article and any related source code or other files is governed
by the terms and conditions of