Build A Self-Caching ASP.NET Web Site Thumbnail Control

By Peter Bromberg

Peter hooks up his new WebSiteThumbnail class into the previously published In-Memory Image control, and shows how it works.

"Clearly the use of a mobile phone while driving can be a distraction to drivers, but labeling it the only . . . <<<CRASHHHHHH>>>" -- Anonymous Expert

Don't ask me why, but recently I got involved in finding out how to generate web site "snapshot" thumbnail images like the ones you see on Alexa or via the snap.com freebie.

I didn't particularly plan on using this stuff on any of my sites, but I guess I was just curious. Turns out that with .NET, I could only find one fairly reliable way to take a picture of a web page and it has some "gotchas". This will illustrate what I did to fix them. For starters, I should mention that what I present here is an extension of my in memory ASP.NET image control here. It just made sense that once I figured out a reliable way to get a snapshot" of a web page, I'd want to put it into an image control that offers caching. In addition, I've set it up so that you can use the class that does the Web site snapshots in a manner external to the control, and I've added two static methods, one of which allows the specification of an absolute path for saving the generated thumbnail images. If an image is requested that already exists, the cached version is served, which is much faster.

At any rate, the trick is to use the Windows Forms Webbrowser control. Contrary to what you may or may not have heard, this does not have to be situated on a container form - it can be used independently with 100% programmatic access. There are a couple of catches: First, being a Windows Form control, this must operate on an STA (Single Threaded Apartment) thread. This means you need to either set the "AspCompat = "true" attribute on the page that uses it, or you need to make the actual Webbrowser Navigate call to the target page on a secondary thread whose state has been set to STA. I chose the latter. The other gotcha is that the Webbrowser control does its navigation on more than one thread. The DocumentCompleted event handler is fired when the browser has fully loaded the target url, and therefore it is in this event that we want to do our business logic.

In this case, I decided to use a ManualResetEvent to block the initial calling thread until the bitmap is actually available, and then call the Set method on it in order to unblock and allow the method to return our web site thumbnail. Concurrent with this, I use a timeout that also sets the ManualResetEvent since occasionally the Webbrowser control will go into LaLa Land especially if you are behind a corporate firewall, whose software brand I will choose not to mention. In the event that the returned bitmap is null, I then replace it with a stock image "Photo Not Available" that I pull out of the assembly as an embedded resource. The last thing you want to see is an ugly red X image placeholder in the browser.

Since the code for the Image control is already published, here I'll just focus on the part that I added to it, my "WebSiteThumbnail" class:

using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
using System.IO;
using System.Reflection;

namespace PAB.WebControls
{
    public class WebSiteThumbnail
    {
        private string url = null;      
        private Bitmap bmp = null;
        public Bitmap Image 
        {
            get 
            { 
                return bmp; 
            } 
        }
        private ManualResetEvent mre = new ManualResetEvent(false);
        private int timeout = 5; // this could be adjusted up or down
        private int thumbWidth;
        private int thumbHeight;
        private int width;
        private int height;
        private string absolutePath;
        #region Static Methods
        public static Bitmap GetSiteThumbnail(string url, int width, int height, int thumbWidth, int thumbHeight)
        {
            WebSiteThumbnail thumb = new WebSiteThumbnail(url, width, height, thumbWidth, thumbHeight);
            Bitmap b = thumb.GetScreenShot();
            if (b == null)
                b = (Bitmap)System.Drawing.Image.FromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream("PAB.WebControls.Notavailable.jpg"));
            return b;
        }

