Designing a Maintainable PDF Generation Web API in ASP.NET Core (Linux) C# with Clean Architecture and TX Text Control
This article shows how to create a PDF generation Web API in ASP.NET Core on Linux using TX Text Control .NET Server. The clean architecture is used to create a maintainable and testable solution.

When building document processing APIs, it's easy to fall into the trap of cramming too much logic into a single controller method. But as your application grows, the need for maintainability, testability, and clarity becomes critical.
Here's a guide to building a clean, scalable, and testable PDF merging API using TX Text Control, C#, and ASP.NET Core on Linux.
Why Clean Architecture?
Clean architecture emphasizes separation of concerns, keeping your business logic independent of frameworks, UI, and infrastructure. The goal is to:
- Improve maintainability
- Enable unit testing
- Allow for future extensibility
Let's look at how these principles apply to a TX Text Control based document merge API.
Folder Structure
The project structure is based on the default Visual Studio project template for creating an ASP.NET Core Web API application. The project is structured as follows:
/Controllers
DocumentController.cs
/Services
IDocumentMergeService.cs
DocumentMergeService.cs
/Utilities
MailMergeMapper.cs
/Models
MergeBody.cs
Core Components
Let's take a look at the core components responsible for the application merge process:
- DocumentController
The controller is responsible for handling incoming requests and delegating the work to the service layer.
[ApiController] [Route("[controller]")] public class DocumentController : ControllerBase { private readonly IDocumentMergeService _documentMergeService; public DocumentController(IDocumentMergeService documentMergeService) { _documentMergeService = documentMergeService; } [HttpPost("merge")] public IActionResult Merge([FromBody] MergeBody mergeBody) { if (mergeBody == null || mergeBody.Template == null) { return BadRequest("Invalid input. Template is required."); } try { var result = _documentMergeService.MergeDocument(mergeBody); return Ok(result); } catch (Exception ex) { return BadRequest(ex.Message); } } }
- IDocumentMergeService
The service layer handles the business logic. The service layer is independent of the controller and can be reused in other parts of the application. It is an interface to abstract the merge logic and promote testability.
public interface IDocumentMergeService { string MergeDocument(MergeBody mergeBody); }
- DocumentMergeService
The service implementation is where the actual merge logic resides. The service is responsible for merging the documents using TX Text Control and returning the result.
public class DocumentMergeService : IDocumentMergeService { public string MergeDocument(MergeBody mergeBody) { using var tx = new ServerTextControl(); tx.Create(); tx.Load(Convert.FromBase64String(mergeBody.Template), BinaryStreamType.InternalUnicodeFormat); var mailMerge = MailMergeMapper.ToMailMerge(mergeBody, tx); mailMerge.MergeJsonData(mergeBody.MergeData, true); var saveSettings = MailMergeMapper.ToSaveSettings(mergeBody.MergeSettings); tx.Save(out byte[] result, BinaryStreamType.AdobePDF, saveSettings); return Convert.ToBase64String(result); } }
- MailMergeMapper
The MailMergeMapper class is responsible for mapping the incoming JSON payload to a to MailMerge configuration. It basically returns a MailMerge object.
public static class MailMergeMapper { public static MailMerge ToMailMerge(MergeBody body, TXTextControl.ServerTextControl tx) { return new MailMerge { TextComponent = tx, DataSourceCulture = new CultureInfo(body.MergeSettings.DataSourceCulture), FormFieldMergeType = (FormFieldMergeType)body.MergeSettings.FormFieldMergeType, MergeCulture = new CultureInfo(body.MergeSettings.MergeCulture), RemoveEmptyBlocks = body.MergeSettings.RemoveEmptyBlocks ?? false, RemoveEmptyFields = body.MergeSettings.RemoveEmptyFields ?? false, RemoveEmptyImages = body.MergeSettings.RemoveEmptyImages ?? false, RemoveEmptyLines = body.MergeSettings.RemoveEmptyLines ?? false, RemoveTrailingWhitespace = body.MergeSettings.RemoveTrailingWhitespace ?? false }; } public static TXTextControl.SaveSettings ToSaveSettings(MergeSettings settings) { return new TXTextControl.SaveSettings { Author = settings.Author, CreationDate = (DateTime)settings.CreationDate, CreatorApplication = settings.CreatorApplication, DocumentSubject = settings.DocumentSubject, DocumentTitle = settings.DocumentTitle, LastModificationDate = (DateTime)settings.LastModificationDate }; } }
- MergeBody
The MergeBody class defines the incoming data contract. It is used to deserialize the incoming JSON payload.
public class MergeBody { public string MergeData { get; set; } public string Template { get; set; } public MergeSettings MergeSettings { get; set; } } public class DocumentSettings { public string Author { get; set; } public DateTime? CreationDate { get; set; } public string CreatorApplication { get; set; } public string DocumentSubject { get; set; } public string DocumentTitle { get; set; } public DateTime? LastModificationDate { get; set; } } public class MergeSettings : DocumentSettings { public bool? RemoveEmptyFields { get; set; } public bool? RemoveEmptyBlocks { get; set; } public bool? RemoveEmptyImages { get; set; } public bool? RemoveTrailingWhitespace { get; set; } public bool? RemoveEmptyLines { get; set; } public int? FormFieldMergeType { get; set; } public string Culture { get; set; } public string DataSourceCulture { get; set; } public string MergeCulture { get; set; } }
Single Responsibility Principle (SRP)
Each class has a single responsibility. The controller is responsible for handling HTTP requests, the service is responsible for the merge logic, and the mapper is responsible for mapping the incoming JSON payload to a MailMerge object.
Class | Responsibility |
---|---|
DocumentController | Handles HTTP requests and responses. |
DocumentMergeService | Manages document merging logic. |
MailMergeMapper | Converts DTOs (MergeBody) into MailMerge objects. |
MergeBody | Define the structure of the request body. |
Consuming the API
The API can be consumed with a simple HTTP POST request. The request body is a JSON object containing the merge data and template file. The response is a base64 encoded string of the merged document.
In the example project, we use Swagger to easily test the endpoint. Start the application and navigate to /swagger to test the API.
- Click on the POST operation.
- Click on Try it out.
-
Copy and paste the following JSON payload into the request body:
{ "mergeData":"{\"FirstName\":\"John\",\"LastName\":\"Doe\"}", "template":"CAcBAA4AAAAAAAAAAAAXAAIAqwBGAGkAcgBzAHQATgBhAG0AZQC7AHQAZQB4AHQAQwBvAG4AdAByAG8AbAAxAAAANgIAAAMAAQABAAEAAAAAAAAAAgCfhwEAAQALAAAAAAAAQAEAkgcAAABQAQAMAAAAAAAAAABAAAAAAAAAAFABAAwADAAAAAAAAEAAAAAAAAAAUDj/AAAAAAAAkAEAAAAAAAACIkFyaWFsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQABABgAAAAAAAAAAAAAAAAAZAAgAg8AAAABbgQB3AgBSg0BuBEBJhYBlBoBAh8BcCMB3icBTCwBujABKDUBljkBBD4BAAAAAAAAAAAAFAAAAEYAaQByAHMAdABOAGEAbQBlAAAAAQAHAAAAAAAAACwAAABNAEUAUgBHAEUARgBJAEUATABEAAAARgBpAHIAcwB0AE4AYQBtAGUAAAAAAAAAAAAAAAAAAAAAAAAAAABBAHIAaQBhAGwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAOQEAAABAAEACQYAAQAAAC4AAP//AAAAALcAAQAAAABAAAAAUAEAAgAJBAAAAAA8AABkAAAAAAEAAAAJBAAAAAAAAABkAAAAAAEAAAAJBAAAAAAAAABkAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAEAAQABABgAAAAAAAAAAAAAAAAAAAAAAAEAUwB5AG0AYgBvAGwAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQBAAAwAAQA0C8AAOA9AACgBaAFoAWgBQAAAEAAAAAAAAAAAAEAAAABAA4AAAAAAAAAAAAkAQAAAQAAAAAAOP8AAAAAAACQAQAAAAAAAAIiQXJpYWwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQQByAGkAYQBsAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAFAAAAAAAAAAAAAAAABkACACDwAAAAFuBAHcCAFKDQG4EQEmFgGUGgECHwFwIwHeJwFMLAG6MAEoNQGWOQEEPgEAAQAJBgABAAAALgAA//8AAAAAtwABAAAAAEAAAABQAAASAAAAAAAAAAAAAAAAAAAAAAAAAAAACQRkAAAAWwBOAG8AcgBtAGEAbABdAAAAUwB5AG0AYgBvAGwAAAAAAABAIAABAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQB6AAAAMgAAAABAAAAAADcCNwI3AjcC0C8AAOA9AACgBaAFoAWgBSAKP4AAAAAAAAAAAAEAAAAAAAAAGwEAAAAAAAAAAAAAAAAAAAAAAAAsAAAAAAAAAAAAxgHGAcYBxgEAAABAAAAAQAAAAEAAAABAAAAAAAAAAAAAAA==", "mergeSettings":{ "removeEmptyFields":true, "removeEmptyBlocks":true, "removeEmptyImages":true, "removeTrailingWhitespace":true, "removeEmptyLines":true, "formFieldMergeType":1, "culture":"en-US", "dataSourceCulture":"en-US", "mergeCulture":"en-US", "author":"John Doe", "creationDate":"2024-07-12T14:44:27.7222043+02:00", "creatorApplication":"TX Text Control", "documentSubject":"Subject", "documentTitle":"Title", "lastModificationDate":"2024-07-12T14:44:27.7244266+02:00" } }
-
Click on Execute.
-
The response is a base64-encoded string of the merged document. You can use this string to save the document to a file or display it in a viewer.
Deployment
Deploying the application on a Windows or Linux server is easy. The application is built using .NET Core, which is cross-platform. You can deploy the application to a Linux server using Docker or directly to the server.
Conclusion
Building a clean, scalable, and testable PDF merge API using TX Text Control, C#, and ASP.NET Core on Linux is easy. By following the principles of clean architecture, you can build a maintainable and extensible API that is easy to test and deploy.
Download the complete source code from GitHub.
Download and Fork This Sample on GitHub
We proudly host our sample code on github.com/TextControl.
Please fork and contribute.
Requirements for this sample
- TX Text Control .NET Server 33.0
- Visual Studio 2022
ASP.NET
Integrate document processing into your applications to create documents such as PDFs and MS Word documents, including client-side document editing, viewing, and electronic signatures.
- Angular
- Blazor
- React
- JavaScript
- ASP.NET MVC, ASP.NET Core, and WebForms
Related Posts
Use MailMerge in .NET on Linux to Generate Pixel-Perfect PDFs from DOCX…
This article explores how to use the TX Text Control MailMerge feature in .NET applications on Linux to generate pixel-perfect PDFs from DOCX templates. This powerful combination enables…
Deploying the TX Text Control Document Editor Backend Web Server in Docker
This article describes how to deploy the TX Text Control Document Editor backend web server in Docker. The backend web server is a .NET Core application that provides the required synchronization…
Generating Dynamic NDAs Using TX Text Control MailMerge in C# .NET
This article demonstrates how to generate dynamic NDAs using TX Text Control MailMerge in C# .NET. It covers the process of creating a template, binding data, and generating the final document.
Configuring the TX Text Control Document Editor Backend Web Server,…
This article describes how to configure the TX Text Control Document Editor Backend Web Server, including port and logging settings.
Manipulating Table Cells During the MailMerge Process in .NET C#
This article shows how to manipulate table cells during the mail merge process in .NET C#. The FieldMerged event can be used to manipulate the table cells after they are merged.