DEV Community

IronSoftware
IronSoftware

Posted on

Convert Razor Views to PDF Headlessly in C#

Our background job needed to generate PDF invoices from Razor templates. No ASP.NET runtime available. Console app context. The built-in IronPdf.Extensions.Razor package required a running web server.

Headless Razor rendering solved this. Here's how to render Razor views to PDF without ASP.NET.

How Do I Render Razor Views Headlessly?

Use Razor.Templating.Core:

using IronPdf;
using Razor.Templating.Core;
// Install via NuGet: Install-Package IronPdf
// Install via NuGet: Install-Package Razor.Templating.Core

var model = new { CustomerName = "John Doe", Total = 1250.00 };

var html = await RazorTemplateEngine.RenderAsync("/Views/Invoice.cshtml", model);

var renderer = new [ChromePdfRenderer](https://ironpdf.com/blog/videos/how-to-render-html-string-to-pdf-in-csharp-ironpdf/)();
var pdf = renderer.RenderHtmlAsPdf(html);

pdf.SaveAs("invoice.pdf");
Enter fullscreen mode Exit fullscreen mode

Renders Razor templates to PDF without a web server running.

What Is Headless Rendering?

Headless rendering generates web content without a GUI. No browser window. No ASP.NET pipeline. Just template → HTML → PDF.

Use cases:

  • Console applications
  • Background workers
  • Azure Functions / AWS Lambda
  • Scheduled batch jobs
  • Microservices without web hosting

I use this for nightly report generation in a Windows Service.

Why Not Use IronPdf.Extensions.Razor?

IronPdf.Extensions.Razor requires ASP.NET Core running. It hooks into the MVC pipeline to resolve views. In console apps or background services, there's no pipeline.

Razor.Templating.Core renders views independently. No ASP.NET required.

How Do I Set Up a Razor View?

Create a .cshtml file in your project:

Views/Invoice.cshtml:

@model dynamic

<h1>Invoice for @Model.CustomerName</h1>
<p>Total: $@Model.Total</p>
Enter fullscreen mode Exit fullscreen mode

Set the file to "Copy to Output Directory" in Visual Studio.

Can I Use Layouts?

Yes, define a layout:

Views/Shared/_Layout.cshtml:

<!DOCTYPE html>
<html>
<head>
    <style>
        body { font-family: Arial; }
    </style>
</head>
<body>
    @RenderBody()
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Reference it in your view:

@{
    Layout = "/Views/Shared/_Layout.cshtml";
}

<h1>Content Here</h1>
Enter fullscreen mode Exit fullscreen mode

How Do I Pass Data to Views?

Use anonymous objects or strongly-typed models:

// Anonymous object
var model = new { Name = "Alice", Age = 30 };

// Strongly typed
public class InvoiceModel
{
    public string CustomerName { get; set; }
    public decimal Total { get; set; }
}

var model = new InvoiceModel { CustomerName = "Bob", Total = 500 };

var html = await RazorTemplateEngine.RenderAsync("/Views/Invoice.cshtml", model);
Enter fullscreen mode Exit fullscreen mode

Can I Use Partial Views?

Yes, with Html.Partial():

Views/Shared/_Header.cshtml:

<div class="header">
    <h2>Company Name</h2>
</div>
Enter fullscreen mode Exit fullscreen mode

Invoice.cshtml:

@Html.Partial("/Views/Shared/_Header.cshtml")

<h1>Invoice</h1>
Enter fullscreen mode Exit fullscreen mode

Partials render inline.

How Do I Handle CSS?

Embed styles inline or use <style> tags:

<style>
    .invoice { border: 1px solid #000; padding: 20px; }
    .total { font-weight: bold; }
</style>

<div class="invoice">
    <p class="total">Total: $1,250</p>
</div>
Enter fullscreen mode Exit fullscreen mode

Or reference external stylesheets (they must be accessible):

<link rel="stylesheet" href="https://example.com/styles.css">
Enter fullscreen mode Exit fullscreen mode

Can I Generate PDFs in Azure Functions?

Yes, perfect use case:

[FunctionName("GenerateInvoice")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
    ILogger log)
{
    var model = new { CustomerName = "John", Total = 100 };

    var html = await RazorTemplateEngine.RenderAsync("/Views/Invoice.cshtml", model);

    var renderer = new ChromePdfRenderer();
    var pdf = renderer.RenderHtmlAsPdf(html);

    return new FileContentResult(pdf.BinaryData, "application/pdf")
    {
        FileDownloadName = "invoice.pdf"
    };
}
Enter fullscreen mode Exit fullscreen mode

Serverless PDF generation from Razor templates.

How Do I Debug Rendering Issues?

Output the HTML first:

var html = await RazorTemplateEngine.RenderAsync("/Views/Invoice.cshtml", model);

File.WriteAllText("debug.html", html);

// Then convert to PDF
var pdf = renderer.RenderHtmlAsPdf(html);
Enter fullscreen mode Exit fullscreen mode

Open debug.html in a browser to verify template output.

Can I Cache Compiled Templates?

Razor.Templating.Core caches compiled views automatically. Subsequent renders are fast.

For high-volume scenarios, pre-render templates on startup:

await RazorTemplateEngine.RenderAsync("/Views/Invoice.cshtml", new { });
Enter fullscreen mode Exit fullscreen mode

This warms the template cache.

How Do I Handle Images?

Use absolute URLs or data URIs:

<!-- Absolute URL -->
<img src="https://example.com/logo.png" />

<!-- Data URI (Base64) -->
<img src="data:image/png;base64,iVBORw0KG..." />
Enter fullscreen mode Exit fullscreen mode

Or embed from file:

var logoBytes = File.ReadAllBytes("logo.png");
var logoBase64 = Convert.ToBase64String(logoBytes);

var model = new { LogoBase64 = logoBase64 };

var html = await RazorTemplateEngine.RenderAsync("/Views/Template.cshtml", model);
Enter fullscreen mode Exit fullscreen mode

In the view:

<img src="data:image/png;base64,@Model.LogoBase64" />
Enter fullscreen mode Exit fullscreen mode

What's the Performance?

First render: ~100-200ms (template compilation)
Cached renders: ~10-30ms (HTML generation)
PDF conversion: ~200-500ms (depends on complexity)

For batch processing, render templates in parallel:

var tasks = invoices.Select(async inv =>
{
    var html = await RazorTemplateEngine.RenderAsync("/Views/Invoice.cshtml", inv);
    var pdf = renderer.RenderHtmlAsPdf(html);
    return pdf;
});

var pdfs = await Task.WhenAll(tasks);
Enter fullscreen mode Exit fullscreen mode

Can I Use Dependency Injection?

Razor.Templating.Core doesn't natively support DI. For services, pass data through the model:

public class InvoiceModel
{
    public string CustomerName { get; set; }
    public string CompanyLogo { get; set; } // Retrieved from service
}

var logoService = new LogoService();
var model = new InvoiceModel
{
    CustomerName = "Alice",
    CompanyLogo = logoService.GetLogo()
};

var html = await RazorTemplateEngine.RenderAsync("/Views/Invoice.cshtml", model);
Enter fullscreen mode Exit fullscreen mode

How Do I Handle Errors?

Wrap rendering in try-catch:

try
{
    var html = await RazorTemplateEngine.RenderAsync("/Views/Invoice.cshtml", model);
    var pdf = renderer.RenderHtmlAsPdf(html);
}
catch (Exception ex)
{
    // Handle missing view, model errors, etc.
    Console.WriteLine($"Rendering failed: {ex.Message}");
}
Enter fullscreen mode Exit fullscreen mode

Common issues: missing view files, incorrect model types, syntax errors in .cshtml.

Can I Use This in a Windows Service?

Yes, ideal for background PDF generation:

public class PdfGenerationService : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var reports = GetPendingReports();

            foreach (var report in reports)
            {
                var html = await RazorTemplateEngine.RenderAsync("/Views/Report.cshtml", report);
                var pdf = new ChromePdfRenderer().RenderHtmlAsPdf(html);
                pdf.SaveAs($"reports/{report.Id}.pdf");
            }

            await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Generates PDFs on a schedule without ASP.NET.


Written by Jacob Mellor, CTO at Iron Software. Jacob created IronPDF and leads a team of 50+ engineers building .NET document processing libraries.

Top comments (0)