Generating PDF files is a common use case. Sometimes, there will be a need to send dynamically generated pdf attachments(like bank statements, invoices, and more) over email or give PDF export/download option to the users in the web portal or in the mobile app via API call.
PDF can be designed & generated using popular libraries like iTextSharp. But they are quite complicated and time-consuming to achieve the expected design. But if you are a web developer already, you will find it easy and quick to design with HTML & CSS and later convert it to PDF.
Let’s move on to see how to generate PDF from HTML in ASP.NET Core running on .NET 5.
There are two parts to this.
- Generating dynamic HTML string — using Razor.Templating.Core. (With the power of Razor language which is already being used in MVC views)
- Converting HTML to PDF — using jsreport
Generating HTML
Let’s create a new solution using the .NET CLI command. One can use Visual Studio as well.
dotnet new sln -n HtmlToPdf
I’ve added a new ASP.NET Core Web API project to the solution as below
![](https://cdn-images-1.medium.com/max/1000/1*VNWvTHsa3oG6jwpU7dsh5w.png)
I removed the default files Weatherforcast.cs & WeatherForecastController.cs
and added PdfController.cs with one Get action method which looks like this.
![](https://cdn-images-1.medium.com/max/1000/1*hEbJaXPooZRsUfcv255C4w.png)
Next, I added a Razor Class Library(RCL) project named HtmlToPdf.Template to create HTML template for our pdf.
HtmlToPdf.Template.csproj file should look like the below. If not please change as below.
![](https://cdn-images-1.medium.com/max/1000/1*7xpS1nFvGYdqEJKWhoO54A.png)
I grabbed some HTML from here created by Daryl Shannon and added it to PdfTemplate.cshtml file under the Views folder.
![](https://cdn-images-1.medium.com/max/1000/1*NGOWo9lmopMt3EXGi3bmOw.png)
I’ll add a reference to the HtmlToPdf.Template project to HtmlToPdf.Api
Also, I’ll install Razor.Templating.Core NuGet package to HtmlToPdf.Api project to render the PdfTemplate.cshtml file to a string. One good thing about this package is that we can even pass the view model to render the HTML string dynamically. For complete guidance on using this package, refer to this article. For simplicity’s sake, I’ll simply render the static HTML.
Now, all I need to do is to call the await RazorTemplateEngine.RenderAsync(“~/Views/PdfTemplate.cshtml”) method to render the string out of .cshtml file
![](https://cdn-images-1.medium.com/max/1000/1*xEBLVKC-wj5up2la8nx6rw.png)
Let’s quickly run the API project & see if the .cshtml to string works.
![](https://cdn-images-1.medium.com/max/1000/1*ymq3J_dIx0qvte6cCQ-GjA.png)
YES! Now we have the html string in hand.
HTML To PDF
I’ll be using JsReport library to convert HTML to PDF. Add the following references to your HtmlToPdf.Api.csproj
Note
Reference to jsreport.Binary.Linux is not required if you are not hosting in Linux machines
<PackageReference Include="jsreport.Binary" Version="2.11.0" /> <PackageReference Include="jsreport.Binary.Linux" Version="2.11.0" /> <PackageReference Include="jsreport.Local" Version="2.3.1" />
Use the following code to generate the PDF out of HTML string & return the file in the API.
[HttpGet()] public async Task<IActionResult> Get() { var html = await RazorTemplateEngine.RenderAsync("~/Views/PdfTemplate.cshtml"); var rs = new LocalReporting() .KillRunningJsReportProcesses() .UseBinary(RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? JsReportBinary.GetBinary() : jsreport.Binary.Linux.JsReportBinary.GetBinary()) .Configure(cfg => cfg.AllowedLocalFilesAccess().FileSystemStore().BaseUrlAsWorkingDirectory()) .AsUtility() .Create(); var generatedPdf = await rs.RenderAsync(new RenderRequest { Template = new Template { Recipe = Recipe.ChromePdf, Engine = Engine.None, Content = html, Chrome = new Chrome { MarginTop = "10", MarginBottom = "10", MarginLeft = "50", MarginRight = "50" } } }); return File(generatedPdf.Content, generatedPdf.Meta.ContentType, "GeneratedPdfFile.pdf"); }
If we run the api & hit https://localhost:5001/pdf, we can notice that the pdf file is downloaded!
![](https://cdn-images-1.medium.com/max/1000/1*ccn_fgMp9U99Vm5QUKNb1w.png)
Let’s look how does the pdf file look like
![](https://cdn-images-1.medium.com/max/1000/1*qvrBeevGUn2g-kklHPSIiw.png)
Voila!! Now we’ve rendered the HTML string into PDF.
Pro Tip
One can use the JsReport Playground to quickly design the HTML & see the pdf output in real-time. Later, we can grab that HTML to add to our .cshtml file.
Running on Linux using Docker
As we know, ASP.NET Core & JsReport are cross-platform, one might want to host it on a Linux server. Initially, it was a pain for me to write the Dockerfile to install the required dependencies for jsreport. Though, there were some examples. I had to face some rough edges. After googling & installing the required dependencies, I was able to successfully host it on a Linux machine(I used Ubuntu 20.04 here)
Here’s the Dockerfile.
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base RUN apt-get update # for jsreport # install chrome with deps, see https://github.com/jsreport/jsreport/blob/master/docker/full/Dockerfile RUN apt-get install -y --no-install-recommends libgconf-2-4 gnupg git curl wget ca-certificates libgconf-2-4 && \ wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - && \ sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' && \ apt-get update && \ apt-get install -y lsb-release google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst libxtst6 libxss1 --no-install-recommends RUN apt-get install -y gconf-service libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxss1 libxtst6 libappindicator1 libnss3 libasound2 libatk1.0-0 libc6 ca-certificates fonts-liberation lsb-release xdg-utils wget ENV chrome_launchOptions_executablePath google-chrome-stable ENV chrome_launchOptions_args --no-sandbox,--disable-dev-shm-usage,--single-process,--no-zygote WORKDIR /app EXPOSE 80 EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build WORKDIR /src COPY ["HtmlToPdf.Api/HtmlToPdf.Api.csproj", "HtmlToPdf.Api/"] RUN dotnet restore "HtmlToPdf.Api/HtmlToPdf.Api.csproj" COPY . . WORKDIR "/src/HtmlToPdf.Api" RUN dotnet build "HtmlToPdf.Api.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "HtmlToPdf.Api.csproj" -c Release -o /app/publish FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "HtmlToPdf.Api.dll"]
Key Takeaways
- If you want to generate the PDF in a background service or console application, that can also be done. Instead of ASP.NET Core, one needs to create the Worker Service or Console application and use the same project reference and code.
- You might notice that it takes 1 or more seconds to generate the PDF as jsreport is using a headless chrome browser on the server to generate the PDF from HTML. Generally, that should be ok.
- You might be surprised to see that the Docker image size is around 1GB when you add jsreport whereas the normal size is around 100MB. It’s because the image needs to have chrome dependencies. I believe this can be optimized.
Full code available here: RazorTemplating/examples/_RealWorldSamples/HtmlToPdf at master · soundaranbu/RazorTemplating (github.com)
Hope this article is helpful. Let’s meet in another one. Thanks!
Happy Coding 🙂
How can the docker image be optimized?
I haven’t tried that yet. But one way to keep your application image clean is to run the jsreport in a separate container & use “jsreport.Client” library. Have a look at the sample project by jsreport here jsreport/jsreport-dotnet-example-docker-compose: Run jsreport in one container, .net core app in another container and orchestrate them using docker-compose (github.com)
More documentation on this here .Net Client (jsreport.net)