Promoting SoC Through Application Layering

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...

Exporting to PDF in a Blazor Web App

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.

Web Application Screenshot
Screenshot of Exported PDF

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.

Visual Studio Project Screenshot

For this example, we'll need to install the html2pdf.js library using NPM. We also need a few other libraries (see Hosting Javascript Libraries in ASP.NET Applications for details):

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 document
		document.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 occurs
				document.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).

.showonprint {
    display: none;
}

.print body {
    margin: 0 !important;
    background-color: white !important;
    color: black !important;
}

.print .showonprint {
    display: block !important;
}

.print #header, .print footer, .print .sidebar, .print .top-row, .print .hideonprint {
    display: none !important;
}

.print .element::-webkit-scrollbar {
    display: none !important;
}

Now we can update the App.razor to reference the necessary Javascript files, here is the App.razor file:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="/" />
    <link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
    <link rel="stylesheet" href="app.min.css" />
    <link rel="stylesheet" href="ExportToPDFBlazor.styles.css" />
    <link rel="icon" type="image/png" href="favicon.png" />
    <HeadOutlet />
</head>

<body>
    <Routes />
    <script src="_framework/blazor.web.js"></script>
    <script src="/lib/html2pdf.js/html2pdf.bundle.min.js"></script>
    <script src="/js/ExportToPDFBlazor.min.js"></script>
</body>

</html>

Finally, let's look at a Razor Component that exports part of a web page to a PDF.

@page "/"
@using ExportToPDFBlazor.Models
@rendermode InteractiveServer

<PageTitle>Widgets</PageTitle>

<div id="SectionToPrint">

    <div class="hideonprint">
        <h1>Widgets</h1>

        <div class="d-flex flex-row justify-content-between align-items-center mb-2">
            <div>This component demonstrates exporting data to PDF.</div>
            <div><button class="btn btn-secondary" type="button" @onclick="OnExportClick">Export</button></div>
        </div>
    </div>

    <h1 class="showonprint text-center">Widgets Report</h1>

    <table class="table">
        <thead>
            <tr>
                <th scope="col">Id</th>
                <th scope="col">Name</th>
                <th scope="col">Description</th>
                <th scope="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>
                    <td class="text-end">$@widget.Price.ToString("N2")</td>
                </tr>
            }
        </tbody>
    </table>
</div>

@code {
    private List<Widget> Widgets { get; set; } = new List<Widget>();

    [Inject]
    private IJSRuntime _jsRuntime { get; set; }

    protected override void OnInitialized()
    {
        Widgets = GetWidgets();
    }

    private async Task OnExportClick()
    {
        // export the WeatherDataToPrint div to a PDF titled WeatherData.pdf
        await _jsRuntime.InvokeVoidAsync("ExportToPDFBlazor.ExportToPDF", new object[] { "SectionToPrint", "WidgetReport.pdf" });
    }

    public List<Widget> GetWidgets()
    {
        return new List<Widget>()
        {
            new Widget()
            {
                Id = 1,
                Name = "Widget1",
                Description = "Widget 1",
                Price = 1m
            },
            new Widget()
            {
                Id = 2,
                Name = "Widget2",
                Description = "Widget 2",
                Price = 10m
            },
            new Widget()
            {
                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.

Source code: https://github.com/jharrell-bits/ExportToPDFBlazor