        public static Bitmap GetSiteThumbnail(string url, int width, int height, int thumbWidth, int thumbHeight,string absolutePath)
        {           
            WebSiteThumbnail thumb = new WebSiteThumbnail(url, width, height, thumbWidth, thumbHeight,absolutePath );
            Bitmap b = thumb.GetScreenShot();
            if (b == null)
                b = (Bitmap)System.Drawing.Image.FromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream("PAB.WebControls.Notavailable.jpg"));
             return b;
        }
        #endregion

        #region Ctors
        public WebSiteThumbnail(string url, int width, int height,int thumbWidth, int thumbHeight)
        {
            this.url = url;
            this.width = width;
            this.height = height;
            this.thumbHeight = thumbHeight;
            this.thumbWidth = thumbWidth;
        }
        public WebSiteThumbnail(string url, int width, int height, int thumbWidth, int thumbHeight,string absolutePath)
        {
            this.url = url;
            this.width = width;
            this.height = height;
            this.thumbHeight = thumbHeight;
            this.thumbWidth = thumbWidth;
            this.absolutePath = absolutePath;
        }
        #endregion

        #region ScreenShot
        public Bitmap GetScreenShot()
        {
             string fileName = url.Replace("http://", "") + ".jpg";
             fileName = System.Web.HttpUtility.UrlEncode(fileName);
            if (absolutePath != null &&  File.Exists(absolutePath + fileName))
            {                
                bmp = (Bitmap)System.Drawing.Image.FromFile(absolutePath + fileName);                   
            }
            else
            {
                Thread t = new Thread(new ThreadStart(_GetScreenShot));
                t.SetApartmentState(ApartmentState.STA);
                t.Start();
                mre.WaitOne();
                t.Abort();
            }
            return bmp;
        }
        #endregion
        private void _GetScreenShot()
        {
            WebBrowser webBrowser = new WebBrowser();
            webBrowser.ScrollBarsEnabled = false;
            DateTime time = DateTime.Now;
            webBrowser.Navigate(url);
            webBrowser.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(WebBrowser_DocumentCompleted);
            while (true)
            {
                Thread.Sleep(0);
                TimeSpan elapsedTime = DateTime.Now - time;
                if (elapsedTime.Seconds >= timeout)
                {
                    mre.Set();
                }
                Application.DoEvents();
            }


        }
        private void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            WebBrowser webBrowser = (WebBrowser)sender;
            webBrowser.ClientSize = new Size(this.width, this.height);
            webBrowser.ScrollBarsEnabled = false;
            bmp = new Bitmap(webBrowser.Bounds.Width, webBrowser.Bounds.Height);
            webBrowser.BringToFront();
            webBrowser.DrawToBitmap(bmp, webBrowser.Bounds); 
            Image img = bmp.GetThumbnailImage(thumbWidth, thumbHeight, null, IntPtr.Zero);
            string fileName = url.Replace("http://", "") + ".jpg";
            fileName = System.Web.HttpUtility.UrlEncode(fileName);
          if (absolutePath != null && !File.Exists(absolutePath + fileName))
          {
              img.Save(absolutePath + fileName);
          }  
            bmp = (Bitmap)img;           
            webBrowser.Dispose();
          if (mre != null) 
              mre.Set();
        }

        public void Dispose()
        {
            if (bmp != null) this.bmp.Dispose();
        }
    }
}
The downloadble solution includes a page that uses the Control, and another page that calls an "imagehandler" page with the parameters on the querystring and shows an image just using the class itself (from out of the control assembly) with the images being cached in a "thumbnails" folder.  Threading aficionados may notice that I am calling Abort on the background thread after the ManualReset is released. I know setting IsBackGround and using  Join() would be a lot gentler, and I tried it. Sorry, the call to Abort on the thread has to stay, at least for now.

There is proxy code in Global.asax that reads from entries in the web.config if you are behind a firewall. No entries, no proxy. Enjoy.

Download the Visual Studio 2005 Solution accompanying this article.
Popularity  (12025 Views)
Picture
Biography - Peter Bromberg
Peter Bromberg is a C# MVP, MCP, and .NET expert who has worked in banking, financial and telephony for over 20 years. Pete focuses exclusively on the .NET Platform, and currently develops SOA and other .NET applications for a Fortune 500 clientele. Peter enjoys producing digital photo collage with Maya,playing jazz flute, the beach, and fine wines. You can view Peter's UnBlog and IttyUrl sites. Follow Microsoft MVP
Create New Account
Article Discussion: Build A Self-Caching ASP.NET Web Site Thumbnail Control
Peter Bromberg posted at Monday, January 29, 2007 1:55 PM
Turning off javascript on the WebBrowser instance..possible?
Stephen G replied to Peter Bromberg at Monday, January 29, 2007 1:55 PM

