Build Your Own Websites Screenshot DLL: Step-by-Step TutorialCapturing website screenshots programmatically is useful for automated testing, thumbnail generation, content monitoring, and more. This tutorial walks you through building a reusable, lightweight Websites Screenshot DLL in C# (.NET) that can be consumed by desktop apps, web services, or other .NET projects. We’ll cover architecture, required tools, rendering approaches, implementation, packaging as a DLL, usage examples, and troubleshooting tips.
Overview and choices
There are two main approaches to capture website screenshots from code:
- Browser-based headless rendering (recommended for full, accurate rendering): run a headless browser engine such as Chromium (via Puppeteer/Playwright) or use the WebView2 control (Edge Chromium) to render pages exactly like a user’s browser.
- HTML/CSS engines or libraries (lighter but less accurate): use libraries that render HTML to image directly (for very simple pages); these often fail on modern CSS/JS-heavy sites.
For a robust DLL, choose a headless Chromium approach. On Windows/.NET, options include:
- Playwright for .NET (cross-platform, modern)
- Puppeteer Sharp (Chromium control)
- Microsoft.Web.WebView2 (Edge/Chromium-based; integrates well on Windows)
This tutorial will implement a flexible solution using Playwright for .NET for cross-platform compatibility and reliability. We’ll also show a simpler option using WebView2 for Windows-only scenarios.
Prerequisites
- .NET 7.0 or later SDK installed (adjust to your target runtime).
- Visual Studio 2022 / VS Code or another IDE.
- NuGet packages: Microsoft.Playwright (or Playwright for .NET), (optional) Microsoft.Web.WebView2.
- Basic C# knowledge.
- Optional: Docker if you want containerized headless Chromium.
Project structure
Create a class library project that will compile to a DLL. Example structure:
- ScreenshotLib (class library)
- IScreenshotService.cs
- PlaywrightScreenshotService.cs
- WebView2ScreenshotService.cs (optional)
- ScreenshotOptions.cs
- Utilities.cs
- README.md
- Tests/ (unit/integration tests)
Define public API
Keep the DLL API small and stable. Example interface:
public interface IScreenshotService { /// <summary> /// Captures a screenshot of the specified URL. /// </summary> /// <param name="url">Page URL.</param> /// <param name="options">Screenshot options like width, height, fullPage, format.</param> /// <returns>Byte array containing the image (PNG/JPEG).</returns> Task<byte[]> CaptureAsync(string url, ScreenshotOptions options, CancellationToken cancellationToken = default); }
ScreenshotOptions example:
public class ScreenshotOptions { public int Width { get; set; } = 1365; public int Height { get; set; } = 768; public bool FullPage { get; set; } = false; public string Format { get; set; } = "png"; // "png" or "jpeg" public int Quality { get; set; } = 80; // for jpeg public int TimeoutMs { get; set; } = 30000; public string UserAgent { get; set; } = null; public Dictionary<string,string> ExtraHeaders { get; set; } = null; public bool Headless { get; set; } = true; }
Implementing PlaywrightScreenshotService
Playwright provides a simple, reliable API for launching a headless browser, navigating to a page, waiting for content to load, and capturing screenshots.
-
Add the NuGet package:
dotnet add package Microsoft.Playwright
-
Initialize Playwright at runtime. Playwright requires browser binaries — use Playwright install step during development or orchestrate installation in deployment.
-
Example implementation:
using Microsoft.Playwright; using System.Threading; public class PlaywrightScreenshotService : IScreenshotService, IAsyncDisposable { private readonly IPlaywright _playwright; private readonly IBrowser _browser; private PlaywrightScreenshotService(IPlaywright playwright, IBrowser browser) { _playwright = playwright; _browser = browser; } public static async Task<PlaywrightScreenshotService> CreateAsync(bool headless = true) { var playwright = await Playwright.CreateAsync(); var browser = await playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions { Headless = headless }); return new PlaywrightScreenshotService(playwright, browser); } public async Task<byte[]> CaptureAsync(string url, ScreenshotOptions options, CancellationToken cancellationToken = default) { var context = await _browser.NewContextAsync(new BrowserNewContextOptions { ViewportSize = new ViewportSize { Width = options.Width, Height = options.Height }, UserAgent = options.UserAgent, ExtraHTTPHeaders = options.ExtraHeaders }); var page = await context.NewPageAsync(); await page.GotoAsync(url, new PageGotoOptions { Timeout = options.TimeoutMs, WaitUntil = WaitUntilState.Load }); // Optional: wait for network idle or selector if (options.FullPage) { var buffer = await page.ScreenshotAsync(new PageScreenshotOptions { FullPage = true, Type = options.Format == "jpeg" ? ScreenshotType.Jpeg : ScreenshotType.Png, Quality = options.Format == "jpeg" ? options.Quality : null }); await context.CloseAsync(); return buffer; } else { var buffer = await page.ScreenshotAsync(new PageScreenshotOptions { FullPage = false, Type = options.Format == "jpeg" ? ScreenshotType.Jpeg : ScreenshotType.Png, Quality = options.Format == "jpeg" ? options.Quality : null }); await context.CloseAsync(); return buffer; } } public async ValueTask DisposeAsync() { await _browser.CloseAsync(); _playwright.Dispose(); } }
Notes:
- Reuse browser instance for performance; create new contexts for isolation.
- Consider pooling contexts/pages when high throughput is needed.
Windows-only: WebView2 alternative
WebView2 uses Edge’s rendering engine and is simple for Windows desktop apps. It can be embedded in a hidden window that renders the page, then capture as a bitmap. WebView2 is not ideal for headless Linux servers.
High-level steps:
- Add Microsoft.Web.WebView2 NuGet.
- Create a WebView2 environment and a CoreWebView2Controller attached to an invisible window.
- Navigate to URL, await CoreWebView2.NavigationCompleted, then use CapturePreviewAsync to get image stream.
- Convert stream to byte[].
This approach is straightforward for desktop apps but requires Win32 UI thread handling.
Packaging as a DLL
- Build the class library: set Project SDK to Microsoft.NET.Sdk and TargetFramework to net7.0 (or your target).
- Ensure Playwright browser binaries are available. For deployment:
- Call Playwright.InstallAsync() during app setup (one-time).
- Or include browser artifacts in deployment and set PLAYWRIGHT_BROWSERS_PATH environment variable.
- Generate XML docs and strong-name/sign the assembly if needed.
- Publish NuGet package if you want internal distribution.
Example csproj snippet:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net7.0</TargetFramework> <Nullable>enable</Nullable> <GeneratePackageOnBuild>true</GeneratePackageOnBuild> <PackageId>ScreenshotLib</PackageId> <Version>1.0.0</Version> <Authors>YourName</Authors> </PropertyGroup> <ItemGroup> <PackageReference Include="Microsoft.Playwright" Version="1.40.0" /> </ItemGroup> </Project>
Adjust Playwright version to the latest compatible release.
Usage examples
Console app example:
var service = await PlaywrightScreenshotService.CreateAsync(); var options = new ScreenshotOptions { Width = 1280, Height = 720, FullPage = false, Format = "png" }; var bytes = await service.CaptureAsync("https://example.com", options); await File.WriteAllBytesAsync("example.png", bytes); await service.DisposeAsync();
ASP.NET Core example (background service):
- Create a singleton PlaywrightScreenshotService at app start.
- Use scoped calls to CaptureAsync from controllers or background queues.
- Be careful with concurrency — reuse browser and create contexts per request.
Performance, scaling, and reliability tips
- Reuse a single browser instance to avoid startup overhead.
- Use browser contexts and page pools; close contexts when done.
- Limit concurrency to avoid high memory/CPU usage; experiment with 2–8 concurrent pages per browser process depending on resources.
- For heavy loads, run multiple containerized instances each with its own browser.
- Use timeouts, retries, and circuit-breaker patterns to handle slow pages.
- Cache screenshots for identical URLs or frequent captures.
- Monitor memory and orphaned processes; ensure proper disposal.
Security considerations
- Run untrusted pages in sandboxed contexts to avoid resource exhaustion.
- Avoid exposing the DLL API to unvalidated inputs that could be abused to make requests to internal networks (SSRF).
- Apply request rate limiting when used in web services.
Testing and CI
- Add integration tests that run Playwright with stable test pages (hosted locally or in CI).
- In CI, install Playwright browsers as part of pipeline (playwright install).
- Use headless mode in CI and mock network calls if necessary.
Troubleshooting common issues
- “Chromium not installed”: run Playwright.InstallAsync() or include browsers in deployment.
- Flickering or partial renders: wait for network idle or specific selectors with page.WaitForSelectorAsync.
- Memory leaks: ensure contexts and pages are closed/disposed.
- Incorrect styles: emulate viewport and user agent similar to target environment.
- WebView2 errors on server: WebView2 requires UI/Win32 environment; not suitable for headless servers.
Extensions and features to add
- PDF capture option.
- DOM snapshot and metadata (title, meta tags).
- Mobile device emulation (user agent, viewport, device scale factor).
- Watermarking, resizing, and format conversion.
- Rate limiting, queuing, and distributed workers for scale.
Summary
Building a Websites Screenshot DLL is about choosing the right rendering approach, designing a small stable API, handling lifecycle and performance, and packaging for distribution. For modern, accurate results, use Playwright (or Puppeteer) to drive Chromium; for Windows-only desktop apps, WebView2 is a viable simpler alternative. Implement proper resource management, security, and scaling patterns to make the DLL production-ready.
Leave a Reply