I need to use the WinForms WebBrowser control in an ASP.NET application in order to take screenshots of webpages.
I'd much rather run it in a console app, and communicate to the app via the ASP.NET application, but it's not my call, I've been told it has to run as part of the website.
It all pretty much works apart from every call to navigate starts fresh with a new session, it's like the cookies aren't being persisted. As an experiment, I changed my IIS application pool to run as me rather than NETWORK_SERVICE, and it all works fine. So it's something strange about it running as network service.
I'm guessing that the network service account doesn't have the permissions to keep track of the ASP.NET_SessionId cookie and the auth cookie, but both these are non-persistent cookies, so I don't know why that would be.
Here is some simplified code so you can get the gist of what I'm doing. There is a bunch of logging and storing of images that I cut out, so it's not complete code. Basically a developer/tester/admin can run this code once (task kicked off via webpage), it will generate bitmaps of every page in the system, they can release a new version of the website and then run it again, it will tell you the differences (new pages, pages removed, pages changed).
public void Run()
{
var t = new Thread(RunThread);
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
private void RunThread()
{
try
{
using (var browser = new WebBrowser())
{
browser.DocumentCompleted += BrowserDocumentCompleted;
if (!VisitPage(browser, "special page that sets some session flags for testing - makes content predictable")))
throw new TestRunException("Unable to disable randomness");
foreach (var page in pages)
{
VisitPage(browser, page);
}
}
}
// An unhandled exception in a background thread in ASP.NET causes the app to be recycled, so catch everything here
catch (Exception)
{
Status = TestStatus.Aborted;
}
}
private bool VisitPage(WebBrowser browser, string page)
{
finishedEvent.Reset();
var timeout = false;
stopwatch = new Stopwatch();
browser.Navigate(page);
stopwatch.Start();
while (!timeout && !finishedEvent.WaitOne(0))
{
Application.DoEvents();
if (stopwatch.ElapsedMilliseconds > 10000)
timeout = true;
}
Application.DoEvents();
if (timeout)
{
if (resource != null)
ShopData.Shop.LogPageCrawlTestLine(testRunID, resource, sequence++, (int)stopwatch.ElapsedMilliseconds, null);
}
browser.Stop();
return !timeout;
}
private void BrowserDocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
try
{
var browser = (WebBrowser)sender;
var width = browser.Document.Body.ScrollRectangle.Width;
var height = browser.Document.Body.ScrollRectangle.Height;
var timeTaken = (int)stopwatch.ElapsedMilliseconds;
if ((width == 0) || (height == 0))
{
return;
}
browser.Width = width;
browser.Height = height;
byte[] buffer;
using (var bitmap = new Bitmap(width, height))
using (var memoryStream = new MemoryStream())
{
browser.DrawToBitmap(bitmap, new Rectangle(0, 0, width, height));
bitmap.Save(memoryStream, ImageFormat.Bmp);
buffer = memoryStream.ToArray();
}
// stores image
}
finally
{
finishedEvent.Set();
}
}
So in summary:
- The above code is started by a ASP.NET page and it runs in the backgound
- It works fine if I run it in an IIS application pool set to run as a proper user
- If I run it in an IIS application pool as NETWORK_SERVICE then every navigate has a new session, rather than persisting a session for the lifetime of the WebBrowser.
- I can get it working fine outside of ASP.NET, but that is not an option for me currently, it has to run inside this ASP.NET application.
//Update
On the advice of my colleague I'm using impersonation in the background thread where I'm creating the WebBrowser, running as a local user. This works fine and I'm pretty happy with this solution. He's a stackoverflow user so I'll let him post the answer and get the credit :)