# 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.

- **Author:** Bjoern Meyer
- **Published:** 2025-03-27
- **Modified:** 2026-07-17
- **Description:** 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.
- **6 min read** (1028 words)
- **Tags:**
  - ASP.NET
  - ASP.NET Core
  - Backend
  - MailMerge
- **Web URL:** https://www.textcontrol.com/blog/2025/03/27/designing-a-maintainable-pdf-generation-web-api-in-asp-net-core-linux-c-sharp-with-clean-architecture-and-tx-text-control/
- **LLMs URL:** https://www.textcontrol.com/blog/2025/03/27/designing-a-maintainable-pdf-generation-web-api-in-asp-net-core-linux-c-sharp-with-clean-architecture-and-tx-text-control/llms.txt
- **LLMs-Full URL:** https://www.textcontrol.com/blog/2025/03/27/designing-a-maintainable-pdf-generation-web-api-in-asp-net-core-linux-c-sharp-with-clean-architecture-and-tx-text-control/llms-full.txt
- **GitHub Repository:** https://github.com/TextControl/TXTextControl.Core.MailMerge.API

---

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.

![PDF Merge Engine Web API](https://s1-www.textcontrol.com/assets/dist/blog/2025/03/27/a/assets/webapi1.webp "PDF Merge Engine Web API")

1. Click on the *POST* operation.
2. Click on *Try it out*.
3. 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"
       }
    }
    ```
4. Click on *Execute*.
    
    ![PDF Merge Engine Web API](https://s1-www.textcontrol.com/assets/dist/blog/2025/03/27/a/assets/webapi2.webp "PDF Merge Engine Web API")
5. 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.
    
    ![PDF Merge Engine Web API](https://s1-www.textcontrol.com/assets/dist/blog/2025/03/27/a/assets/webapi3.webp "PDF Merge Engine Web API")

### 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.

---

## About Bjoern Meyer

As CEO, Bjoern is the visionary behind our strategic direction and business development, bridging the gap between our customers and engineering teams. His deep passion for coding and web technologies drives the creation of innovative products. If you're at a tech conference, be sure to stop by our booth - you'll most likely meet Bjoern in person. With an advanced graduate degree (Dipl. Inf.) in Computer Science, specializing in AI, from the University of Bremen, Bjoern brings significant expertise to his role. In his spare time, Bjoern enjoys running, paragliding, mountain biking, and playing the piano.

- [LinkedIn](https://www.linkedin.com/in/bjoernmeyer/)
- [X](https://x.com/txbjoern)
- [GitHub](https://github.com/bjoerntx)

---

## Related Posts

- [Create SignFabric Envelopes from Mail Merge Templates in .NET C#](https://www.textcontrol.com/blog/2026/06/23/create-signfabric-envelopes-from-mail-merge-templates-using-dotnet-csharp/llms.txt)
- [Use MailMerge in .NET on Linux to Generate Pixel-Perfect PDFs from DOCX Templates](https://www.textcontrol.com/blog/2025/05/27/use-mailmerge-in-dotnet-on-linux-to-generate-pixel-perfect-pdfs-from-docx-templates/llms.txt)
- [Deploying the TX Text Control Document Editor Backend Web Server in Docker](https://www.textcontrol.com/blog/2025/04/10/deploying-the-tx-text-control-document-editor-backend-web-server-in-docker/llms.txt)
- [Generating Dynamic NDAs Using TX Text Control MailMerge in C# .NET](https://www.textcontrol.com/blog/2025/04/08/generating-dynamic-ndas-using-tx-text-control-mailmerge-in-csharp-dotnet/llms.txt)
- [Configuring the TX Text Control Document Editor Backend Web Server, including Port and Logging](https://www.textcontrol.com/blog/2025/03/28/configuring-the-tx-text-control-document-editor-backend-web-server-including-port-and-logging/llms.txt)
- [Manipulating Table Cells During the MailMerge Process in .NET C#](https://www.textcontrol.com/blog/2024/12/03/manipulating-table-cells-during-the-mailmerge-process-in-net-csharp/llms.txt)
- [When to Generate Documents Server-Side Instead of Client-Side: A Focus on Data Security](https://www.textcontrol.com/blog/2024/10/04/when-to-generate-documents-server-side-instead-of-client-side-a-focus-on-data-security/llms.txt)
- [Mail Merge: Inserting Merge Blocks using the DataSourceManager in C#](https://www.textcontrol.com/blog/2024/10/02/mail-merge-inserting-merge-blocks-using-the-datasourcemanager-in-csharp/llms.txt)
- [Creating Advanced Tables in PDF and DOCX Documents with C#](https://www.textcontrol.com/blog/2024/09/30/creating-advanced-tables-in-pdf-and-docx-documents-with-csharp/llms.txt)
- [Designing the Perfect Contract Template for MailMerge in C#](https://www.textcontrol.com/blog/2024/09/26/designing-the-perfect-contract-template-for-mailmerge-in-csharp/llms.txt)
- [Video Tutorial: Creating a MailMerge Template and JSON Data Structure](https://www.textcontrol.com/blog/2024/08/16/video-tutorial-creating-a-mailmerge-template-and-json-data-structure/llms.txt)
- [Getting Started Video Tutorial: How to use the MailMerge and ServerTextControl Classes in ASP.NET Core C#](https://www.textcontrol.com/blog/2024/08/05/getting-started-video-tutorial-how-to-use-the-mailmerge-and-servertextcontrol-classes-in-asp-net-core-c/llms.txt)
- [Getting Started Videos: New Text Control YouTube Channel](https://www.textcontrol.com/blog/2024/08/02/getting-started-videos-new-text-control-youtube-channel/llms.txt)
- [Best Practices for Mail Merge and Form Field Processing in ASP.NET Core C# Applications](https://www.textcontrol.com/blog/2024/07/30/best-practices-for-mail-merge-and-form-field-processing-in-asp-net-core-csharp-applications/llms.txt)
- [Advantages of Flow Type Layout Reporting vs. Banded Reporting or PDF Template Engines in .NET C#](https://www.textcontrol.com/blog/2024/07/29/advantages-of-flow-type-layout-reporting-vs-banded-reporting-or-pdf-template-engines-in-net-c-sharp/llms.txt)
- [Designing a MailMerge Web API Endpoint with ASP.NET Core in C#](https://www.textcontrol.com/blog/2024/07/12/designing-a-mailmerge-web-api-endpoint-with-asp-net-core-in-c-sharp/llms.txt)
- [Enhancing Documents with QR Codes and Barcodes in .NET C#: A Comprehensive Guide](https://www.textcontrol.com/blog/2024/07/11/enhancing-documents-with-qr-codes-and-barcodes-in-net-csharp-a-comprehensive-guide/llms.txt)
- [Document Automation 101: Leveraging TX Text Control for Business Efficiency in .NET C# Applications](https://www.textcontrol.com/blog/2024/07/09/document-automation-101-leveraging-tx-text-control-for-business-efficiency-in-net-c-applications/llms.txt)
- [Building an ASP.NET Core Backend Application to Host the Document Editor and Document Viewer](https://www.textcontrol.com/blog/2024/03/14/building-an-asp-net-core-backend-application-to-host-the-document-editor-and-document-viewer/llms.txt)
- [Merging Templates with MailMerge with Different Merge Field Settings in C#](https://www.textcontrol.com/blog/2023/12/16/merging-templates-with-mailmerge-with-different-merge-field-settings/llms.txt)
- [How to Mail Merge MS Word DOCX Documents in ASP.NET Core C#](https://www.textcontrol.com/blog/2023/10/16/how-to-mail-merge-ms-word-docx-documents-in-aspnet-core-csharp/llms.txt)
- [MailMerge: Working with Image Placeholders](https://www.textcontrol.com/blog/2022/12/22/mailmerge-working-with-image-placeholders/llms.txt)
- [Getting Started: ServerTextControl and MailMerge with ASP.NET Core](https://www.textcontrol.com/blog/2022/09/01/getting-started-servertextcontrol-and-mailmerge-with-aspnet-core/llms.txt)
- [Adding SVG Watermarks to Documents](https://www.textcontrol.com/blog/2022/01/28/adding-svg-watermarks-to-documents/llms.txt)
- [Using MailMerge in ASP.NET Core 6 Web Applications](https://www.textcontrol.com/blog/2022/01/27/using-mailmerge-in-aspnet-core-6-web-applications/llms.txt)
