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");
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>
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>
Reference it in your view:
@{
Layout = "/Views/Shared/_Layout.cshtml";
}
<h1>Content Here</h1>
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);
Can I Use Partial Views?
Yes, with Html.Partial():
Views/Shared/_Header.cshtml:
<div class="header">
<h2>Company Name</h2>
</div>
Invoice.cshtml:
@Html.Partial("/Views/Shared/_Header.cshtml")
<h1>Invoice</h1>
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>
Or reference external stylesheets (they must be accessible):
<link rel="stylesheet" href="https://example.com/styles.css">
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"
};
}
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);
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 { });
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..." />
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);
In the view:
<img src="data:image/png;base64,@Model.LogoBase64" />
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);
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);
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}");
}
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);
}
}
}
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)