There are many reasons you, as a developer, might want to screenshot a webpage. For example, you may want to take regular screenshots of a website for monitoring or compliance purposes, generate images from dynamic HTML, CSS, and SVG, or create image previews of URLs for social media or your own directory of links.
When you deal with webpage screenshots, it's common to think you need to use JavaScript to interact with a web page to screenshot it. For example, you might think of writing a Node.js service that will accept REST requests for screenshots, process them, and return the screenshot. Granted, this is a possible solution, but it isn't straightforward!
This article will show you how to take screenshots of webpages in an ASP.NET Core application without JavaScript. First, you'll set up a Reading List Web API. Then, you'll see how you can use different NuGet packages to take screenshots of webpages from an ASP.NET Core application. You'll also learn to use the Urlbox library to take screenshots asynchronously.
Prerequisites
The code samples in this article are compatible with .NET 6. .NET 6 is the latest LTS version of .NET from Microsoft, which lets you write performant, cross-platform applications using a single codebase. Please download the .NET 6 SDK to follow along with the code samples.
Many code samples use Google Chrome to render and screenshot the web pages. While most libraries automatically download Chrome, it's best to manually install it before starting so that you have all the necessary dependencies to run the browser from code.
This tutorial also requires PowerShell 7.2 for some of the NuGet package setup scripts. PowerShell is a cross-platform task automation solution developed by Microsoft. You can download the latest version of PowerShell for your platform here.
To demonstrate taking screenshots in an ASP.NET Core application, you'll build a basic Reading List Web API. The API will let you save and retrieve URLs in your reading list. You'll also see how you can take screenshots of those URLs for link previews on your user interface.
You can download the starter code from this GitHub repository if you want to skip to taking screenshots in ASP.NET Core.
Create the Project
Open a new terminal window and navigate to the folder where you want to store your code. Next, run the following command to create a new ASP.NET Core Web API using the .NET CLI.
Write the IScreenshotService Interface
Interfaces make it easy to describe method headers without implementing them. You'll write an IScreenshotService interface to define the methods available to screenshot a web page. This interface will make it easy to update the application to use the different screenshot service implementations presented in this article.
Create a Services folder in the project directory. Then, create a Screenshot subdirectory in that folder. Next, create the file IScreenshotService.cs and paste in the code below:
IScreenshotService.cs
Create the IReadingListService Interface and Implement It
It's best practice to keep controller methods lightweight. To do this, you'll implement the Reading List API's logic in a service.
In the Services folder, create a ReadingList folder. Next, you'll make the model classes and service interface and class. Create Models and DTO (Data Transfer Object) folders inside the ReadingList folder. Inside the Models folder, create a ReadingItemModel class, which will store the URL in the database.
Create the file ReadingItemModel.cs with the following content:
Next, you'll create data transfer objects that you'll use for requests and responses in the REST API. First, create a ReadingItemDTO.cs file in the DTO folder. This DTO will return the details for an item in the database. Then, copy the following code into the new file named ReadingItemDTO.cs:
Make a new file in the DTO folder and call it ReadingItemCreateDTO.cs. This DTO will be the request object for creating a new item in the database. You can paste the following code into the file:
Once you've created the necessary model and DTO objects, you'll need to write the service interface and implement it. To do this, create the file IReadingListService.cs in the ReadingList folder and copy the following code into it:
Once you've created the interface, implement it by creating a ReadingListService.cs file and copying the following class code into it:
Finally, register the ReadingListService implementation in the Program.cs file by adding this line after creating the builder variable. Further down in the same file, add code to create the screenshots folder if it doesn't already exist:
Make sure you configure the ScreenshotsFolder in the appsettings.json file:
Set Up the ReadingListController
The last scaffolding step is to set up the ReadingListController class. This class will expose the actual endpoints in the REST API. In the Controllers folder, create a ReadingListController.cs file and paste in the following code:
You are now ready to start implementing screenshot functionality in your application.
Using PuppeteerSharp
The PuppeteerSharpNuGet package is a .NET port of the popular Node.js Puppeteer API. The package lets you use Google Chrome programmatically for various automation tasks, such as taking screenshots.
With a terminal open in the project folder, run the following command to install the package:
You'll now implement the IScreenshotService interface using the PuppeteerSharp package. First, create a file called PuppeteerSharpScreenshotService.cs in the Services > Screenshot folder. Then, copy and paste the following code into the file:
The method above first downloads Google Chrome if it hasn't already been downloaded. It then creates a new browser instance with some launch options. Once the browser runs, the method creates a new page and navigates to the specified URL; when the page has loaded, the code takes a screenshot and closes the browser. Finally, the method returns the screenshot's raw bytes.
In the Program.cs file, register the PuppeteerSharpScreenshotService class so you can use it in the ReadingListService. Do this by adding the following line before the line that registers the ReadingListService class:
Start the application by running the following command in the terminal:
Once the project runs, open the Swagger page in a browser by going to https://localhost:<PORT>/swagger/index.html.
Add a new URL to your reading list on the Swagger page using the POST /ReadingList endpoint. The endpoint will save the object to the database, take a screenshot of the URL, and return the ID of the new reading item in the response.
Copy the ID from the response object and use it in the GET /ReadingList/{id}/screenshot endpoint. This endpoint will retrieve the screenshot for the saved URL.
Below is a screenshot of the TechCrunch homepage taken with PuppeteerSharp:
The screenshot is appropriately sized and portrays the web page accurately. However, one immediate eyesore is the advertisements on the page's top and right. There is also a banner at the bottom of the web page, which looks unappealing, and a scrollbar on the right side of the page.
Using Selenium
Selenium is a browser automation tool similar to PuppeteerSharp. However, unlike PuppeteerSharp, Selenium is compatible with several browser vendors, meaning you're not limited to using Chromium-based browsers. The Selenium WebDriver API enables the library to communicate with different browsers.
Run the following commands in the terminal. The first command installs the Selenium NuGet package. The second command installs a NuGet package that will assist in downloading the correct WebDriver to use with Chrome:
Once you've installed the packages, create another class called SeleniumScreenshotService in the Services > Screenshot directory, and paste in the following code:
The code first downloads the correct WebDriver for Chrome using the WebDriverManager package. It then creates a new window in Chrome and navigates to the specified URL. Once the website has loaded, the method takes a screenshot and receives the image's raw bytes. Finally, the browser is closed, and the raw bytes are returned.
In the Program.cs file, replace the line that registered the PuppeteerSharpScreenshotService with the following:
Rerun the application using the following command:
Navigate to the Swagger page and follow the same process as before to save a new URL to the reading list and retrieve its screenshot. You should see an image similar to the one below if you saved the TechCrunch homepage:
The screenshot looks remarkably similar to the one taken with PuppeteerSharp. You'll notice that advertisements are still appearing on the page. The ugly scrollbar is also still visible, as is the distracting bottom banner.
Using Playwright
Playwright is a modern browser automation library developed by Microsoft. While it's designed primarily for end-to-end testing of web apps, you can also use it for browser automation tasks. Like Selenium, it supports multiple browser vendors with a single API.
After adding the package to the project, Playwright must complete its setup with a PowerShell script. To do this, build the project. Once built, run the PowerShell script and wait for it to finish:
In the Services > Screenshot folder, create a file called PlaywrightScreenshotService.cs and paste the following code in there:
The Playwright implementation is the most straightforward code so far! The code first instantiates a new instance of Playwright. Next, the Playwright instance launches Chrome. Once the browser runs, a new page is created with the desired viewport dimensions and used to navigate to the specified URL. Finally, the method takes a screenshot and returns the raw bytes.
Update the Program.cs file to use the PlaywrightScreenshotService implementation of the IScreenshotService interface:
Rerun the application:
Open Swagger and use the REST endpoints to save a URL and retrieve its screenshot. Below is an example of a screenshot of the TechCrunch homepage taken with Playwright.
Once again, the screenshot looks similar to the previous screenshots. The advertisements and ugly scrollbar are still visible, and the banner on the bottom also still blocks portions of the page. While this is easier to implement, the final screenshot has the same flaws as the previous screenshots.
Sign up for a seven-day free trial. You can log in after confirming your email address. When you log in, you'll be taken to the Dashboard page, where you'll find your API Key and Secret. You'll need these later, so be sure to take note of them.
To interact with Urlbox, you will use the Urlbox .NET library. The library wraps the Urlbox REST API and makes it easy to interact with Urlbox from .NET code.
Download the Urlbox .NET GitHub repository, extract the ZIP file, and copy the Urlbox folder to your project folder. Once copied, run the following command to reference the Urlbox project from the ReadingListApi project:
Now that your application references the Urlbox library, you'll write a screenshot service implementation that uses Urlbox. To do this, create a UrlboxScreenshotService.cs file in the Services > Screenshot folder and paste in the following code:
This short code sample has a lot going on. The code requests a PNG file before taking the screenshot. The method also specifies the width and height of the screenshot. Some of Urlbox's more powerful features, such as ad blocking, retina screenshots, and CSS selector-based element hiding, are employed. After configuring the options for the screenshot, the code downloads the image as a Base64 string from Urlbox and returns the raw bytes.
Update the Program.cs file to register the UrlboxScreenshotService. You also need to configure and register the Urlbox service so the UrlboxScreenshotService class can use it:
When configuring the Urlbox service, the code retrieves the API key and secret from the configuration object. Update the appsettings.json file to include your Urlbox API key and secret:
Run the application:
Open Swagger in your browser and save a URL. Then, retrieve the screenshot and see the result. To demonstrate, if you took a screenshot of the TechCrunch homepage, you should see something similar to the image below:
The screenshot looks much better! You'll first notice that the ads are gone, as is the scrollbar. The screenshot is also retina quality, resulting in superior image quality compared to the earlier screenshots. The banner at the page's bottom is also gone, thanks to the custom selector used in the code. Overall, this screenshot looks much better than the previous ones.
Using Urlbox with Webhooks
As a bonus exercise, you'll see how you can use webhooks with Urlbox to make API calls asynchronously. In other words, you can send a request for a screenshot without needing to wait for a response. Instead, Urlbox will send a JSON message to your specified webhook URL once the screenshot is complete.
First, install the Newtonsoft.Json NuGet package in your project. You'll use this to parse JSON content to dynamics in .NET. Add the package with the following command:
In the Services > Screenshot directory, create a file called IWebhookScreenshotService.cs. The file will contain an interface describing methods that you can use to take a screenshot asynchronously. Paste the following code in the file:
Instead of having one method to take a screenshot and return the raw bytes, there are two. The first method accepts a URL as a parameter, sends a request to take a screenshot, and then returns a Render ID that you can later use to reconcile the webhook message with a specific reading item. The second method accepts a webhook message as a parameter so that it can download the rendered screenshot. You'll notice the webhook message parameter is dynamic to support different webhook formats. Finally, the method returns the raw screenshot bytes and Render ID, which you can use to save the screenshot to the correct reading item in the database.
Write an implementation for this interface by creating a new file called UrlboxWebhookScreenshotService.cs in the Services > Screenshot directory and pasting in the following code:
In the method to send a screenshot request (SendScreenshotUrlRequestAsync), you'll notice the request to the Urlbox service is slightly different from before. The code sends a POST request to a /render endpoint. In addition, the code also specifies a webhook_url configuration option. This option contains the endpoint URL that Urlbox will call when the screenshot is complete.
When you look at the method to process a webhook (ProcessScreenshotUrlWebhookAsync), the code retrieves the renderUrl field from the webhook. This URL is then used to retrieve the completed screenshot file.
You'll notice that the code uses the "BaseUrl" configuration option to construct the webhook endpoint. For this to work, you must add it to your appsettings.json file:
Now that you've created a Urlbox webhook service, you must update the ReadingListService to use the IWebhookScreenshotService instead of the IScreenshotService. First, open IReadingListService.cs and create another method called ProcessScreenshotWebhookAsync to process a webhook:
Once you've updated the interface, change the ReadingListService class by adding the new method and updating the existing methods to use IWebhookScreenshotService instead of IScreenshotService:
Now, instead of saving the image to a file right after sending a screenshot request, the code saves the Render ID to the reading item in the database. This minor change will require an update on the ReadingListModel class, which you will do next. However, before doing that, notice that the method that processes the webhook uses the screenshot service to download the rendered screenshot and then saves it to the correct file.
Make the following update to the ReadingListModel.cs file so that you can store the Render ID:
The Program.cs file also needs to be updated to register the new Urlbox service. Replace the code that registers the existing Urlbox service with the code below:
Lastly, update the ReadingListController.cs file to add a new endpoint to which Urlbox can send the webhook message. Do this by opening the file and adding the following method to the bottom of the class:
The request's body is converted to a dynamic type using Newtonsoft's ExpandoObjectConverter. The code then passes the converted type to the ProcessScreenshotWebhookAsync method on the IReadingListService.
You can now run the application. First, Urlbox needs to call your webhook endpoint, so you will need to host the application somewhere like Azure or use a reverse proxy like ngrok to expose your localhost to the internet. Once that's sorted, use the Swagger page to request a screenshot and download it.
The initial request to take the screenshot is much faster because the code doesn't wait for the image to download. Instead, a few seconds after saving the URL to our reading list, Urlbox calls the webhook and sends it the URL to the completed screenshot, which the application then downloads and saves.
Conclusion
You might want to incorporate screenshots into your ASP.NET Core application for many reasons. In this article, you've seen how you can use different NuGet packages to take screenshots of a website locally. You also saw the benefit of using a screenshot service like Urlbox. With it, you can take ad-free, high-quality screenshots with minimal effort in an ASP.NET Core application. Finally, you learned how to use the webhook functionality provided by Urlbox to take screenshots asynchronously.
If you're looking for an easy, quick, and affordable way to create high-quality screenshots that stand out, look no further than Urlbox.