HTML to PDF
in C# and .NET.

The .NET ecosystem has excellent PDF libraries, and most of the good ones are either commercial or want you to describe the document in C# rather than HTML. If you already have the HTML, send it.

The call

Post your markup with an API key. The PDF comes back in the same response, base64 encoded by default. Nothing is written to disk on our side.

Program.cs
using System.Net.Http.Headers;
using System.Net.Http.Json;

var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("HTML4PDF_KEY"));

var response = await http.PostAsJsonAsync("https://api.html4pdf.com/generate-pdf", new
{
    html = "<h1>Invoice INV-1042</h1><p>Total: $1,645.40</p>",
    responseFormat = "binary",
    options = new { format = "A4", printBackground = true }
});

response.EnsureSuccessStatusCode();

await File.WriteAllBytesAsync("invoice.pdf", await response.Content.ReadAsByteArrayAsync());

From an ASP.NET Core endpoint

Render your Razor view to a string, post it, and return a FileResult. Use IHttpClientFactory rather than a new HttpClient per request.

InvoicesController.cs
[HttpGet("invoices/{id}.pdf")]
public async Task<IActionResult> Download(string id)
{
    var html = await _razor.RenderAsync("Invoices/Show", await _invoices.FindAsync(id));

    var http = _httpFactory.CreateClient("html4pdf");
    var response = await http.PostAsJsonAsync("https://api.html4pdf.com/generate-pdf", new
    {
        html,
        responseFormat = "binary"
    });

    response.EnsureSuccessStatusCode();

    return File(await response.Content.ReadAsByteArrayAsync(), "application/pdf", $"{id}.pdf");
}

What comes back

The default response is JSON with the document base64 encoded, the credits you have left, and how long the render took. Send responseFormat as binary instead and the body is the PDF itself, with a Content-Type of application/pdf.

200 OK
{
  "success": true,
  "pdf": "JVBERi0xLjQKJeLjz9MKMyAwIG9...",
  "creditsRemaining": 9,
  "metadata": { "size": 42813, "processingTime": 2841 }
}

A 402 means you are out of credits, a 401 means the key is wrong, and a 422 means the markup could not be rendered. Everything else is worth retrying once.

The usual C# and .NET libraries

Worth knowing before you add a dependency, because for a lot of documents one of these is the right answer and costs nothing.

LibraryWhat it isThe trade-off
QuestPDFA fluent C# layout API with a live preview.Genuinely pleasant for reports and invoices built in code. It does not take HTML, and the licence is free only below a revenue threshold.
IronPDFA commercial library that embeds a Chromium renderer.Accurate HTML rendering in-process. Per-developer licence cost and a large package.
PuppeteerSharpThe .NET port of Puppeteer, driving headless Chrome.Free and accurate, but it downloads and manages a browser on every machine that runs your app.
DinkToPdfA wrapper around the wkhtmltopdf native library.Lightweight, but it needs the native binary per platform and the engine is unmaintained.

C# and .NET: common questions

How do I convert HTML to PDF in C# for free?

PuppeteerSharp is free but installs a browser. An API call is free to integrate and needs nothing installed: the sample above is one HttpClient call, and new accounts get 10 credits to try it.

Does it work on Linux containers?

Yes. There is no native dependency, so the same code runs on Windows, Linux and Alpine images.

Can I use it in Azure Functions?

Yes, and it avoids the usual problem of fitting Chrome into a function app. Allow a few seconds for the call.

Should I create a new HttpClient each time?

No. Register it with IHttpClientFactory and inject it, as in the controller above, to avoid socket exhaustion.