views:

376

answers:

3

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 :)

A: 

Do you see the session cookie in the requests coming from the WebBrowser control? I cannot find anything on how this control is supposed to handle cookies - if it ignores them you would get the behavior you are describing

mfeingold
I can't see any difference in any of the WebBrowser properties in the case where it works, and the case where it doesn't. I think the control basically runs IE, so it makes sense that it's some permissions issue stopping the cookies from working. On the advice of my colleague, I'm going to try impersonation in the thread to get it running as a local user.
Andrew Barrett
+1  A: 

Using impersonation in the background thread would allow the WebBrowser to run as a user with permissions to run IE and thus store cookies, instead of it running as the Application Pool user (NETWORK_SERVICE).

You can setup the Impersonation in the web.config or programatically to restrict it to a specific thread.

Amal Sirisena
My world is a better place now.
Andrew Barrett
+2  A: 

not offcially supported due to its usage of WinInet. see http://support.microsoft.com/kb/238425 for limitations of WinInet in a service.

Sheng Jiang 蒋晟
Thanks for the link, that explains it really well.
Andrew Barrett