I'm looking through the properties of the WebBrowser control and do not see anything to turn "off" Javascript, and here's why i need it

I am using this Website Thumbnail code to get screenshots, but my local browser here has the option on to debug javascript errors.. so if locally why i am developing and the code goes to get a screenshot of a site with javascript (example: http://www.boston.com) the thumbnail just hangs as it cannot get by the dialog box asking to debug or not

Is there some tricky way to get around this?  I'd assume the best way would be just to all out turn off javascript during the call, but i cannot see how to do so...

Thanks in advance
- Steve

I am not 100% sure,
Peter Bromberg replied to Stephen G at Monday, January 29, 2007 1:55 PM

but if you go into Internet Explorer, Tools/Internet Options/Advanced, and check the two Disable Javascript Debugging checkboxes, the Webbrowser control may inherit these settings.

 

webBrowser overhead
suvan ji replied to Peter Bromberg at Monday, January 29, 2007 1:55 PM

Hi,

I'm trying to use the thumbnailControl as followed.

I've created a page which displays 20-30 thumbnails of different URLs,

and by using ajax I'm refreshing each thumbnail every 1 minute the fastest.

the activity on the server side brings the cpu to 50% for long periods and the process

accumulates memory upto around 500mb of RAM.

I've invistigated the issue little bit and found on the net that the webBrowser control does not release it's resources and that might be the cause for this to happen.

any comment or suggestion?

thanks,

Shahar Weinstein.

Capture Web Page with Flash
David Evans replied to Peter Bromberg at Monday, January 29, 2007 1:55 PM

Hello,

Thank you for the code - this has been very helpful.  I do have a few web pages on my site that I wish to make thumbnails for, but they all have embedded Flash video and/or animations that the WebBrowser object doesn't understand.  How can I "install Flash" into the off-screen Web Browser object so that it renders Flash content properly and includes that in the screen shot / thumbnail - any ideas?

Thank you so much, David 

DocumentComplete Event fired prior to the document rendering
Matthew VanDerlofske replied to Stephen G at Monday, January 29, 2007 1:55 PM

Do you know of a way to make the webbrowser wait until page has been rendered before the event fires?

I have javascript and client side controls that run on the page, but the screen shot I am getting is of the page prior to all these running.

Can I have my javascript set a hidden variable once it is complete and check for that instead?

WebBrowser DocumentCompleted Event Question - Only Fires Once
Heath Allen replied to Peter Bromberg at Monday, January 29, 2007 1:55 PM

Dear Peter,

I love your thumbnail utility and it works great.

I actually started to use it as a basis for a project that I'm working on where I'm instantiating a WebBrowser control to be used in a Web Application or Web Service to automate the filling in off online forms.  I chose this approach rather than using the HttpWebRequest and HttpWebResponse objects because some of the forms we need to fill in our AJAX based and doing that with HttpWebRequest of HttpWebResponse is almost impossible.

I have the code working for the first page, but when I try to navigate to a second page in the process on the same new thread, the DocumentCompleted event never fires.  I put in some debugging code and it shows me exactly what happens, basically it just hangs up on my second Navigate() method call and it eventually times out after my 10 second timeout is reached.

Below you will find the results of my debugging, as you can see, it just hangs after Step 8 and then eventually times out, it's supposed to be going to the DocumentCompleted event handler but never does.  The code for this class is below, I'm actually using this WebControl in a web application and that's how you would run it, actually in the same way you run the Thumbnail control.

1 After wb instanitated in _ProcessWebPageAction() method 0:34:640
2 After th.Start() in StartProcess method 0:34:653
3 After wb.Navigate() to http://www.myfreecorporation.com/ 0:34:653
4 After wb object instantiated from sender in DocumentCompleted Event 0:35:450
5 After this._documenttext set in DocumentCompleted Event 0:35:450
6 Before calling of next action in DocumentCompleted Event 0:35:450
7 After wb instanitated in _ProcessWebPageAction(object sender) method 0:35:450
8 After wb.Navigate() to http://www.myfreecorporation.com/contact-us.aspx 0:35:450
9 Before _mre.Set() after TimeOut in _ProcessWebPageAction(object sender) method 0:45:450
10 After _mre.WaitOne() in StartProcess method 0:45:450
11 After th.Abort() in StartProcess method 0:45:450

CODE:  (I also uploaded the file as well), I left the debugging code in, just comment it out when you try to run. 

using System;

using System.Collections.Specialized;

using System.Drawing;

using System.Drawing.Imaging;

using System.IO;

using System.Web;

using System.Web.SessionState;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.Reflection;

using System.ComponentModel;

using System.Windows.Forms;

using System.Threading;

namespace WebAutomator {

public class OnlineForm : System.Web.UI.Control {

//Control Properties:

public string DocumentText = "";

public int Timeout = 10; //Timeout in seconds for the entire process

private void OnlineForm_Init(EventArgs e){

//Start by going to the Home Page:

string otherTestUrl = "http://www.myfreecorporation.com/";

string otherTestUrl2 = "http://www.myfreecorporation.com/contact-us.aspx";

string[] urlActionArray = new string[2];

urlActionArray[0] = otherTestUrl;

urlActionArray[1] = otherTestUrl2;

//Instantiate the WebBrowserAutomationProcess object:

WebBrowserAutomationProcess ap = new WebBrowserAutomationProcess(urlActionArray, this.Timeout);

//Call the method to start the process:

this.DocumentText = ap.StartProcess();

}

#region Control Init and Render Event Handler

protected override void OnInit(EventArgs e){

OnlineForm_Init(e);

}

protected override void Render(HtmlTextWriter output){

output.Write(this.DocumentText);

}

#endregion

#region Web Browser Automation Section

public class WebBrowserAutomationProcess {

private string _documenttext = "";

private string[] _urlActionArray;

private int _urlActionIndex = 0;

private int _timeout;

private ManualResetEvent _mre = new ManualResetEvent(false);

public WebBrowserAutomationProcess(string[] urlActionArray, int timeout){

this._urlActionArray = urlActionArray;

this._timeout = timeout;

}

//To user the System.Windows.Forms.WebBrowser control in a WebApp or WebService, it must be instantiate and accessed on a new

//STA (Single Threaded Apartment) thread, without doing this, it would never work:

public string StartProcess(){

//Clear all existing steps:

DbHelper.DeleteSteps();

//Instantiate the new thread and set it's apartment state to STA:

Thread th = new Thread(new ThreadStart(_ProcessWebPageAction));

th.SetApartmentState(ApartmentState.STA);

//Start processing the web page action by starting the thread:

th.Start();

DbHelper.InsertStep("After th.Start() in StartProcess method");

//Tell the ManualResetEvent to wait, this freezes the main thread until the new thread has finished it's task:

_mre.WaitOne();

DbHelper.InsertStep("After _mre.WaitOne() in StartProcess method");

//Abort the new thread once the ManualResetEvent has been set (this occurs in the DocumentCompleted event handler):

th.Abort();

DbHelper.InsertStep("After th.Abort() in StartProcess method");

//Finally, return the document text to show we had a successful process which was set in the DocumentCompleted event handler as well:

return this._documenttext;

}

//1st Overloaded Method - Called for the first action:

private void _ProcessWebPageAction(){

//Instantiate the webbrowser object:

WebBrowser wb = new WebBrowser();

wb.AllowNavigation = true;

DbHelper.InsertStep("After wb instanitated in _ProcessWebPageAction() method");

//Set the DocumentCompleted event handler:

wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(WebBrowser_DocumentCompleted);

//Navigate to the url for this process:

string url = this._urlActionArray[this._urlActionIndex];

wb.Navigate(url);

DbHelper.InsertStep("After wb.Navigate() to " + url);

//Get the start time of navigate method:

DateTime startTime = DateTime.Now;

//This loop serves 2 very important purposes, the first it so keep things going with the DoEvents() call so the

//DcoumentCompleted event is actually trigger and the second purpose is that it provides a scheduled timeout if

//the process as a whole is running longer than it should:

while (true){

Thread.Sleep(0);

TimeSpan elapsedTime = DateTime.Now - startTime;

if (elapsedTime.Seconds >= this._timeout){

DbHelper.InsertStep("Before _mre.Set() after TimeOut in _ProcessWebPageAction method");

_mre.Set();

}

//Call DoEvents() to ensure that the DocumentCompleted event fires:

Application.DoEvents();

}

}

//2nd Overloaded Method - Called for the second and all subsequent actions:

private void _ProcessWebPageAction(object sender){

//Cast the sender to a WebBrowser, essentially it's the same object instantiated at the very start of the thread:

WebBrowser wb = (WebBrowser)sender;

wb.AllowNavigation = true;

DbHelper.InsertStep("After wb instanitated in _ProcessWebPageAction(object sender) method");

//Remove the previous DocumentCompletedEventHandler and add it again:

wb.DocumentCompleted -= new WebBrowserDocumentCompletedEventHandler(WebBrowser_DocumentCompleted);

wb.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(WebBrowser_DocumentCompleted);

//Navigate to the url for this process:

string url = this._urlActionArray[this._urlActionIndex];

wb.Navigate(url);

DbHelper.InsertStep("After wb.Navigate() to " + url);

//Get the start time of navigate method:

DateTime startTime = DateTime.Now;

//This loop serves 2 very important purposes, the first it so keep things going with the DoEvents() call so the

//DcoumentCompleted event is actually trigger and the second purpose is that it provides a scheduled timeout if

//the process as a whole is running longer than it should:

while (true){

Thread.Sleep(0);

TimeSpan elapsedTime = DateTime.Now - startTime;

if (elapsedTime.Seconds >= this._timeout){

DbHelper.InsertStep("Before _mre.Set() after TimeOut in _ProcessWebPageAction(object sender) method");

_mre.Set();

}

//Call DoEvents() to ensure that the DocumentCompleted event fires:

Application.DoEvents();

}

}

private void WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e){

//Cast the sender to a WebBrowser, essentially it's the same object instantiated in the _ProcessWebPageAction method:

WebBrowser wb = (WebBrowser)sender;

DbHelper.InsertStep("After wb object instantiated from sender in DocumentCompleted Event");

//Set the class's _documenttext property to the DocumentText of the WebBrowser control:

this._documenttext = wb.DocumentText;

DbHelper.InsertStep("After this._documenttext set in DocumentCompleted Event");

//Increment the urlIndex:

this._urlActionIndex++;

//Check to see if there are more actions to process:

if (this._urlActionIndex < this._urlActionArray.Length){

DbHelper.InsertStep("Before calling of next action in DocumentCompleted Event");

//Start the next action in the process and pass the WebBrowser object as the sender arg:

_ProcessWebPageAction(wb);

}

else {

//Cleanup the WebBrowser object:

wb.Dispose();

DbHelper.InsertStep("After WebBrowser Dispose() in DocumentCompleted Event");

//If the ManualResetEvent exists, then set it so the WebBrowser thread is aborted and the control is rendered:

if (_mre != null){

DbHelper.InsertStep("Before _mre.Set() in DocumentCompleted Event");

_mre.Set();

}

}

}

}

#endregion

}

}

