There are lots of occasions where you want to create a PDF document, e.g. for a report or an invoice. While there are plenty of options for PDF handling in .NET it turns out none of them actually work with Azure Functions running true serverless in a consumption plan. This is because all libraries I found do rely on GDI+ which is not available in the consumption plan. However most of these solutions work with Azure Functions running in App Plan Basic or higher.
So what is the alternative for running truly serverless?
One of the beauties of serverless systems to me is that you can mix and match runtimes and programming languages and really leverage the advantages of each to compose a distributed system. So while I prefer to build most Function apps using C# and .NET in this case nodejs with Javascript is a better alternative. Since the PDF creation will just be a small component of the overall solution I'm trying to build I can still use C# for all of my other functions.
For Javascript there is a really good library that works both in the browser and the server using nodejs: https://github.com/bpampuch/pdfmake
This library works well with Azure Functions running in the consumption plan and allows easy document composing using a template.
So let's create a new Azure Functions app. An intro into creating Azure Functions in general can be found here: https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-function-vs-code?pivots=programming-language-javascript
After that we need to install the pdfmake library
npm install pdfmake — save
I wanted the actual PDF template to be hosted in Azure blob storage so I can easily update it. Therefore we need to add an input binding to Azure storage to retrieve the template in our function.json:
{ "bindings": [ { "authLevel": "function", "type": "httpTrigger", "direction": "in", "name": "req", "methods": [ "post" ], "route": "pdf" }, { "type": "http", "direction": "out", "name": "res" }, { "name": "pdfTemplate", "type": "blob", "path": "templates/template.json", "connection": "Storage", "direction": "in" } ] }
Here is a sample template, I'm using to create a voucher. Find the full reference for creating pdf with pdfmake here: https://pdfmake.github.io/docs/
{ "pageSize": "A4", "pageOrientation": "portrait", "content": [ { "text": "Gutschein", "fontSize": 27, "color": "#707070", "margin": [ 0,16 ], "style": "page" }, { "text": "einlösbar bei", "fontSize": 15, "color": "#707070", "margin": [ 0,32,0,4 ], "style": "page" }, { "text": "{vendor_name}", "fontSize": 15, "bold": true, "style": "page" }, { "text": "{vendor_street}", "fontSize": 15, "style": "page" }, { "text": "{vendor_zip_city}", "fontSize": 15, "style": "page" } ] }
So finally let's put this all together in our function's index.js
const pdfMakePrinter = require('pdfmake'); module.exports = async function (context, req) { const voucher = context.req.body; let templateStr = JSON.stringify(context.bindings.pdfTemplate); templateStr = templateStr.replace('{vendor_name}', voucher.vendorName); templateStr = templateStr.replace('{vendor_street}', voucher.vendorStreet); templateStr = templateStr.replace('{vendor_zip_city}', voucher.vendorZipCity); const pdf = await generatePDF(JSON.parse(templateStr)); context.res = { body: pdf }; }; async function generatePDF(docDefinition) { const fontDescriptors = { Roboto: { normal: 'fonts/Roboto-Regular.ttf', bold: 'fonts/Roboto-Medium.ttf', italics: 'fonts/Roboto-Italic.ttf', bolditalics: 'fonts/Roboto-MediumItalic.ttf' } }; const printer = new pdfMakePrinter(fontDescriptors); const doc = printer.createPdfKitDocument(docDefinition); return new Promise(resolve => { const chunks = []; doc.end(); doc.on('data', (chunk) => { chunks.push(chunk); }); doc.on('end', () => { resolve(Buffer.concat(chunks)); }); }); };
Voucher is a json object we are receiving in the body of the incoming post request. The pdf template is providing via an input binding. After replacing some placeholders with real values in the template we can create the pdf file using the method generatePDF and return it as http response.
And that's it! Pretty simple eh? Now I can just call this function from other functions or from where ever.
----
I very much agree with the core premise and benefits of serverless capabilities with Azure Functions — which give us the awesome ability to borrow some very valuable (niche) benefits of other languages in a micro-service form.I also appreciate the creative thinking here in the use of NodeJS (and the oodles of non packages); but I personally find that NodeJS PDF solutions are often lacking maturity and robust features really needed…This article title emphasizes Pdf creation more than Azure Functions runtime and is lacking some significant details around the creation of a Pdf with the chosen (recommended) technologies…Firstly, we can assume that in this simple case, raw string replacements are adequate for dynamic content but it seems fragile and unlikely to be a good idea for many use cases whereby a real templating technology should be used (maybe mustache, Vue.js, etc?).Second, there is no presentation of the final output? Does it work? If so what does the rendered Pdf look like?Third it's completely unclear how the formatting of the output Pdf is even controlled? We can't tell if this provides a true separation between data model and rendering template. In addition, we can't tell if the rendering capabilities are robust enough to cover most reporting and rendering requirements!For anyone looking for a similar and robust approach for serverless, free, template based PDF rendering you can run ApacheFOP purely serverless in Azure— a very mature and long standing solution for this.Here's a ready to roll project: