So for the last couple of weeks I’ve been working on an Azure function app in C#, that will take a payload of HTML & a file name, and then generate and save a PDF file in Blob storage.

I worked based off this tutorial (https://itnext.io/to-azure-functions-wkhtmltopdf-convert-html-to-pdf-9dc69bcd843b) with some additions that I needed to make which I’ll try and explain below.

I used VSCode, but you will also need an Azure Portal account, a Linix Basic (B1) App Service Plan, and an Azure Storage Account.

In the Storage Account, I created a Blob Container called “pdf” which is where all my PDF files generated are placed.

The full repository is found here

You also need to install the Azure Functions Core Tools.

Following on from the tutorial (assuming you have built and followed the tutorial above) which tells you how to install and add DinkToPdf to your project, and how to get these added to your Azure Portal via SSH, I had a couple of issues:

  • My function app ran once, then every subsequent request returned 503’s. This would happen when debugging locally using Postman, or in the Azure Portal. Not good.
  • After the first run locally I noticed I would get this “Qt: Could not initialize OLE (error 80010106)” error thrown in the console.
  • I detailed some of the errors I had here.

So it turns out I needed to use earlier versions in my .csproj file:

<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.1.13" />
<PackageReference Include="Microsoft.Extensions.Http" Version="3.1.13" />

These couldn’t be later than 3.1.13.

I added a Startup class, and had to use Dependency Injection to setup the SynchronizedConverter to be added here, instead initialized as per the tutorial (I’ve left the comments in the code as a reference).

public class Startup : FunctionsStartup
    {
        public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)
        {
            FunctionsHostBuilderContext context = builder.GetContext();
            builder.ConfigurationBuilder.AddEnvironmentVariables();
        }

        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton(typeof(IPdfConverter), new SynchronizedConverter(new PdfTools()));
        }
    }

And above the namespace:

[assembly: FunctionsStartup(typeof(pdfCreation.Startup))]

I also added the STAThread to the function ‘Html2Pdf’:

[STAThread]

And then in the Html2Pdf class, I added:

private readonly IPdfConverter pdfConverter;
public Html2Pdf(IPdfConverter pdfConverter)
{
    this.pdfConverter = pdfConverter;
}

Also, don’t forget to add your connection string under configuration / application settings within the function app in the Azure Portal.

In order to use the Connection String both locally and in production, without having to worry about fluffing around and mistakenly committing it to source control, I did the below:

private string ConnectionString(ILogger log, ExecutionContext context)
        {
            var config = new ConfigurationBuilder()
                .SetBasePath(context.FunctionAppDirectory)
                .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                .AddEnvironmentVariables()
                .Build();

            var connString = config.GetConnectionString("ConnectionString");            

            if(connString == null){log.LogInformation("Connection String is null");}

            return connString;
        }

Just make sure your connection string in the Azure Portal is called “ConnectionString” and copy this from the AzureWebJobsStorage under the Application settings, and it should be golden.

Locally, I added a “local.settings.json” file to the root directory of the project (which you’ll see is excluded via .gitignore). The format for this is below:

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true;",
    "AzureWebJobsDashboard": "UseDevelopmentStorage=true;",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "FUNCTIONS_EXTENSION_VERSION": "~3",
    "APPINSIGHTS_INSTRUMENTATIONKEY": "XXXXXX",
    "APPLICATIONINSIGHTS_CONNECTION_STRING": "InstrumentationKey=XXXXXX;IngestionEndpoint=https://YOUR_LOCATION...ETC",
    "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=YOUR_STORAGE_ACCOUNT;AccountKey=YOUR_STORAGE_ACCOUNT_KEY;EndpointSuffix=core.windows.net"
  },
  "ConnectionStrings": {
    "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=YOUR_STORAGE_ACCOUNT;AccountKey=YOUR_STORAGE_ACCOUNT_KEY;EndpointSuffix=core.windows.net"
  }
}

When running locally, the connection string will be read from this file, otherwise it will be read from the function app’s application settings.

Hopefully these additions help someone! If you need any clarification, just hit me up.