The Second Navigation, not fire DocumentCompleted event
Gabriel Jaume replied to Heath Allen at Monday, January 29, 2007 1:55 PM
Hello Heath Allen,

Your code help me to work with WebBrowser Control on my ASP.NET pages.
But I found a problem: The second time when code call to wb.Navigation(url), the function WebBrowser_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e) never fires.

I try with your code, and appears the same problem. Only render the firt page "http://www.myfreecorporation.com/" and no render "http://www.myfreecorporation.com/contact-us.aspx" because the second time you call wb.Navigate(...) not fires the handler.

Do you have any idea about this?
GetSiteThumbnail hangs locally
rahul pandhe replied to Gabriel Jaume at Monday, January 29, 2007 1:55 PM
I am trying to take thumbnail images of some of the dynamically rendered pages from my own server. Your library and website is on the same server.
If I try local aspx pages, the request just hangs.
If I try external websites, I get the image.

Is it because the server process is deadlocked and does not allow rentry on the same thread?

e.g I have follow line in an ASPX page getsnap.aspx. It just hangs/spins the browser
 img = PAB.WebControls.WebSiteThumbnail.GetSiteThumbnail("http://localhost/xyz.aspx", 640, 480, 640, 480)

However this works
 img = PAB.WebControls.WebSiteThumbnail.GetSiteThumbnail("http://www.microsoft.com/", 640, 480, 640, 480)



