Custom reporting tool from HTML to PDF: flexible, straightforward & regulatory compliant

In the business world, reporting is a necessary evil: it is accepted/must be done in order to achieve a better outcome (e.g., organisational oversight, standardisation, discipline). Then, as if not enough pressure, regulatory bodies will keep piling-up.

Take for example EU’s Payment Services Directive 2015/2366 (check the official page, its Wikipedia page, and EU’s official rolling plan): due to the technological changes in payments, “it aims at better protecting consumers when they pay online, promoting the development and use of innovative online and mobile payments, and making cross-border European payment services safer”. In plain English, this ends-up in developing and implementing a “single, open and secure European technical standard for QR-codes [that] would support the uptake and interoperability of instant payments”. The final deadline to comply: 14th September 2019 (unofficially extended “because pandemics”).

For virtually all B2C companies, this means they have to include standardized security marks (i.e., QR codes) on all the bills. Furthermore, this means the payment software needs to be updated, in order to accommodate the regulatory requests and to create leaner reporting that supports internal needs. But then, some (very) old software can’t easily be upgraded, and it’s easy to start afresh.

Working with a retail client, we have developed a custom reporting tool that is:

  • regulatory compliant: it includes all the security items required by the Directive (EU) 2015/2366
  • programmatic: it defines the structure of all the needed reports, creates their templates, then fills them automatically anytime a report is needed.

Why build your own reports instead of using ready-made reports generators

First of all, it takes time to learn how to use a report generator and -most important- how to communicate with its APIs. This results in high installation effort (and possibly tedious maintenance along the way).

Then, the costs of a really good, high-performing report generator are not far away from the cost of a custom reporting tool.

By building your own, you can focus on your specific requirements without the overhead of a “satisfy all” ready-made solution. This results in higher flexibility (of design, data sources, etc.) and unmatched performance.

Our reporting solution

For this specific customer, we had already built a cashbox module as the first part of a bigger ERP system. The reporting capabilities were a natural extension, in the form of an additional microservice that could be used by other modules along the way.

For each needed report, we created a three-step process:

  • creating a static HTML template
  • inject dynamic data
  • converting the HTML to .pdf

We started by defining the layouts with HTML, as it is the most popular language that describes layouts. For each type of report, we needed three HTML files: a body, a header and a footer. (This is because client’s different reports all needed different headers / body / footers). We could easily create and modify report mock-ups in HTML alone to get useful feedback of the client in the earlier stage.

Once we had the layout defined, the next step was to inject the dynamic data. We used a heterogeneous data source (SQL tables, JSON files and in-memory data). When the data was available, we used Razor as a template engine to inject the data into HTML.

To integrate Razor into our software, we used the NuGet package and we installed RazorEngine 3.10.0. You can see an example below, of how Razor is used inside HTML. In this example, the code creates a table of articles with three columns, the position of the article, the article’s code and its price.

<table>
    @foreach(var article in @Model.Items){
    <tr>
        <td>
            <span> @article.Position</span>
            <span> @article.Code</span>
        </td>
        <td>
            <span> @article.Price €</span>
        </td>
    </tr>
    }
</table>

@Model represents the root of the model, where Razor expects to find the data. In the code, we tell Razor to use the customized ReportModel, C# object as the @Model.

We call the Razor method RunCompile with the following parameters:

  • HTML template’s content as string
  • Template identifier to differentiate one type of template from another
  • Type of the data model
  • An instance of the data model

This method returns the HTML string with injected data as the final HTML result.

    var result = Engine.Razor.RunCompile(File.ReadAllText(htmlPath), templateIdentifier,typeof(ReportModel), reportModel);
ReportModel class looks like below:
public class ReportModel
{
 
    public List<Item> Items {get; set;}
}
 
public class Item
{
    public int Position {get; set;}
    public double Price {get; set;}
    public string Code {get; set;}
}

After these steps, we end up with a complete HTML and the final step takes care of the conversion of the HTML into a pdf report.

