How to Take a Screenshot with C# and .NET
You're building a .NET application that needs screenshots. You could self-host Puppeteer, but you'd be managing:
- Browser processes on your servers
- Memory and CPU overhead
- Crash recovery and logging
- Scaling across load-balanced instances
Use an API instead.
One HTTP request. Binary PNG response. Works with .NET 6+, ASP.NET Core, Azure Functions, and Console apps.
The Basic Pattern: HttpClient Binary Download
curl -X POST https://api.pagebolt.dev/v1/screenshot \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com","format":"png","fullPage":true}' \
> screenshot.png
In C#, use HttpClient for type-safe requests:
C# Implementation: HttpClient Binary Response
using System;
using System.Net.Http;
using System.Text.Json;
using System.Threading.Tasks;
public class PageBoltClient
{
private readonly string _apiKey;
private readonly HttpClient _httpClient;
private const string BaseUrl = "https://api.pagebolt.dev/v1";
public PageBoltClient(string apiKey)
{
_apiKey = apiKey;
_httpClient = new HttpClient();
}
public async Task TakeScreenshotAsync(string url, string outputPath)
{
var payload = new
{
url = url,
format = "png",
width = 1280,
height = 720,
fullPage = true,
blockBanners = true
};
var content = new StringContent(
JsonSerializer.Serialize(payload),
System.Text.Encoding.UTF8,
"application/json"
);
var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/screenshot")
{
Content = content,
Headers = { { "Authorization", $"Bearer {_apiKey}" } }
};
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
throw new Exception($"API error: {response.StatusCode}");
}
// Response is binary PNG — read as bytes
var imageBytes = await response.Content.ReadAsByteArrayAsync();
// Write directly to file
await File.WriteAllBytesAsync(outputPath, imageBytes);
Console.WriteLine($"Screenshot saved to {outputPath}");
}
public static async Task Main(string[] args)
{
var client = new PageBoltClient(Environment.GetEnvironmentVariable("PAGEBOLT_API_KEY"));
await client.TakeScreenshotAsync("https://example.com", "screenshot.png");
}
}
Key points:
-
ReadAsByteArrayAsync()handles binary PNG data -
File.WriteAllBytesAsync()writes bytes directly to disk - No JSON decoding — response is raw PNG bytes
Production Pattern: Reusable Service
using System;
using System.Net.Http;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
public class ScreenshotOptions
{
[JsonPropertyName("url")]
public string Url { get; set; }
[JsonPropertyName("format")]
public string Format { get; set; } = "png";
[JsonPropertyName("width")]
public int Width { get; set; } = 1280;
[JsonPropertyName("height")]
public int Height { get; set; } = 720;
[JsonPropertyName("fullPage")]
public bool FullPage { get; set; } = true;
[JsonPropertyName("blockBanners")]
public bool BlockBanners { get; set; } = true;
[JsonPropertyName("blockAds")]
public bool BlockAds { get; set; } = false;
[JsonPropertyName("darkMode")]
public bool DarkMode { get; set; } = false;
}
public interface IScreenshotService
{
Task<byte[]> TakeScreenshotAsync(ScreenshotOptions options);
Task SaveScreenshotAsync(ScreenshotOptions options, string filePath);
}
public class PageBoltScreenshotService : IScreenshotService
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly ILogger<PageBoltScreenshotService> _logger;
private const string BaseUrl = "https://api.pagebolt.dev/v1";
public PageBoltScreenshotService(
HttpClient httpClient,
ILogger<PageBoltScreenshotService> logger,
string apiKey)
{
_httpClient = httpClient;
_logger = logger;
_apiKey = apiKey;
}
public async Task<byte[]> TakeScreenshotAsync(ScreenshotOptions options)
{
if (string.IsNullOrEmpty(options.Url))
throw new ArgumentException("URL is required");
var content = new StringContent(
System.Text.Json.JsonSerializer.Serialize(options),
System.Text.Encoding.UTF8,
"application/json"
);
var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/screenshot")
{
Content = content,
Headers = { { "Authorization", $"Bearer {_apiKey}" } }
};
var response = await _httpClient.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_logger.LogError($"Screenshot API error: {response.StatusCode}");
throw new Exception($"API error: {response.StatusCode}");
}
return await response.Content.ReadAsByteArrayAsync();
}
public async Task SaveScreenshotAsync(ScreenshotOptions options, string filePath)
{
var imageBytes = await TakeScreenshotAsync(options);
await File.WriteAllBytesAsync(filePath, imageBytes);
_logger.LogInformation($"Screenshot saved to {filePath}");
}
}
ASP.NET Core Integration
Register in dependency injection:
// Program.cs
builder.Services.AddHttpClient<IScreenshotService, PageBoltScreenshotService>()
.ConfigureHttpClient(client =>
{
client.Timeout = TimeSpan.FromSeconds(30);
});
builder.Services.AddScoped(provider =>
new PageBoltScreenshotService(
provider.GetRequiredService<HttpClient>(),
provider.GetRequiredService<ILogger<PageBoltScreenshotService>>(),
Environment.GetEnvironmentVariable("PAGEBOLT_API_KEY")
)
);
var app = builder.Build();
// Controller
[ApiController]
[Route("api/[controller]")]
public class ScreenshotsController : ControllerBase
{
private readonly IScreenshotService _screenshotService;
public ScreenshotsController(IScreenshotService screenshotService)
{
_screenshotService = screenshotService;
}
[HttpPost]
public async Task<IActionResult> TakeScreenshot([FromQuery] string url)
{
try
{
var options = new ScreenshotOptions { Url = url, FullPage = true };
var imageBytes = await _screenshotService.TakeScreenshotAsync(options);
return File(imageBytes, "image/png");
}
catch (Exception ex)
{
return StatusCode(500, ex.Message);
}
}
}
app.MapControllers();
app.Run();
Test it:
curl -X POST "http://localhost:5000/api/screenshots?url=https://example.com" \
> screenshot.png
Real Use Case: Background Job Service
Generate screenshots in background tasks using Hangfire:
using Hangfire;
public class ReportService
{
private readonly IScreenshotService _screenshotService;
private readonly IBackgroundJobClient _jobClient;
public ReportService(
IScreenshotService screenshotService,
IBackgroundJobClient jobClient)
{
_screenshotService = screenshotService;
_jobClient = jobClient;
}
public void GenerateReportScreenshot(long reportId, string reportUrl)
{
// Queue background job
_jobClient.Enqueue(() => GenerateScreenshotJob(reportId, reportUrl));
}
[AutomaticRetry(Attempts = 3)]
public async Task GenerateScreenshotJob(long reportId, string reportUrl)
{
var outputPath = Path.Combine("reports", $"report-{reportId}.png");
try
{
var options = new ScreenshotOptions { Url = reportUrl, FullPage = true };
await _screenshotService.SaveScreenshotAsync(options, outputPath);
// Update database
// await _reportRepository.UpdateScreenshotPathAsync(reportId, outputPath);
}
catch (Exception ex)
{
throw; // Hangfire will retry
}
}
}
// In Startup/Program.cs
app.UseHangfireDashboard();
app.UseHangfireServer();
NuGet Installation
No additional packages needed for .NET 6+. HttpClient and System.Text.Json are built-in.
For older .NET Framework projects:
<ItemGroup>
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.Json" Version="6.0.0" />
</ItemGroup>
Error Handling and Retry Logic
public class PageBoltScreenshotService : IScreenshotService
{
public async Task<byte[]> TakeScreenshotWithRetryAsync(
ScreenshotOptions options,
int maxRetries = 3)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
return await TakeScreenshotAsync(options);
}
catch (HttpRequestException ex) when (attempt < maxRetries)
{
// Exponential backoff
var delayMs = 1000 * (int)Math.Pow(2, attempt - 1);
_logger.LogWarning($"Retry {attempt}/{maxRetries} after {delayMs}ms");
await Task.Delay(delayMs);
}
}
throw new Exception("Max retries exceeded");
}
}
Testing: Mock the Service
using Moq;
using Xunit;
public class ScreenshotServiceTests
{
[Fact]
public async Task TakeScreenshot_ReturnsBytes()
{
// Arrange
var mockHttpClientFactory = new Mock<IHttpClientFactory>();
var mockLogger = new Mock<ILogger<PageBoltScreenshotService>>();
var service = new PageBoltScreenshotService(
new HttpClient(),
mockLogger.Object,
"test-key"
);
var options = new ScreenshotOptions { Url = "https://example.com" };
// Act & Assert
// Use a test HTTP handler or mock HttpClient
var result = await service.TakeScreenshotAsync(options);
Assert.NotNull(result);
Assert.NotEmpty(result);
}
}
Pricing
| Plan | Requests/Month | Cost | Best For |
|---|---|---|---|
| Free | 100 | $0 | Learning, low-volume projects |
| Starter | 5,000 | $29 | Small teams, moderate use |
| Growth | 25,000 | $79 | Production apps, frequent calls |
| Scale | 100,000 | $199 | High-volume automation |
Summary
- ✅ Idiomatic C# with HttpClient and async/await
- ✅ Binary response handling with ReadAsByteArrayAsync()
- ✅ File writing with File.WriteAllBytesAsync()
- ✅ ASP.NET Core dependency injection
- ✅ Background job integration (Hangfire)
- ✅ Error handling and retry logic
- ✅ No browser management
- ✅ Works in web apps, Windows services, Azure Functions
Get started free: pagebolt.dev — 100 requests/month, no credit card required.
Top comments (0)