Rahul

VB Version
Richard Spare replied to Peter Bromberg at Monday, January 29, 2007 1:55 PM

If anyone is interested. Below is the VB equivalent.

 

Imports Microsoft.VisualBasic

Imports System

Imports System.Drawing

Imports System.Windows.Forms

Imports System.Threading

Imports System.IO

Imports System.Reflection

Namespace RSL.WebControls

Public Class WebSiteThumbnail

Private url As String = Nothing

Private bmp As Bitmap = Nothing

Private mre As New Threading.ManualResetEvent(False)

Private timeout As Integer = 20

Private thumbWidth As Integer

Private thumbHeight As Integer

Private width As Integer

Private height As Integer

Private absolutePath As String = Nothing

 

Public ReadOnly Property Image() As Bitmap

Get

Return bmp

End Get

End Property

Public Shared Function GetSiteThumbNail(ByVal url As String, ByVal width As Integer, ByVal height As Integer, ByVal thumbWidth As Integer, ByVal thumbHeight As Integer) As Bitmap

Dim thumb As New WebSiteThumbnail(url, width, height, thumbWidth, thumbHeight)

Dim b As New Bitmap(thumb.GetScreenShot())

If b Is Nothing Then

b = System.Drawing.Image.FromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream("PAB.WebControls.Notavailable.jpg"))