Converting HTML to pdf could look like a basic operation and there are a lot of free tools on the internet to do just that. But a report is not just a pdf file, is more than that. It comes with features like adding header, footer, pagination and printing support.

We started to evaluate commercial HTML to pdf libraries that could support our needs for professional reports. We tried IronPDF, Spire.PDF and EVO PDF, all of them with comparable pricing tiers (around 500 €/instance).

Finally, we chose EVO PDF because (A) the generated .pdf file looked how we wanted, with the integrated header and footer (also when printing), and (B) the documentation was up to date and easy to use (very important). As a bonus: you don’t need to buy EVO PDF in the development step, as they offer a fully functional free version that only inserts a red watermark in the report.

How to use EVO PDF

First, we had to add EVO PDF as a NuGet package into our project. In fact, EVO PDF provides installation options for multiple platforms, such as .NET, .NET CORE, Java and more.

Once available, using it is straightforward and well documented.

Here is a sample code from our implementation:

    var htmlToPdfConverter = new HtmlToPdfConverter(); 
// next 5 settings are to optimize the size of the pdf file
    htmlToPdfConverter.ConversionDelay = 0;
    htmlToPdfConverter.PdfDocumentOptions.EmbedFonts = true;
    htmlToPdfConverter.PdfDocumentOptions.CompressCrossReference = true;
    htmlToPdfConverter.PdfDocumentOptions.PdfCompressionLevel = PdfCompressionLevel.Best;
    htmlToPdfConverter.PdfDocumentOptions.ImagesScalingEnabled = true;
 
// Razor method is the same that we explained above
// injecting dynamic data in header 
    var headerPathAfterRazor = _razorService.Razor(headerPath, templatesDirectoryPath, reportModel); 
// injecting dynamic data in footer
    var footerPathAfterRazor = _razorService.Razor(footerPath,
 
    var headerHtml = new HtmlToPdfElement(headerPathAfterRazor);
    var footerHtml = new HtmlToPdfElement(footerPathAfterRazor);
    htmlToPdfConverter.PdfDocumentOptions.ShowHeader = true;
    htmlToPdfConverter.PdfDocumentOptions.ShowFooter = true;
 
// setting the desired height for header / footer
    htmlToPdfConverter.PdfHeaderOptions.HeaderHeight = 100;  
    htmlToPdfConverter.PdfFooterOptions.FooterHeight = 110;
    headerHtml.FitHeight = true;
    footerHtml.FitHeight = true;
// adding the header and the footer to the pdf report
    htmlToPdfConverter.PdfHeaderOptions.AddElement(headerHtml);  
    htmlToPdfConverter.PdfHeaderOptions.AddElement(footerHtml);  
 
// injecting dynamic data in body 
    var bodyPathAfterRazor = _razorService.RazorbodyPath,                templatesDirectoryPath, reportModel);      
// converting html to pdf
  var pdfAsBytes = htmlToPdfConverter.ConvertUrl(bodyPathAfterRazor);
 
Once we generated the pdf document, for printing capabilities the default windows command was reliable and just enough for our needs.
print /d: C:\inetpub\reportsservice\myReport.pdf 

Encountered problems

Reports can go big quickly, even for simple pages. The size can reach hundreds of kilobytes, especially when large pictures or customized fonts are used.

Then, Razor can take some time to compile a template for the first time. That’s why it’s better to do the template compiling in advance, and not during generating the report itself. At this step, the dummy report model will be empty as it has no role here. Once compiled, the next compilation of the same template will be instant.

    var result = Engine.Razor.RunCompile(File.ReadAllText(htmlPath), templateIdentifier,typeof(ReportModel), dummyReportModel);

Conclusions

When approached as a customised solution, the reporting tools stop being the necessary evil:

  • they adapt to clients’ specific context and needs
  • are relatively straightforward to develop and implement
  • can be very flexible to any changes in regulations and/or client specifics.


Should you need more info, or insights into developing your own reporting tools, please get in touch.

29 years in business | 2700 software projects | 760 clients | 24 countries

We turn ideas into software. What is yours?

Get in touch

15 + 8 =