Contract Generation Software

C# PDF API -Template Based PDF Generation

Introduction


C# is a programming language that supports Object Oriented Programming. Therefore, it is actively preferred by many institutions. There are many libraries available in the market for PDF generation. Almost all of them are HTML based and have several drawbacks including the need for a lot of developer’s time.

Template-based PDF generation is the preferred approach for agile organizations. Business users can use existing templates as-is. EDocGen is the best API in this segment. In this article, we integrate of C# application with the REST API.

The application is developed with Asp.Net Core 5. So let's get started.


Application Overview

Before diving into the code part of the application, let me tell you the working logic of the application I developed. The application is a web project. In short, the application enables the conversion of a file from JSON format to pdf format, which is requested by the client to be converted to pdf format. The converted file is sent to the client's email address by e-mail.

The client will send a file in JSON format to the Asp.Net Core Web API I wrote and an e-mail address that the converted file will reach.


Requirements

  1. .Net Core 5.0 version
  2. Docker
  3. When the application is run, a template that will transform the file in JSON format should be loaded on the EDocGen UI and the id of this template should be specified as static in the code.

Now let's go to the project setup.

Project Setup

Firstly we will create an ASP.NET Core Web API application from a Visual Studio application.

C# project for PDF generation

Then we will give a project name and select the .Net Core version of the project. In this project, we will choose .Net Core 5.0.

We have successfully created our project. Now we will install the packages that we will use in our project. Let's install the following packages via Nuget Manager.

● Newtonsoft.JSON

● RestSharp

● StackExchange.Redis

C# redis

Finally, we will prefer Redis cache as the cache mechanism in the application. Instead of installing Redis cache on our computer, we will run it in a docker container. For this, let's open a file named 'docker-compose.yml' in the file location of the application and paste the following codes into it.

version: '3'
services:
redis-sample:
ports:
- '6379:6379'
container_name: redis-cache
image: 'redis:latest'
networks:
- base-network

networks:
base-network:
driver: bridge

Then let's open a terminal in the file location and run Redis in a docker container with the command:

docker compose up -d

Project Namespaces and Classes

The project structure will be as follows:

DocumentController: Handle client’s post request with JSON file and email.

AuthenticationService: Check token is in the Redis cache.

LoginService: Get a token with username and password.

CacheService: Generate caching operations.

DocumentService: Send JSON file to ‘/generate/bulk’ service for generating a file with pdf format.

EmailService: Send generated file with pdf format to ‘/output/email’ service for emailing to the client.

FileUploadService: Save the file in ‘/Sources’ folder

Authentication and Login

It is the first step of the flow. AuthenticationService runs first in every request made to the endpoint. In AuthenticationService, first, the token value is searched in the Redis cache. If the token exists in the cache, the file generation process continues. If there is no token value in the cache, LoginService is activated and a token is obtained with username/password information. The resulting token is set to Redis cache with a TTL (Time-to-Live) of 3 hours.

AuthenticationService


 
public class AuthenticationService
    {
        private readonly CacheService cacheService;
        private readonly LoginService loginService;
        public AuthenticationService(CacheService cacheService, LoginService loginService)
        {
            this.cacheService = cacheService;
            this.loginService = loginService;
        }
        public async Task setCredential()
        {
            if (isNotTokenCached())
            {
                await setTokenToCache();
            }
        }

        private bool isNotTokenCached()
        {
            var result = cacheService.Get(RedisConstant.PDF_GENERATOR_TOKEN);
            return string.IsNullOrEmpty(result);
        }

        private async Task setTokenToCache()
        {
            var token = await loginService.loginAccount();
            cacheService.Set(RedisConstant.PDF_GENERATOR_TOKEN, token, TimeSpan.FromHours(3));
        }
    }

LoginService


public class LoginService
    {
        private readonly string username;
        private readonly string password;

        public LoginService(IConfiguration configuration)
        {
            this.username = configuration.GetSection("UserInfo:Username").Value;
            this.password = configuration.GetSection("UserInfo:Password").Value;
        }
        public async Task loginAccount()
        {
            var client = new RestClient("https://app.edocgen.com");
            var request = new RestRequest("/login", Method.Post);
            
            request.AddHeader("Content-Type", "application/json");
            request.AddJsonBody(
                new
                {
                    username = username,
                    password = password
                });

            RestResponse response;
            try
            {
                response = await client.ExecuteAsync(request);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw new LoginException($"Error occurred while getting token with username: {username}");
            }
            
            validateResponse(response);
            return JsonConvert.DeserializeObject(response.Content).token;           
        }
        private void validateResponse(RestResponse response)
        {
            if (isNotResponseValid(response))
                throw new InvalidServiceResponseException("Error occurred while generating document");
        }

        private bool isNotResponseValid(RestResponse response)
        {
            return !(response != null && response.StatusCode == System.Net.HttpStatusCode.OK);
        }
    }

Generate Document

This is the next step after the authentication step. In this step, we will perform validation of the JSON format file sent by the client and save the file in the file '/Sources' folder.

After this process, the following service will go to the step that will request the creation of the JSON file in pdf format.