End If

Return b

End Function

Public Shared Function GetSiteThumbNail(ByVal url As String, ByVal width As Integer, ByVal height As Integer, ByVal thumbWidth As Integer, ByVal thumbHeight As Integer, ByVal absolutePath As String) As Bitmap

Dim thumb As New WebSiteThumbnail(url, width, height, thumbWidth, thumbHeight, absolutePath)

Dim b As Bitmap = thumb.GetScreenShot()

If b Is Nothing Then

b = System.Drawing.Image.FromStream(Assembly.GetExecutingAssembly().GetManifestResourceStream("PAB.WebControls.Notavailable.jpg"))

End If

Return b

End Function

 

 

Public Sub New(ByVal url As String, ByVal width As Integer, ByVal height As Integer, ByVal thumbWidth As Integer, ByVal thumbHeight As Integer)

Me.url = url

Me.width = width

Me.height = height

Me.thumbHeight = thumbHeight

Me.thumbWidth = thumbWidth

End Sub

Public Sub New(ByVal url As String, ByVal width As Integer, ByVal height As Integer, ByVal thumbWidth As Integer, ByVal thumbHeight As Integer, ByVal absolutePath As String)

Me.url = url

Me.width = width

Me.height = height

Me.thumbHeight = thumbHeight

Me.thumbWidth = thumbWidth

Me.absolutePath = absolutePath

End Sub

 

 

 

Public Function GetScreenShot() As Bitmap

Dim filename As String = url.Replace("http://", "") + ".jpg"

filename = System.Web.HttpUtility.UrlEncode(filename)

If Not (absolutePath Is Nothing) And (File.Exists(absolutePath + filename)) Then

bmp = System.Drawing.Image.FromFile(absolutePath + filename)

Else

Dim t As New Thread(New ThreadStart(AddressOf _GetScreenShot))

t.SetApartmentState(ApartmentState.STA)

t.Start()

mre.WaitOne()

t.Abort()

End If

Return bmp

End Function

 

 

Private Sub _GetScreenShot()

Dim webBrowser As New WebBrowser

webBrowser.ScrollBarsEnabled = False

Dim time As DateTime = DateTime.Now

webBrowser.Navigate(url)

AddHandler webBrowser.DocumentCompleted, AddressOf WebBrowser_DocumentCompleted

While (True)

Thread.Sleep(0)

Dim elapsedtime As TimeSpan = DateTime.Now - time

If (elapsedtime.Seconds >= timeout) Then

