Creating a layered design in an application is a fundamental element of modern software architecture. The goal is to promote the Separation of Concerns (SoC) design principle. Separation of Concerns The ideas behind SoC date back to Dijkstra's 1974 paper "On the role of scientific thought" . In computer science, separation of concerns (sometimes abbreviated as SoC) is a design principle for separating a computer program into distinct sections. Each section addresses a separate concern, a set of information that affects the code of a computer program. A concern can be as general as "the details of the hardware for an application", or as specific as "the name of which class to instantiate". A program that embodies SoC well is called a modular program. Modularity, and hence separation of concerns, is achieved by encapsulating information inside a section of code that has a well-defined interface. - Wikipedia SoC is a broad design principal th...
Generating reports can be a challenge in any application. Many applications have a requirement to generate read-only archival reports. There are numerous reporting frameworks that can help with this, including Telerik Reporting, SQL Server Reporting Services, Microsoft Power BI, and even Crystal Reports. One DIY alternative is to use html2pdf.js. html2pdf.js is a very flexible Javascript library; web pages can be exported as PDFs directly in the browser for download. Combined with some CSS, this is a powerful tool.
Below is an example of how to use html2pdf.js inside a Blazor application to export a webpage to a PDF. This example builds on several previous posts, but the most important is Hosting Javascript Libraries in ASP.NET Applications. If you aren't familiar with that post, please review it first.
npm install gulp
npm install del
npm install html2pdf
Let's create a Javascript function for our Blazor application to call. This function should be in a .js file somewhere in the wwwroot folder of our application. We'll pass in the ID of an HTML element to export and the filename for the PDF.
ExportToPDF: (id, filename) => {
var element = document.getElementById(id);
if (element) {
// temporarily add the .print class to the root element of the documentdocument.documentElement.classList.add("print");
var options = {
image: { type: 'jpeg', quality: 0.95 },
filename: filename,
html2canvas: { scale: 2 },
jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
};
html2pdf()
.set(options)
.from(element)
.toPdf()
.save()
.then(function () {
// remove the .print class from the root element of the document after export occursdocument.documentElement.classList.remove("print");
});
}
}
One thing to note about the code above is the styling change required to make the exported PDF display in a print-friendly style. Early in the Javascript function, we add the print class to the root documentElement. This will apply some "print" specific classes to the entire web page. Note that the application will flash/change briefly as the PDF is generated due to the style change. If you want the PDF to exactly match the web page, there is no need to add/remove the print class.
Here are some examples of possible .css classes that can improve the formatting of the web page when exporting to PDF. When generating these classes, consider how you would handle the @media print styling for your application, as it may be very similar (or identical).
Finally, let's look at a Razor Component that exports part of a web page to a PDF.
@page"/"@usingExportToPDFBlazor.Models@rendermodeInteractiveServer
<PageTitle>Widgets</PageTitle>
<divid="SectionToPrint"><divclass="hideonprint"><h1>Widgets</h1><divclass="d-flex flex-row justify-content-between align-items-center mb-2"><div>This component demonstrates exporting data to PDF.</div><div><buttonclass="btn btn-secondary"type="button" @onclick="OnExportClick">Export</button></div></div></div><h1class="showonprint text-center">Widgets Report</h1><tableclass="table"><thead><tr><thscope="col">Id</th><thscope="col">Name</th><thscope="col">Description</th><thscope="col"class="text-end">Cost</th></tr></thead><tbody>
@foreach (var widget in Widgets)
{
<tr><td>@widget.Id</td><td>@widget.Name</td><td>@widget.Description</td><tdclass="text-end">$@widget.Price.ToString("N2")</td></tr>
}
</tbody></table></div>@code {
privateList<Widget> Widgets { get; set; } = newList<Widget>();
[Inject]
privateIJSRuntime _jsRuntime { get; set; }
protectedoverridevoidOnInitialized()
{
Widgets = GetWidgets();
}
privateasyncTaskOnExportClick()
{
// export the WeatherDataToPrint div to a PDF titled WeatherData.pdfawait _jsRuntime.InvokeVoidAsync("ExportToPDFBlazor.ExportToPDF", newobject[] { "SectionToPrint", "WidgetReport.pdf" });
}
publicList<Widget> GetWidgets()
{
returnnewList<Widget>()
{
newWidget()
{
Id = 1,
Name = "Widget1",
Description = "Widget 1",
Price = 1m
},
newWidget()
{
Id = 2,
Name = "Widget2",
Description = "Widget 2",
Price = 10m
},
newWidget()
{
Id = 3,
Name = "Widget3",
Description = "Widget 3",
Price = 100m
}
};
}
}
Notice the use of the showonprint and hideonprint classes in the markup to show and hide elements depending on whether the page is being displayed normally or being exported to PDF. Also worth noting is the usage of IJSRuntime to call the Javascript function to trigger the export to PDF (the details of Blazor Javascript calls are out of scope for this post).
There are a lot of details in this example, so grab the code from GitHub if you need more clarification.
This example was created using Visual Studio 2022 running a Blazor Web App project in .NET 8.