Url

https://app.edocgen.com/api/v1/document/generate/bulk

Method

HTTP POST

Parameters

documentId(string, formData): id of the template.

format(string, formData) : output format docx or pdf. Default is docx.

outputFileName(string, formData): file name for the output file.

inputFile(file, formData): file containing marker values. Suppoerts JSON, XLSX and XML.

Headers

x-access-token(string, header): authorization header as obtained by calling login

Content-Type(string header): multipart/form-data

If the request is successful, the output generation step will start.

DocumentService


    
public class DocumentService
    {
        private readonly CacheService cacheService;
        private readonly EmailService emailService;
        private readonly FileUploadService fileUploadService;
        private readonly AuthenticationService authenticationService;

        public DocumentService(CacheService cacheService, EmailService emailService, 
            FileUploadService fileUploadService, AuthenticationService authenticationService)
        {
            this.cacheService = cacheService;
            this.emailService = emailService;
            this.fileUploadService = fileUploadService;
            this.authenticationService = authenticationService;
        }

        public async Task executeDocumentProcess(FileUploadDto fileUploadDto, string email)
        {
            try
            {
                FileUpload.validateFile(fileUploadDto);
                fileUploadService.saveFileToFolder(fileUploadDto);
                FileRequestModel fileRequestModel = createFileRequestModel(fileUploadDto);
                var response = await generateDocument(fileRequestModel);
                await emailService.processOutput(fileRequestModel, email);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
                throw new FileGenerationException("Error occurred while generating document");
            }
        }

        private async void setServiceCredential()
        {
           await authenticationService.setCredential();
        }

        private FileRequestModel createFileRequestModel(FileUploadDto fileUploadDto)
        {
            var token = cacheService.Get(RedisConstant.PDF_GENERATOR_TOKEN);
            var filePath = @"./Sources/" + fileUploadDto.file.FileName;
            var fileName = fileUploadDto.file.FileName + DateTime.Now.ToString();

            FileRequestModel fileRequestModel = new FileRequestModel()
            {
                token = token,
                filePath = filePath,
                fileName = fileName
            };

            return fileRequestModel;
        }

        private bool isNotResponseValid(RestResponse response) {
            return !(response != null && response.StatusCode == System.Net.HttpStatusCode.OK);
        }

        private async Task generateDocument(FileRequestModel fileRequestModel)
        {
            var client = new RestClient("https://app.edocgen.com");
            var request = new RestRequest("/api/v1/document/generate/bulk", Method.Post);
            request.AddHeader("Content-Type", "multipart/form-data");
            request.AddHeader("x-access-token", fileRequestModel.token);
            request.AddParameter("documentId", FileGenerationConstants.DEFAULT_TEMPLATE_ID);
            request.AddFile("inputFile", fileRequestModel.filePath);
            request.AddParameter("outputFileName", fileRequestModel.fileName);
            request.AddParameter("format", FileGenerationConstants.PDF_OUTPUT_FORMAT);

            var response = await client.ExecuteAsync(request);

            validateResponse(response);
            return response;
        }

        private void validateResponse(RestResponse response)
        {
            if (isNotResponseValid(response))
                throw new InvalidServiceResponseException($"Error occurred as a result of service call: {response.ResponseUri} , " +
                    $"message: {response.ErrorMessage} , " +
                    $"exception: {response.ErrorException}");
        }        
    }
}

 

Output Process

This is the next step after converting the JSON formatted file sent by the client to pdf. In this step, we will run a 10-second Thread.sleep before output is generated in EmailService. This is due to the fact that there may be a delay in generating the file with pdf format.

The first step in the output generation phase will be to get the id information of the pdf file created in the previous step. For this, we will send a request to the service below.

Url

https://app.edocgen.com/api/v1 /api/v1/output/name/{output_file_name}

Method

HTTP GET

Headers

x-access-token(string, header): authorization header as obtained by calling login

Content-Type(string header): multipart/form-data

We obtain the id information from the output information obtained as a result of this service call. After obtaining the id information of the generated pdf file, we will proceed to the e-mailing step.

The service we will use to send e-mails is as follows.

Url

https://app.edocgen.com/api/v1 /api/v1/output/email

Method

HTTP POST

Body

{

“outId”:”string”,

“emailId”:”string”

}

Headers

x-access-token(string, header): authorization header as obtained by calling login

Content-Type(string header): multipart/form-data

We will set the ID of the pdf obtained in the previous step in the field “outId” in the body of the email service. We will add the e-mail address information sent by the customer to the "emailId" field and make a request to the service.

EmailService