mre.Set()

End If

Application.DoEvents()

End While

End Sub

Private Function ThumbnailCallback() As Boolean

Return False

End Function

 

 

 

 

Private Sub WebBrowser_DocumentCompleted(ByVal sender As Object, ByVal e As WebBrowserDocumentCompletedEventArgs)

Dim webBrowser As WebBrowser = sender

webBrowser.ClientSize = New Size(Me.width, Me.height)

webBrowser.ScrollBarsEnabled = False

Dim tmpBmp As New Bitmap(webBrowser.Bounds.Width, webBrowser.Bounds.Height)

webBrowser.BringToFront()

webBrowser.DrawToBitmap(tmpBmp, webBrowser.Bounds)

Dim dummyCallBack As System.Drawing.Image.GetThumbnailImageAbort

dummyCallBack = New _

System.Drawing.Image.GetThumbnailImageAbort(AddressOf ThumbnailCallback)

 

Dim img As Image = tmpBmp.GetThumbnailImage(thumbWidth, thumbHeight, dummyCallBack, IntPtr.Zero)

Dim filename As String = url.Replace("http://", "") + ".jpg"

filename = System.Web.HttpUtility.UrlEncode(filename)

If Not (absolutePath = Nothing) And Not (File.Exists(absolutePath + filename)) Then

img.Save(absolutePath + filename)

End If

bmp = img

webBrowser.Dispose()

If Not mre Is Nothing Then

mre.Set()

End If

 

End Sub

Public Sub Dispose()

If Not (bmp Is Nothing) Then

bmp.Dispose()

End If

End Sub

 

End Class

End Namespace

Thanks for contribution!
Peter Bromberg replied to Richard Spare at Monday, January 29, 2007 1:55 PM
Sometimes we c# diehards forget that our brethren prefer VB.NET. Oh, well!
Demo self caching multiple thumbnails with refresh
Jayesh Modi replied to suvan ji at Monday, January 29, 2007 1:55 PM

G'Day Suvan JI,

This is old story thread I am trying to follow and I was quite looking forward to where did that code you presented end up? can you please post a url where I cam see that work?

Thanks,

Jayesh

 

I noticed the use of AspCompat="true", does this app ref a COM object?
Robert Collins replied to Jayesh Modi at Monday, January 29, 2007 1:55 PM

I have noticed that the GetSiteThumbnail.aspx has the AspCompat="true" Attribute which generally means the page references a COM object and thus need to run in compatibility mode. I removed the attribute and of course the application fails.  I looked in the code and cannot see any COM objects being used, so I am not sure why AspCompat="true" is being used.  Can you explain why and what is causing the requirement?

lauren replied to rahul pandhe at Monday, January 29, 2007 1:55 PM
I am having similar problem that can not get it to create and cache from local server. Similarly if browse server from another workstation, I can't see thumbs of pages on that server but external sites work fine. Oddly it is not entirely consistent and if I restart the process it will sometimes grab the initial page image but fails for those thereafter.  I do not have a clue about threading, but hopefully someone far smarter than myself can shed some light on this--it is driving me bonkers.
Nick replied to Peter Bromberg at Monday, January 29, 2007 1:55 PM
I successfully got this implemented on my workstation, and it's pretty slick, thank you so much; however, when I publish the code out to the server it no longer works...I'm running on IIS7, and when trying to build the preview it never returns a thumbnail, I guess you could say it just hangs and doesn't give me any error messages or anything. Is there something I need to do to get this working in IIS?
Sean replied to Peter Bromberg at Monday, January 29, 2007 1:55 PM
I know this article is a little dated, but ManualResetEvent should not be used.  There is no reliable way to handle thread timeouts the way this article implemented them.  Instead, use a class-level boolean variable (I called mine IsFinished) and join the thread with a timeout ( i.e. t.Join(20000) for a 20 second timeout ).  There are situations where this code will hang forever for a reason I was not able to figure out.  Just thought you guys should know.
Peter Bromberg replied to Sean at Monday, January 29, 2007 1:55 PM
The ManualResetEvent's WaitOne method has overloads that provide a timeout if you need this.