C# 및 .NET에서 스크린샷을 찍고 PDF를 생성하는 방법

발행: (2026년 2월 26일 오후 03:08 GMT+9)
4 분 소요
원문: Dev.to

Source: Dev.to

위에 제공된 소스 링크 외에 번역할 텍스트가 포함되어 있지 않습니다. 번역을 원하는 본문을 제공해 주시면 한국어로 번역해 드리겠습니다.

URL의 스크린샷

using System.Net.Http;
using System.Text;
using System.Text.Json;

public class PageBoltClient
{
    private static readonly HttpClient _http = new();
    private readonly string _apiKey = Environment.GetEnvironmentVariable("PAGEBOLT_API_KEY")!;
    private const string BaseUrl = "https://pagebolt.dev/api/v1";

    public async Task<byte[]> ScreenshotAsync(string url)
    {
        var body = JsonSerializer.Serialize(new
        {
            url,
            fullPage = true,
            blockBanners = true
        });

        using var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/screenshot");
        request.Headers.Add("x-api-key", _apiKey);
        request.Content = new StringContent(body, Encoding.UTF8, "application/json");

        using var response = await _http.SendAsync(request);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsByteArrayAsync();
    }
}

URL에서 PDF

public async Task<byte[]> PdfFromUrlAsync(string url)
{
    var body = JsonSerializer.Serialize(new { url, blockBanners = true });

    using var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/pdf");
    request.Headers.Add("x-api-key", _apiKey);
    request.Content = new StringContent(body, Encoding.UTF8, "application/json");

    using var response = await _http.SendAsync(request);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsByteArrayAsync();
}

HTML에서 PDF 생성 (PuppeteerSharp / wkhtmltopdf 대체)

public async Task<byte[]> PdfFromHtmlAsync(string html)
{
    var body = JsonSerializer.Serialize(new { html });

    using var request = new HttpRequestMessage(HttpMethod.Post, $"{BaseUrl}/pdf");
    request.Headers.Add("x-api-key", _apiKey);
    request.Content = new StringContent(body, Encoding.UTF8, "application/json");

    using var response = await _http.SendAsync(request);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsByteArrayAsync();
}

System.Text.Json.JsonSerializer.Serialize는 모든 이스케이프 처리를 수행합니다 — 수동으로 문자열을 만들 필요가 없습니다.

DI에서 싱글톤으로 등록 (권장)

// Program.cs
builder.Services.AddHttpClient();
builder.Services.AddSingleton<PageBoltClient>();
// PageBoltClient.cs — 생성자 주입 버전
public class PageBoltClient
{
    private readonly HttpClient _http;
    private readonly string _apiKey;
    private const string BaseUrl = "https://pagebolt.dev/api/v1";

    public PageBoltClient(HttpClient http, IConfiguration config)
    {
        _http   = http;
        _apiKey = config["PageBolt:ApiKey"]
                ?? throw new InvalidOperationException("PageBolt:ApiKey not configured");
    }

    public async Task<byte[]> ScreenshotAsync(string url) { /* … */ }
    public async Task<byte[]> PdfFromHtmlAsync(string html) { /* … */ }
}

ASP.NET Core 컨트롤러

using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("api/invoices")]
public class InvoiceController : ControllerBase
{
    private readonly PageBoltClient _pagebolt;
    private readonly IInvoiceService _invoices;
    private readonly IRazorViewRenderer _renderer;

    public InvoiceController(
        PageBoltClient pagebolt,
        IInvoiceService invoices,
        IRazorViewRenderer renderer)
    {
        _pagebolt = pagebolt;
        _invoices = invoices;
        _renderer = renderer;
    }

    [HttpGet("{id}/pdf")]
    public async Task<IActionResult> DownloadPdf(int id)
    {
        var invoice = await _invoices.GetByIdAsync(id);
        var html    = await _renderer.RenderAsync("Invoice", invoice);

        var pdf = await _pagebolt.PdfFromHtmlAsync(html);
        return File(pdf, "application/pdf", $"invoice-{id}.pdf");
    }

    [HttpGet("{id}/screenshot")]
    public async Task<IActionResult> GetScreenshot(int id)
    {
        var invoice = await _invoices.GetByIdAsync(id);
        var image   = await _pagebolt.ScreenshotAsync($"https://yourapp.com/invoices/{id}");

        return File(image, "image/png", $"invoice-{id}.png");
    }
}

Azure Functions (Consumption Plan)

여기서는 브라우저 없이 접근하는 방법이 빛을 발합니다. Azure Functions Consumption 플랜에서는 Chromium 바이너리 다운로드를 위한 쓰기 가능한 디렉터리가 없기 때문에 PuppeteerSharp와 Playwright가 실패합니다. HTTP 호출은 정상적으로 작동합니다:

[Function("GenerateInvoicePdf")]
public async Task<HttpResponseData> Run(
    [HttpTrigger(AuthorizationLevel.Function, "get", Route = "invoices/{id}/pdf")] HttpRequestData req,
    int id)
{
    var html = await _renderer.RenderAsync("Invoice", id);
    var pdf  = await _pagebolt.PdfFromHtmlAsync(html);

    var response = req.CreateResponse(HttpStatusCode.OK);
    response.Headers.Add("Content-Type", "application/pdf");
    response.Headers.Add("Content-Disposition", $"attachment; filename=\"invoice-{id}.pdf\"");
    await response.WriteBytesAsync(pdf);
    return response;
}

Author

Custodia-Admin

모든 코드 스니펫은 .NET 프로젝트에 바로 삽입할 수 있습니다. 외부 브라우저가 필요 없습니다.

브라우저 바이너리 없음. 파일 시스템 쓰기 없음. Consumption 플랜에서 작동합니다.

2분 안에 시작하기 – 월 100회 무료 요청, 신용카드 필요 없음

0 조회
Back to Blog

관련 글

더 보기 »

🚀 LeetCode Top 150 — 진행 로그 #1

진행 상황: 데이터 구조와 알고리즘 기반을 강화하기 위해 Top 150 LeetCode 여정을 공식적으로 시작했습니다. 진행률: 150문제 중 3문제 해결했습니다. R...

내가 만드는 모든 서비스는 죽는다

그게 바로 요점입니다. 저는 급여에서 직접 청구서를 결제할 수 있게 하는 핀테크 스타트업 Ontime Payments에서 시니어 소프트웨어 엔지니어로 일하고 있습니다. 우리는 의도적으로...

현대 웹 앱에서 OTP 인증 시작하기

왜 OTP 인증이 중요한가 - 로그인 또는 회원가입 시 사용자 신원을 확인합니다 - 가짜 계정 생성을 방지합니다 - 추가적인 보안 계층을 제공합니다 - 일반적으로 사용됩니다