Micro Services with Azure Functions — PDF Download — DotNet Core

Allowing you to Generate PDF from your website whilst keeping it responsive

Tl;dr

I’m going to show you how you can move long running tasks such as PDF/report generation from your MVC website to their own Micro Service using Azure Functions.


Introduction

Many times I have seen the following; “A website that allows you to input data, and then after the data input completion, it allow you to download the report into something like a PDF or a word document”.

The problem: If there are enough users, or the time to generate the PDF/report is very long, the whole website can be taken down.

Solution 1: We move the task of generating PDFs to another thread using background processes (using Hangfire for example).

Solution 2: We move the task of generating PDFs to another (micro) service all together.

There are limitations with Solution 1 when it comes to scaling, you can’t scale the PDF generation side of the website without scaling the full website. 2nd issue, if there is an issue with the PDF generation code, the website may be taken down with it.

Which means Solution 2 is what we’re going for. We’re going to create a separate service for handling PDF generation using Azure Functions.


Let’s code

…but before we do that, let’s make sure you have these prerequisites.

  • Install Visual Studio 2017 with the latest “.NET Core cross-platform development” and “Azure development” workloads installed
  • Install Azure Storage Explorer
  • Install Azure Functions Tools
  • Knowledge of C# and .NET Core, ASP.NET (I will do a JavaScript version of this article shortly)

… now we can code :)

Let start a fresh project in Visual Studio (exciting right?)

I’m going to be creating an ASP.NET Core Web Application choosing the MVC project (Model-View-Controller).

Let’s create a new model NewUserDetailReportViewModel in our Models folder.

Let’s create a new Controller called UserReportingController where we will handle all of this reporting functionality as well as our user input.

We have the Index action that will just return our index.cshtml view and the Post action which will either return redirect you to the Downloads action (we haven’t created) if successfully, else it will just display the form for you again to submit.

Now let’s work on our views. In /Views/UserReporting let’s add our index.html file in order for us to create our report.

Our form is quiet simple. We allow the user to input the data from our NewUserDetailReportViewModel and we submit the result to our Post action.

Let’s try out what we have at the moment.

If we submit the form. We should get:

…and that is fine! It doesn’t look like it’s fine but trust me, it is. We just haven’t created our downloads action and view.

Publishing Events

What we are hoping to do is, as soon as we receive this request (command) to create a report, then we send out an event into a queue to request this report to be created.

Let’s create a shared project where we will store our event objects.

Note: Ensure that it is a .NET Standard Class Library. The reason for this because or MVC website is ASP.NET Core and our Micro Service will be in .NET Framework. .NET Standard Class Library means that it will be compatible with both.

Let’s create an Events folder and within it, our IEvent interface

Let’s implement our first event, CreateReportRequested

This event looks very much like our NewUserDetailReportViewModel with the addition of an Id and CreatedAt which are specific for our event.

Let’s jump back to our web project and add a new interface for publishing events.

We will create a new folder within our web project called services. Let’s add our IEventPublisher interface within it.

Our interface for publishing events is simple. Publish takes in a single parameter of type IEvent which we have declared within the Shared project (make sure to reference it).

Let’s implement this interface. In the same folder let’s add our EventPublisher service that implements this interface.

We have not yet implemented this function. Let’s add the following nuget packages to our web project. From your package manager console, execute the following (or add them through the package explorer):

Install-Package WindowsAzure.Storage
Install-Package Microsoft.WindowsAzure.ConfigurationManager

Add the following to your appsettings.Development.json file

Let’s go back and re-implement our EventPublisher.cs service

Let’s register our service in the Setup.cs just after the services.AddMvc() code:

var queueConnectionString = Configuration.GetConnectionString("StorageConnectionString");
services.AddSingleton<IEventPublisher, EventPublisher>(_ => new EventPublisher(queueConnectionString));

Now we make the following changes to the UserReportingController

