In a blazor wasm, I want to create a pdf and make it downloadable. I tested pdfflow, but was only able to make it run in a console application. There are several commercial solutions (devexpress, syncfusion), but they are really expensive. Next, I stepped over the blog post Generate PDF files using an html template and Playwright, that seemed much promising to me. Unfortunately, it does not contain a complete example and I cannot make it run. Here is, what I tried: I added template.html to wwwroot folder.
template.html
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="invoice.css">
</head>
<body style="padding: 3rem">
<h1>Invoice</h1>
Awesome company<br />
</body>
</html>
I added a button, that invokes CreatePdf()
. I copied the code from the link above and modified it according to my needs (and because e. g. there is no method LoadOrder(orderId) provided).
Index.razor
@page "/"
@using Scriban
@using System.IO
@using Microsoft.Playwright
@inject HttpClient Http
<button class="btn btn-primary" @onclick="CreatePdf">pdf</button>
@code{
private async Task CreatePdf()
{
//var templateContent = File.ReadAllText("template.html");
var templateContent = await Http.GetStringAsync("template.html");
var template = Template.Parse(templateContent);
//var templateData = new { Invoice = LoadOrder(orderId) };
//var pageContent = template.Render(templateData);
var pageContent = "testString";
//var dataUrl = "data:text/html;base64," + Convert.ToBase64String(Encoding.UTF8.GetBytes(pageContent));
var dataUrl = "data:text/html;base64," + pageContent;
I added the following two lines, because "browser" was not declared (Source from playwright).
// Here, the app crashes.
using var playwright = await Playwright.CreateAsync();
var browser = await playwright.Webkit.LaunchAsync();
await using var context = await browser.NewContextAsync();
var page = await context.NewPageAsync();
await page.GotoAsync(dataUrl, new PageGotoOptions { WaitUntil = WaitUntilState.NetworkIdle });
var output = await page.PdfAsync(new PagePdfOptions
{
Format = "A4",
Landscape = false,
});
await File.WriteAllBytesAsync("output.pdf", output);
} }
How can i make above code run?
You cannot use Playwright from a browser (js / wasm). So, you have to use another solution. For instance, you can use jsPDF. This library is not perfect when converting html to pdf, but maybe it will be ok for your usage.
Add a few script references in index.html
to use jsPDF (you can also install them using npm if you prefer)
<script src="_framework/blazor.webassembly.js"></script>
<!-- jsPDF references -->
<script src="https://unpkg.com/jspdf@latest/dist/jspdf.umd.min.js"></script>
<script src="https://html2canvas.hertzen.com/dist/html2canvas.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dompurify@2.3.3/dist/purify.min.js"></script>
Create a file wwwroot/HtmlToPdf.js
with the following content:
export function generateAndDownloadPdf(htmlOrElement, filename) {
const doc = new jspdf.jsPDF({
orientation: 'p',
unit: 'pt',
format: 'a4'
});
return new Promise((resolve, reject) => {
doc.html(htmlOrElement, {
callback: doc => {
doc.save(filename);
resolve();
}
});
});
}
export function generatePdf(htmlOrElement) {
const doc = new jspdf.jsPDF();
return new Promise((resolve, reject) => {
doc.html(htmlOrElement, {
callback: doc => {
const output = doc.output("arraybuffer");
resolve(new Uint8Array(output));
}
});
});
}
Then, you can use the script from a Razor component:
@inject IJSRuntime JSRuntime
<button type="button" @onclick="DownloadPdf">Generate</button>
@code {
async Task DownloadPdf()
{
await using var module = await JSRuntime.InvokeAsync<IJSObjectReference>("import", "./HtmlToPdf.js");
// Generate and download the PDF
await module.InvokeVoidAsync("generateAndDownloadPdf", "<h1>sample</h1>", "sample.pdf");
// Generate the PDF and get its content as byte[] (need .NET 6 to support Uint8Array)
var bytes = await module.InvokeAsync<byte[]>("generatePdf", "<h1>sample</h1>");
}
}