public class EmailService
    {
        private CacheService cacheService;
        public EmailService(CacheService cacheService)
        {
            this.cacheService = cacheService;
        }

        public async Task processOutput(FileRequestModel fileRequestModel, string email)
        {
            try
            {
                var outputId = getOutputId(fileRequestModel);

                var client = new RestClient("https://app.edocgen.com");
                var request = new RestRequest("/api/v1/output/email", Method.Post);
                request.AddHeader("Content-Type", "application/json");


                request.AddHeader("x-access-token", fileRequestModel.token);

                request.AddJsonBody(
                                new
                                {
                                    outId = outputId,
                                    emailId = email
                                });
                var response = await client.ExecuteAsync(request);
                validateResponse(response);
            }
            catch (Exception ex)
            {
                throw new ProcessOutputException(ex.Message);
            }  
        }

        private string getOutputId(FileRequestModel fileRequestModel)
        {
            Thread.Sleep(10000);
            var client = new RestClient("https://app.edocgen.com");
            var request = new RestRequest($"/api/v1/output/name/{fileRequestModel.fileName}.{FileGenerationConstants.PDF_OUTPUT_FORMAT}.{FileGenerationConstants.ZIP_OUTPUT_FORMAT}", Method.Get);
            request.AddHeader("Content-Type", "multipart/form-data");
            request.AddHeader("x-access-token", fileRequestModel.token);

            var response = client.Execute(request);
            validateResponse(response);

            var outputModelDto = JsonConvert.DeserializeObject(response.Content);
            validateOutputResponse(outputModelDto);
            return outputModelDto.output[0]._id;
        }

        private void validateResponse(RestResponse response)
        {
            if (isNotResponseValid(response))
                throw new InvalidServiceResponseException($"Error occurred as a result of service call: {response.ResponseUri} , " +
                    $"message: {response.ErrorMessage} , " +
                    $"exception: {response.ErrorException}");
        }

        private bool isNotResponseValid(RestResponse response)
        {
            return !(response != null && response.StatusCode == System.Net.HttpStatusCode.OK);
        }

        private void validateOutputResponse(OutputModelDto outputModelDto)
        {
            if (isNotOutputResponseValid(outputModelDto))
                throw new InvalidOutputResponseException("Output model is not valid");
        }

        private bool isNotOutputResponseValid(OutputModelDto outputModelDto)
        {
            return !(outputModelDto != null && outputModelDto.output.Any());
        }

Controller Step

This is the step where the file in JSON format and email information sent from the client are handled. In this step, we'll start the authentication process first, then continue the document generation process.

DocumentController


    [Route("api/[controller]/generate")]
    [ApiController]
    public class DocumentController : ControllerBase
    {
        private readonly AuthenticationService authenticationService;
        private readonly DocumentService documentService;

        public DocumentController(DocumentService documentService, AuthenticationService authenticationService)
        {
            this.authenticationService = authenticationService;
            this.documentService = documentService;
        }

        [HttpPost]
        [Route("{email}")]
        public async Task generateDocument([FromForm] FileUploadDto fileUploadDto, string email)
        {
            await authenticationService.setCredential();
            await documentService.executeDocumentProcess(fileUploadDto, email);
            return new ResponseModel() { 
                isSuccess = true,
                statusCode = 200,
                message = $"The file named {fileUploadDto.file.FileName} converted to {FileGenerationConstants.PDF_OUTPUT_FORMAT} format successfully. Sent to {email} address." 
            };
        }

Test

After developing our application according to the flow and structure we designed, now it's time to test it. We will use the postman client for testing.

Let's run the application. After we run it, we will send a request to the “api/document/generate/{email}” endpoint we created. We will send this JSON file to our endpoint.


JSON_Data.json


    [
    {
        "Invoice_Number": "SBU-2053501",
          "Invoice_Date": "31-07-2020",
          "Terms_Payment": "Net 15",
          "Company_Name": "Company A",
          "Billing_Contact": "A-Contact1",
          "Address": "New york, United States",      
          "Logo":"62b83ddcd406d22dc7516b53",  
          "para": "61b334ee7c00363e11da3439",
          "Email":"[email protected]",
          "subtemp": "62c85b97f156ce4fbdb01bcb",
          "ITH": [{
            "Heading1":"Item Description",
            "Heading2": "Amount"
           
        }],
       "IT": [{        
            "Item_Description": "Product Fees: X",
            "Amount": "5,000"
           
        }]
        },
        {
        "Invoice_Number": "SBU-2053502",
          "Invoice_Date": "31-07-2020",
          "Terms_Payment": "Net 15",
          "Company_Name": "Company B",
          "Billing_Contact": "B-Contact2",
          "Address": "Seattle, United States",        
          "Logo":"62b83ddcd406d22dc7516b53",
          "para": "61b334ee7c00363e11da3439",
          "Email":"[email protected]",
            "subtemp": "62c85b97f156ce4fbdb01bcb",
          "ITH": [{
            "Heading1":"Item Description",
            "Heading2": "Amount"
           
        }],
       "IT": [{        
            "Item_Description": "Product Fees: X",
            "Amount": "5,000"
           
        },
        {          
            "Item_Description": "Product Fees: Y",
            "Amount": "6,000"
           
        }]
        }
]

    
                    

Now let's test with the postman as follows.

C# Postman

We see that we have received a successful response. Let's check our email address now.

C# postman document download

We have tested that EDocGen successfully converts our JSON file to pdf files and sends mail.

We have come to the end of this article. This time, we experienced how fast and effectively the document generate services developed by EDocGen work through the C# language. See you in the next post.

Popular Posts