Use Rotativa.AspNetCore to turn a Razor view into a PDF: install the NuGet package, deploy a platform-appropriate wkhtmltopdf executable, register Rotativa middleware, and return a ViewAsPdf result from your controller. The approach works with the framework versions documented by the project (.NET Core 3.1, .NET 5, and .NET 6 through .NET 8); verify compatibility before using a newer target.
What Rotativa.AspNetCore does
Rotativa.AspNetCore is a .NET wrapper around the wkhtmltopdf and wkhtmltoimage command-line tools. It renders a Razor view in a headless WebKit-based process and returns a PDF or image. Your application still owns the HTML, CSS, data, authentication, and file-delivery policy; Rotativa starts the renderer and translates its output into an ASP.NET Core action result.
The package listing currently shows version 1.4.0. Package metadata changes, so confirm the version and README on NuGet when you install it.
Prerequisites and project setup
1. Add the package
From the project directory, install the package with the .NET CLI:
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problems#1 Best Overall
dotnet add package Rotativa.AspNetCore
Alternatively, install Rotativa.AspNetCore from NuGet in Visual Studio. Restore the project and build once so dependency errors appear before deployment.
2. Put wkhtmltopdf in the deployment
Rotativa does not replace the renderer executable. The web-process account must be able to read and execute it. The documented default is a Rotativa directory in the application root. Place the operating-system-appropriate binary there:
- Windows:
wkhtmltopdf.exe - Linux and other Unix-like hosts:
wkhtmltopdf
Do not copy a Windows executable into a Linux container, and do not assume a developer-machine installation exists on the server. Preserve execute permissions in Linux images and verify that the service account can access the directory. If you use another folder, pass its relative path in the setup call shown below.
Configure Rotativa in the application pipeline
.NET 6, .NET 7, and .NET 8
In the minimal-hosting style used by these versions, register services and call UseRotativa() after building the application:
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Clear out junk files and repair common Windows errorsFree Scan →var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseRotativa();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.Run();
If the executable is in a custom relative folder, provide that path through the Rotativa setup overload documented by the package rather than relying on the default Rotativa folder.
.NET Core 3.1 and .NET 5
Those versions use the environment-aware middleware form:
Rank #2
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseRotativa(env);
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Use the corresponding configuration overload if your renderer directory is not the default. The folder path is relative to the application root as deployed, not necessarily your source-code directory.
Return a Razor view as a PDF
Use the action’s default view
Create a normal MVC action and return ViewAsPdf. Rotativa resolves the view associated with the action:
using Microsoft.AspNetCore.Mvc;
using Rotativa.AspNetCore;
public class InvoicesController : Controller
{
public IActionResult Invoice(int id)
{
var model = LoadInvoice(id); // Replace with your database/service call.
return new ViewAsPdf(model);
}
}
For an action named Invoice in InvoicesController, the conventional view location is Views/Invoices/Invoice.cshtml. The model is passed to that Razor view just as it would be for an HTML response.
Choose a named view and download behavior
Use the named-view constructor when the PDF template differs from the action name. Set ContentDisposition to Attachment and provide FileName to force a download. Without these settings, the browser normally displays the PDF inline.
public IActionResult DownloadInvoice(int id)
{
var model = LoadInvoice(id);
return new ViewAsPdf("InvoicePdf", model)
{
ContentDisposition = Rotativa.AspNetCore.Options.ContentDisposition.Attachment,
FileName = $"Invoice-{id}.pdf"
};
}
The exact namespace for ContentDisposition can vary with the package version; use the enum exposed by the installed Rotativa.AspNetCore package and let your IDE add the matching using directive.
Pass view data
When the view needs supplemental values that do not belong in the main model, use the result’s view-data support. A strongly typed view model is preferable for invoice totals, addresses, and line items; view data is useful for small presentation flags such as a logo variant or a report title. Keep database work in your service layer and pass a fully populated model to the action.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallBuild a PDF-ready Razor view
Use ordinary Razor markup, but design for a fixed page rather than an interactive browser. Give tables explicit widths, avoid very wide flex layouts, and keep print-only elements in the template. Relative URLs for CSS, images, and fonts can fail when the renderer runs outside a normal browser request. Prefer absolute, reachable URLs or embed required assets, and test the deployed environment rather than only localhost.
@model InvoiceViewModel
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<title>Invoice @Model.Number</title>
<style>
body { font-family: Arial, sans-serif; font-size: 12px; }
table { width: 100%; border-collapse: collapse; }
th, td { border-bottom: 1px solid #ddd; padding: 6px; text-align: left; }
.total { text-align: right; font-weight: 700; }
</style>
</head>
<body>
<h1>Invoice @Model.Number</h1>
<p>Issued @Model.IssueDate.ToString("yyyy-MM-dd")</p>
<table>
<thead><tr><th>Description</th><th>Qty</th><th>Amount</th></tr></thead>
<tbody>
@foreach (var line in Model.Lines)
{
<tr><td>@line.Description</td><td>@line.Quantity</td><td>@line.Amount.ToString("C")</td></tr>
}
</tbody>
</table>
<p class="total">Total: @Model.Total.ToString("C")</p>
</body>
</html>
Customize conversion and save the bytes
The result accepts custom wkhtmltopdf switches. Use them for page size, margins, orientation, headers, footers, or other options supported by the renderer version you deploy. Keep switches close to the action that needs them and test each one against your installed binary, because renderer builds can differ.
When another service must archive the document, call BuildFile to obtain the generated PDF bytes, then write them through controlled storage code. Protect the destination with authorization, encryption and retention rules; do not place invoices or other sensitive output in a publicly served directory by default.
public async Task ArchiveInvoice(int id)
{
var model = LoadInvoice(id);
var pdf = new ViewAsPdf("InvoicePdf", model);
byte[] bytes = await pdf.BuildFile(ControllerContext);
// Store bytes in private object storage or a protected database.
await _archive.StoreAsync($"invoices/{id}.pdf", bytes);
return Ok(new { id });
}
Some package versions expose a synchronous BuildFile signature. Follow the signature shown by IntelliSense for the version you installed and pass the current controller context.
Recommended Free Tools
Security: treat HTML as code
The wkhtmltopdf project explicitly warns: “Do not use wkhtmltopdf with any untrusted HTML – be sure to sanitize any user-supplied HTML/JS, otherwise it can lead to complete takeover of the server it is running on!” Never concatenate user-provided HTML into a view and render it directly. Render a server-owned Razor template with encoded values, validate URLs and file paths, and isolate the conversion process where your threat model requires it.
- Allow-list fields and formatting rather than accepting arbitrary scripts.
- Do not let users choose local file paths, executable switches, or unrestricted remote URLs.
- Run the web process with the minimum filesystem and network permissions needed.
- Keep secrets out of view source and generated files; redact logs that may contain document data.
- Review outbound network access because external images, CSS, or JavaScript can leak information or stall conversion.
Deployment checklist
- Publish the application for the target operating system and architecture.
- Copy the matching
wkhtmltopdfexecutable and any required supporting files into the configured folder. - Verify execute permission and ownership for the account running Kestrel, IIS, systemd, or the container entrypoint.
- Confirm the application root at runtime; relative paths that work locally can point elsewhere after publishing.
- Call a health-check or staging PDF endpoint and inspect logs for renderer exit codes and missing assets.
- Load-test realistic documents, then set request timeouts and queue limits so large conversions cannot exhaust web workers.
The official wkhtmltopdf site lists 0.12.6 as its stable series, released June 11, 2020. That is an aging dependency: check the current project status, operating-system support, and your organization’s security policy before standardizing it.
Rank #4
Common failures and fixes
“wkhtmltopdf executable not found”
The binary is absent, in the wrong relative folder, or the configured path is incorrect. Confirm the published application root, filename, custom-folder setting, and service-account permissions.
Permission denied or process will not start
On Linux, restore the executable bit and ensure the service account can traverse every parent directory. On Windows, verify that application-control policy has not blocked the executable.
Blank pages or missing images and CSS
The renderer cannot resolve relative or protected resources. Use absolute URLs reachable from the server, embed critical assets, and ensure authentication does not require an interactive login. Check TLS certificates and outbound firewall rules.
View not found or model errors
Use the conventional Views/Controller/Action.cshtml path, or pass the exact named view. Confirm that the model type and property names match the Razor template.
PDF is cut off or laid out incorrectly
Set explicit page dimensions, margins, and widths with renderer switches; simplify wide CSS layouts; and add page-break rules around sections that must stay together. Test with long text and multiple pages, not just a one-line sample.
Requests time out
Large pages, slow external assets, and JavaScript can keep wkhtmltopdf running. Remove unnecessary resources, host assets locally, apply a bounded wait strategy, and move expensive jobs to a background queue instead of tying up a request indefinitely.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Rotativa versus a hosted PDF API
Running Rotativa gives you control over deployment, network boundaries, templates, and renderer switches, but you must package, patch, monitor, and secure the executable. A hosted service such as Rotativa.io can avoid installing PDF tooling on your application server, at the cost of a service dependency and transmitting conversion requests outside your process. Review each provider’s current terms, data handling, region availability, and pricing before sending confidential documents; the available project instructions do not establish current hosted-service prices.
Or skip the browser setup
If your requirement is simply “give me a clean screenshot or PDF of this URL,” ScreenshotNeo is a direct API option. It accepts cookie and consent banners before capture, removes more than 60 known consent platforms plus newsletter popups and chat widgets, and bills only clean shots. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed; response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.
See the complete parameter reference in the ScreenshotNeo documentation. A one-request example:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python:
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js:
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Every plan includes features such as full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF paper and margin controls, custom CSS and JavaScript, click-and-wait actions, blocked resources, custom headers and cookies, timezone and geolocation, transparent backgrounds, resizing, selectable cache TTLs, signed links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, usage reporting, and an OpenAPI specification. The parameter names used by other screenshot APIs are accepted to ease migration.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →The Free plan includes 1,000 shots per month with no card. Paid plans start at $5 for 3,000 shots; annual billing gives two months free. Create a free ScreenshotNeo account to try it without a card.
Frequently Asked Questions
Can Rotativa.AspNetCore render an image instead of a PDF?
Yes. The package wraps both wkhtmltopdf and wkhtmltoimage; use the image result types and options documented by the installed package version.
Does Rotativa.AspNetCore support .NET 9 or later?
The cited project documentation establishes support through .NET 8 only. Check the current README and test your target framework before upgrading.
Where should generated PDFs be stored?
Use private, access-controlled storage with an explicit retention policy. Avoid writing sensitive documents to a publicly served web folder.
The Bottom Line
For a self-hosted Razor-to-PDF workflow, install Rotativa.AspNetCore, deploy a matching wkhtmltopdf binary, register the correct middleware for your framework version, and return a configured ViewAsPdf result. Treat every input as untrusted and verify the aging renderer dependency before production use.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

