2

I am struggling to find a way to take a screenshot of a website in MVC4. I have seen two potential solutions, which neither work well for MVC.

The first is using the WebBrowser, tutorial found here, but this gives me a ActiveX control '8856f961-340a-11d0-a96b-00c04fd705a2' cannot be instantiated because the current thread is not in a single-threaded apartment error.

The other is using a 3rd party called Grabz.It, but I haven't found a way to integrate it into MVC.

Any other ideas/solutions?

Thanks.

tereško
  • 58,060
  • 25
  • 98
  • 150
happygilmore
  • 3,008
  • 4
  • 23
  • 37
  • I don't understand what you mean in your comment to @CodeCaster's answer. What do you mean by "need to save screenshots of websites people submit to me"? – Chris Farmer Jun 27 '13 at 16:06
  • maybe I need to explain what I want in more detail. I am hosting a directory of websites that others submit to me. First I need to have some server side code for verification and I want to save and show the image of the thumbnail along with the url when listing the url. – happygilmore Jun 27 '13 at 16:10
  • So a user will submit a url to you and then you want your app (running somewhere on some server) to somehow load that url into a virtual browser and take a screenshot? – Chris Farmer Jun 27 '13 at 17:04
  • that was the original idea, but I'm open to other solutions if they work :-) – happygilmore Jun 27 '13 at 17:06

3 Answers3

3

Given your additional details, you should be able to do this with any number of tools. CodeCaster's idea is fine, and PhantomJS also offers similar webkit-based image generation of an arbitrary url (https://github.com/ariya/phantomjs/wiki/Screen-Capture). It offers several output format options, such as PNG, JPG, GIF, and PDF.

Since PhantomJS is using WebKit, a real layout and rendering engine, it can capture a web page as a screenshot. Because PhantomJS can render anything on the web page, it can be used to convert contents not only in HTML and CSS, but also SVG and Canvas.

You would need to execute the phantomjs.exe app from your MVC app, or probably even better by some service that is running behind the scenes to process a queue of submitted urls.

Chris Farmer
  • 24,974
  • 34
  • 121
  • 164
  • since I am on shared hosting I can't run PhantomJS, so I am looking at other options. I will inform this post when I find the best solution. Thanks though. – happygilmore Jun 28 '13 at 10:24
1

Why do you want to integrate this in MVC? Is it your website's responsibility to take screenshots of other websites? I would opt to create the screenshot-taking-logic in a separate library, hosted as a Windows Service for example.

The WebBrowser control needs to run on an UI thread, which a service (like IIS) doesn't have. You can try other libraries though.

You could for example write some code around wkhtmltopdf, which renders (as the name might suggest) HTML to PDF using the WebKit engine.

CodeCaster
  • 147,647
  • 23
  • 218
  • 272
  • Another library you can use is [Selenium WebDriver](http://stackoverflow.com/q/16795458/247702). – user247702 Jun 27 '13 at 15:51
  • @happygilmore that does not answer my question. Taking a screenshot of a website does not sound like something a website itself should do. Single Responsibility. Also take a look at the other commenters. – CodeCaster Jun 27 '13 at 15:56
  • @CodeCaster then I'm not sure I understand your question. I need to save the screenshots of websites people submit to me. I am not sure that javascript will help me do that. – happygilmore Jun 27 '13 at 16:04
  • Click the links, read, learn. There is no web browser built into ASP.NET MVC, so it cannot download and render other webpages. You need a library that simulates a browser, some of those libraries maybe aren't written in C#. That should not matter if, and I've been stressing that, you do not build the screenshot-taking-functionality into your MVC website, but in a separate application. Your website can then feed the user's URL to this application, the application takes a screenshot and informs your site again. – CodeCaster Jun 27 '13 at 17:30
  • I understand what you are saying now. When you say 'separate application' could you be more specific. – happygilmore Jun 27 '13 at 17:51
  • @happygilmore like I said, _"hosted as a Windows Service for example"_. You _can_ however just try to pick any of the mentioned libraries and try if it sits nicely besides MVC and integrate it in your website project if you think that makes things easier. – CodeCaster Jun 27 '13 at 17:54
  • @CodeCaster that probably makes the most sense, but in my case that is not practical since I have share hosting and no access. – happygilmore Jun 27 '13 at 18:04
1

You need to specify that the thread is in STA (single threaded apartment mode in order to instantiate the web browser).

public ActionResult Save()
{
    var url = "http://www.google.co.uk";

    FileContentResult result = null;
    Bitmap bitmap = null;

    var thread = new Thread(
    () =>
    {
        bitmap = ExportUrlToImage(url, 1280, 1024);
    });

    thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
    thread.Start();
    thread.Join();

    if (bitmap != null)
    {
        using (var memstream = new MemoryStream())
        {
            bitmap.Save(memstream, ImageFormat.Jpeg);
            result = this.File(memstream.GetBuffer(), "image/jpeg");
        }
    }

    return result;
}

private Bitmap ExportUrlToImage(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;

    Bitmap bitmap = new Bitmap(wb.Width, wb.Height);
    wb.DrawToBitmap(bitmap, new System.Drawing.Rectangle(0, 0, wb.Width, wb.Height));
    wb.Dispose();

    return bitmap;
}
obaylis
  • 2,904
  • 4
  • 39
  • 66