What we have done here is taken in our interfaceIEventPublisher as a parameter in the constructor which will be resolved to our implementation later by Dependency Injection. In the Post method, we created a new CreateReportRequested event populating it with the data we got from our form. We then published our event.

If you run the following from the site and submit the form, we should get a published event to our Queue.

Now if you open the Storage Explorer and navigate to where your instance of local storage is, expand Queues and you should our eventqueue

Note: If you are getting any errors, make sure that the storage emulator is running. Also that your queue name does not contain any uppercase letters or special characters (with the exception of a dash -)

Now if you click on that you will get a list of queue items

This is the information I have submitted

Great! We just finished part 1!

Building our Micro Service

Congratulations on getting this far. Now we can focus on creating our first Micro Service.

What we are trying to achieve with this, is to listen to our queue and execute our function which will be converting our data into PDF files stored in Blobs and the list of files into tables which we will be displaying in our /Downloads page.

Now in our solution, let’s add our Azure function

Now we choose our function configuration. We want this to be triggered by an entry in a queue, so we choose Queue trigger and we type our queue name eventqueue within the queue path.

Once the function creation is complete. Let’s run it and see what we get.

Our function is running locally and it has picked up our item from the evenqueue and processed it! Exciting I know.

Let’s add our PDF document generator.

Note: For the sake of this tutorial, I’m going to us PdfSharp (the extension package HtmlRenderer.PdfSharp) to create and generate our PDF documents. I will not be styling or package comparison for creating PDFs as this tutorial is not about that.

Let’s install HtmlRenderer.PdfSharp

Install-Package HtmlRenderer.PdfSharp

Let’s create our blob storage table record. Let’s head back to our Shared project and create a new folder called Tables. Create a new class called UserReportRecord.

Now let’s head back to our function. Let’s rename it to something appropriate. I’m calling it GenerateUserReport . Do the same for the function name.

In our functions project, I’m going to create a static class that will generate our PDF and return us a byte array. I’m calling this class PDFGenerator .

The content variable holds the HTML data with our user report data embedded. We then use the HtmlRenderer.PdfSharp to generate our PDF and return it as a byte array.

Let’s go back to our function and put it all together.

In our Run method, we’re doing a few things here. First, let’s talk about the parameters.

We have our QueueTrigger which is how this function is going to be triggered. Next we have two output parameters. The first is an Azure Table and the second is an Azure Blob. We define their name and connection through the connection string.

We next ensure that if the container we specified, user-reports isn’t created, we create it and also set the public access permissions of the container to be blob only. This means any one is able to download our PDFs. Of course this isn’t for production purposes but only for the purpose of demonstrating this application to you.

Next we create a BlockBlob in our container and upload our PDF byte array onto it.

Next we get the AbsoluteUri of this file in our blob so that we can reference it from the Tables record.

Then we create our UserReportRecord which stores all of the information about this file.

If you run the above example, we can see a new record in our table and an item in our blob container.

If you copy over the Url and paste in into your browser, you should see the PDF file generated.

It’s not the sexiest PDF generated but we got it working! Congratulations, you have created your first Micro Service!

Putting it all together

I was tempted to end the article here. But once we submit the form, we are redirected to a downloads page that doesn’t exist. Let’s have this downloads page list all of the items that we have downloaded.

Back in our web project, let’s create a DownloadsController .

let’s create our DownloadItem view model in the Models folder.

This view model is very similar to our UserReportRecord but it implements TableEntity class which is needed to run queries against Azure tables.

and in the views, let’s create our index.html in the Downloads folder.

Now let’s go back to the DownloadsController and put it all together

In the constructor we create a new table client using our storage connection string.

In our index method, we refer to our table where we store our record in the function, downloads , then we create a query that will return us our list of DownloadItem

We then return the results to the view.

If you run the following, you should get something like this.

And that’s the end of this article. Here’s the link to the full source of this project.