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.
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.
…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
… 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
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.
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
Let’s implement our first event,
This event looks very much like our
NewUserDetailReportViewModel with the addition of an
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):
Add the following to your
Let’s go back and re-implement our
Let’s register our service in the
Setup.cs just after the
var queueConnectionString = Configuration.GetConnectionString("StorageConnectionString");services.AddSingleton<IEventPublisher, EventPublisher>(_ => new EventPublisher(queueConnectionString));
Now we make the following changes to the
What we have done here is taken in our interface
IEventPublisher 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
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
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
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
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
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.
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
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
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.