[Edit: I’ve posted it on CodeProject and there are some greatl people commenting on it that did the investigation on ho to wrap it in a STA Thread and make it a part of your ASP.Net solution – really cool stuff!]

An interesting use case. Darek (our beloved sys admin – we all bow to him and worship his skills) has recently asked if it’s possible to write a .net application to make a thumbnail of a website. Which is pretty trivial with Windows forms actually.

All you really need to do is drop a WebBrowser on your form and once it’s loaded the page call:

webBrowser.DrawToBitmap(bitmap, bitmapRect);

When it gets tricky is when you want to do it in a console application is a way that can take a shot of multitude of websites provided in a batch file. There is a dirty way of instantiating a whole form, making it show (or not), doing the work and then exiting the Winforms app. Which might probably be enough for a quick solution, but I wanted to publish this piece of code, so I would actually NOT take a pride in something like that.

How is it done the proper way then?

So we instantiate the web control (I’ve written a separate class to do the dirty work for me called WebPageBitmap..

public WebPageBitmap(string url, int width, int height, bool scrollBarsEnabled)
{
this.url = url;
this.width = width;
this.height = height;
webBrowser = new WebBrowser();
webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(documentCompletedEventHandler);
webBrowser.Size = new Size(width, height);
webBrowser.ScrollBarsEnabled = scrollBarsEnabled;
}

Easy so far and pretty similar to what the regular app would do anyway. The documentCompletedEventHandler is a delegate to tell that the has loaded (I initially wanted to use that for drawing the bitmap but deferred that to the point where the bitmap is actually fetched after I added the resizing part). Now comes the interesting case.

Since the call is asynchronous a simple webBrowser.Navigate(url); won’t cut it. Since we are in a single thread and the browser does not create a separate thread for that. Which makes sense by the old windows rule: Only th thread that creates a control, accesses the control. We need to somehow allow the control to take the flow of the thread and do its work. Navigate only tells it that it should perform the action and immediately exits. The developer’s responsibility then is to know when the control is ready for consumption. Which is the case when the webBrowser.ReadyState progresses to (or returns to) the state of WebBrowserReadyState.Complete. To pass the flow to the app controls you need to perform Application.DoEvents();. which was a bit of a wild guess when I used it. Surprise, surprise, it works just like it did in other Windows frameworks I used before.

public void Fetch()
{
webBrowser.Navigate(url);
while (webBrowser.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
}

The effect is a tiny and neat (I hope) app that pulls a web page from the net and makes a screenshot off of it (with possible rescaling):

You can get the source here.

Also there is a compiled… binary… ready to use… compiled with love… for your downloading pleasure… HERE

1 Star2 Stars3 Stars4 Stars5 Stars (7 votes, average: 4.29 out of 5)
Loading...



This entry (Permalink) was posted on Friday, November 17th, 2006 at 3:55 pm and is filed under .Net Framework, C#, Downloadable, Software Development, Web applications. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response , or trackback from your own site.



  • Sanjit

    This doesn’t work for me. I get a blank image containing all white.

  • cehem

    white image for me too…

  • I have updated the code and the codeproject article ro reflect the changes – the application should work ok now.

  • Marcelo Gajardo

    Hi Adam!. I’m from Argentina, my english is not good je!.
    Thank you for your code to get a thumbnail from a WebPage.
    I comment you I make a few changes in the code to generate “screenshots” with same size of the page and without scrollbars :).
    I read your comment too, about throw host not found exception and will work about this.
    When I’ve got the code complete will send you (if you want it).

    Bye.

  • l

    brilliant

  • Frakn

    The drawToBitmap function is not usable for many pages. The result is the white image mentioned by a lot of people.

    Furthermore to be a real world application, it misses out a lot of functions like stopping nasty popup boxes, prevent JS errors, timeouts and release of the browsers eaten up resources when snapping fails.

  • It’s not a production app, that’s true. Stopping pop-ups and js is not really hard to add though, it was not a point of this demonstration but I guess I can add it. (I’ve maintained the app far longer than I ever intended, I might just change it into a VirtualPathProvider now that you mentioned production.

    Reading to the comment though, I wonder if you have tried the app. I agree that at the moment the article does not describe the process in whole (it’s quite old now) – but both the code project article comments as well as my comment on the post says that I’ve dealt with that problem . The problem that you describe is one from before the update.

    Agreed it needs more than this single line as it needs the cast to
    IHTMLElementRender2 and then

    IntPtr graphicshdc = graphics.GetHdc();
    render.DrawToDC(graphicshdc);

    I’m curious though, which sites you tried it on that it didn’t work on?

    Thank you for your feedback

  • Hi Adam,

    you’re right with your assumption that I tried the “old” version, which indeed didn’t render sites like digg.com and yahoo.com for example. I guess this is due these pages require you to send out a header identifying your browser.

    I have tried to run your new version though, but it keeps telling me that the application has an error and needs to be closed. This happens both on Vista x64 Ultimate as well as on Windows 2008 Server x64. Changing permissions as well as firewall settings didn’t help though. Problems taken form the popup:

    Problem Signature 01: getsitethumbnail.exe
    Problem Signature 02: 1.0.0.0
    Problem Signature 03: 486f7330
    Problem Signature 04: mscorlib
    Problem Signature 05: 2.0.0.0
    Problem Signature 06: 48ead9ba
    Problem Signature 07: c44
    Problem Signature 08: 3
    Problem Signature 09: System.FormatException
    OS Version: 6.0.6001.2.1.0.256.1
    Locale ID: 1031

    I used this to get the thumbnail:
    GetSiteThumbnail http://www.test.net 1024 768 240 180 C:\test.jpg

    You might have any idea what causes this.

    cheers
    Frank

  • Frank

    Ups sorry… GetSiteThumbnail http://www.test.com C:\test.jpg 1024 768 240 180

    Yet, I have found a page not being capable to snap: http://www.tv-movie.de and in addition ( no matter what popup blocker settings I adjust in IE 7 ) a popup from fonic.de pops up on my desktop…

  • Thanks Frank,

    I don’t have a 64 bit version of any of those machines installed on my machine at this point but I’m going to install 64-bit version of Windows 7 & hopefully I can reproduce the problem. in the mean time I wonder if running the app as an administrator might be the case.

    thanks for a great feedback, I’ll look into fixing those later in the day once I have a proper win-x64 setup here.

    Cheers

  • The problem on those machines might be actually related to the security measures introduced in Vista and Server 2008, could you try to save the picture in e.g. your documents or desktop?

    I’ve checked the command line and it crashed even on Vista 32 (not sufficient rights to save to C:\), but:
    GetSiteThumbnail http://www.test.net %userprofile%\desktop\test.jpg 1024 768 240 180

    worked

  • Pingback: SoakIE – a Web Server Stress Tool with a twist | Codality()

  • Doris

    It works well with some websites, but, for instance, it wouldn’t take a snapshot of bing.com. Is there a way to allow the entire web page to finish loading and then take a snapshot? It would help with the sites containing flash or large images.

  • Hi Doris,

    The application should be waiting before the browser reports that the page is finished loading. agreed that might not be enough if the loading is in a plugin’s space. You can pretty easily modify the application to wait a certain timeout before taking the screenshot and after the browser reports loading completion.

    If that is not something that you can do yourself (not being programmer maybe?) mail me and I will compile a custom version of the app for you. You can find my mail on the “About me” page.

  • I tried to create a thumbnail from yahoo.com and I got a blank page.

  • Hello,

    Great code, it’s working on many sites but on mine only the left part is rendered, do you know why ? Thanks.

  • hi reboltutorial,
    I wonder if you could provide me with some URL’s to the sites.
    Preferably email as the spam filter will cut you off if you try to put them here .

  • Offshore c sharp net programmer

    Great job dude. The code is working…Thanks a lot for it!

  • Nice tutorial and great code. Thx for it.

  • Al

    Great article! However Yahoo site doesn’t work as well as several other sites. If anybody came up with the solution – please post. Thanks!

  • One thing to be aware of is that some hosting sites might not allow the way this code creates an STA thread. On my CrystalTech hosting account, it works just fine. But I also have it on a GoDaddy grid hosting account and it fails miserably. The error I get cannot be caught as an exception. It seems to completely restart the request.

    More details on this and my version of the code can be read at http://blackbeltcoder.com/Articles/asp/creating-website-thumbnails-in-asp-net.

  • I’m using application.doevents(0 and I get an error. I’m not using your code. I’m using someone elses code:

    Error 8 ‘System.Web.HttpApplicationState’ does not contain a definition for ‘DoEvents’

    Doevents will work in a project but not a web application apprarantely. Did I miss something? How can I get it to work in my website application?

    public Bitmap GenerateScreenshot(string url, int width, int height)
    {
    // Load the webpage into a WebBrowser control
    WebBrowser wb = new WebBrowser();
    wb.ScrollBarsEnabled = false;
    wb.ScriptErrorsSuppressed = true;
    wb.Navigate(url);
    while (wb.ReadyState != WebBrowserReadyState.Complete) { Application.DoEvents(); }

    // Set the size of the WebBrowser control
    wb.Width = width;
    wb.Height = height;

    if (width == -1)
    {
    // Take Screenshot of the web pages full width
    wb.Width = wb.Document.Body.ScrollRectangle.Width;
    }

    if (height == -1)
    {
    // Take Screenshot of the web pages full height
    wb.Height = wb.Document.Body.ScrollRectangle.Height;
    }

    // Get a Bitmap representation of the webpage as it’s rendered in the WebBrowser control
    Bitmap bitmap = new Bitmap(wb.Width, wb.Height);
    wb.DrawToBitmap(bitmap, new Rectangle(0, 0, wb.Width, wb.Height));
    wb.Dispose();

    return bitmap;
    }

    }

  • Robin khurana

    but the thumnail generated is blank white images sometimes …how to solve this problem

  • asd ada asd

    test