PDFPipe

Guide

Generate a PDF from HTML in .NET

You have an ASP.NET Core endpoint, some HTML (an invoice, a policy document, a packing slip), and you need to return a PDF. The .NET answers each have a catch: PuppeteerSharp downloads a full Chromium on first run, DinkToPdf wraps an unmaintained native library that crashes under concurrent load, and the polished commercial options bill per server. The lighter path is one HttpClient call.

Minimal API endpoint

The PDFPipe endpoint is POST https://api.pdfpipe.xyz/v1/pdf. Send a JSON body with your HTML and options, attach a bearer token, and you get back raw application/pdf bytes. Register a named HttpClient in Program.cs so the connection pool is shared across requests.

csharp
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHttpClient("pdfpipe", client =>
{
    client.BaseAddress = new Uri("https://api.pdfpipe.xyz");
    client.Timeout = TimeSpan.FromSeconds(30);
    client.DefaultRequestHeaders.Authorization = new("Bearer",
        Environment.GetEnvironmentVariable("PDFPIPE_API_KEY"));
});

var app = builder.Build();

app.MapGet("/invoice/{id}", async (string id, IHttpClientFactory factory) =>
{
    var html = $$"""
        <html>
          <body style="font-family: sans-serif; padding: 40px">
            <h1>Invoice #{{id}}</h1>
            <p>Amount due: $420.00</p>
          </body>
        </html>
        """;

    var client = factory.CreateClient("pdfpipe");
    var response = await client.PostAsJsonAsync("/v1/pdf", new
    {
        html,
        options = new { format = "A4" }
    });

    if (!response.IsSuccessStatusCode)
    {
        var detail = await response.Content.ReadAsStringAsync();
        return Results.Problem($"PDF render failed: {detail}", statusCode: 502);
    }

    var stream = await response.Content.ReadAsStreamAsync();
    return Results.Stream(stream, "application/pdf",
        fileDownloadName: $"invoice-{id}.pdf");
});

app.Run();

Results.Stream forwards the bytes without buffering the full document in memory, and IHttpClientFactory handles connection pooling so this scales with your normal request load.

MVC controller version

If you prefer the traditional MVC layout, inject the factory through the constructor. The render call is the same.

csharp
public class InvoiceController : Controller
{
    private readonly HttpClient _pdf;

    public InvoiceController(IHttpClientFactory factory)
        => _pdf = factory.CreateClient("pdfpipe");

    public async Task<IActionResult> Download(string id)
    {
        var html = $"<html><body><h1>Invoice #{id}</h1></body></html>";

        var response = await _pdf.PostAsJsonAsync("/v1/pdf", new
        {
            html,
            options = new { format = "A4" }
        });

        response.EnsureSuccessStatusCode();

        var bytes = await response.Content.ReadAsByteArrayAsync();
        return File(bytes, "application/pdf", $"invoice-{id}.pdf");
    }
}

Resilience with Polly

Renders are idempotent, so the standard resilience handler is safe to add. One line on the named client gives you retries with backoff and a circuit breaker.

csharp
builder.Services.AddHttpClient("pdfpipe", /* ... */)
    .AddStandardResilienceHandler(); // Microsoft.Extensions.Http.Resilience

Rendering from a URL

If the document is already hosted at a URL, pass that instead of inlining HTML.

csharp
await client.PostAsJsonAsync("/v1/pdf", new
{
    url = "https://example.com/report/42",
    options = new { format = "A4" }
});

Why not PuppeteerSharp or DinkToPdf?

ConcernIn-process renderingHttpClient to PDFPipe
Deploy sizePuppeteerSharp pulls ~300 MB Chromium at first runNothing beyond your app
StabilityDinkToPdf's native wkhtmltox crashes under concurrent loadPlain HTTP, no native interop
LicensingCommercial libraries bill per server or per developerFlat API pricing
Azure / containersChromium needs sandbox flags and extra packagesRuns on any tier, including consumption plans

Keep the API key in user secrets locally and your secret store in production. The options object also accepts other page sizes (for example Letter), margins, and landscape orientation, documented in full on the docs page.

Paste your own HTML into the live playground on the home page to see the PDF render instantly, then read the docs for every option.

See pricing →