# Large Documents

> Handle documents with thousands of pages: viewer streaming, request size limits in Kestrel and IIS, long merges, text replacement in large DOCX and PDF size.

- **Product:** TX Text Control .NET Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET Server > Performance and Scalability > Large Documents
- **3034 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/performance/large-documents/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/performance/large-documents/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/performance/large-documents/llms-full.txt

---

A contract of a few hundred pages, a merged report of several thousand or a DOCX file full of photos behaves differently from a one-page letter. It takes longer to load and render, it may exceed the request size limits of your server when a browser sends it, and a merge that creates it no longer fits into a single HTTP request. None of this needs a different API. It needs the right settings on the server and the right place for long-running work, and both are described below for the Document Viewer, the Document Editor, `MailMerge` and `ServerTextControl`.

### Show Large Documents in the Document Viewer

The Document Viewer doesn't wait for the whole document. Since version 32.0 SP3, it loads documents with long polling: the server keeps the request open and sends each page as soon as it's rendered, so users read and annotate the first pages while the rest is still being processed. Your code doesn't need to do anything for this, as [Large Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-viewer/loading/#large-documents) in Document Viewer notes.

- **Rendering.** [DocumentViewerSettings.RenderMode](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-web-mvc-documentviewer/documentviewersettings/rendermode-property/llms-full.txt) chooses how pages are sent: as vector graphics with text, as glyph outlines or as bitmaps. The default, `Auto`, switches between them to balance performance and appearance. [Render Modes](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-viewer/architecture/#render-modes) in Document Viewer Architecture compares the modes.
- **PDF documents.** The viewer can display PDF documents with PDF.js in the browser instead of rendering them on the server, as [Document Viewer Architecture](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-viewer/architecture/llms-full.txt) describes.
- **Documents sent from the browser.** A document that the view passes as [DocumentData](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-web-mvc-documentviewer/documentviewersettings/documentdata-property/llms-full.txt) or that JavaScript loads with [loadDocument](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript-document-viewer/documentviewercontroller/loaddocument-method/llms-full.txt) reaches the server in a POST request, as base64-encoded data. A large document can exceed the request size limit of the server, which the next section raises.

### Raise the Request Size Limits

ASP.NET Core limits the size of request bodies. The limits apply to every request that carries a document: uploads, documents that the Document Viewer posts to its controller and documents that a page sends to your controller after saving them in the Document Editor.

| Server | Limit | Default | When it's exceeded |
|---|---|---|---|
| Kestrel | `KestrelServerLimits.MaxRequestBodySize` | 30,000,000 bytes, about 28.6 MB | The connection is reset |
| IIS, in-process hosting | `maxAllowedContentLength` in *web.config* and `IISServerOptions.MaxRequestBodySize` | 30,000,000 bytes each | IIS answers with the status code 404.13 |
| Form uploads with `IFormFile` | `FormOptions.MultipartBodyLengthLimit` | 134,217,728 bytes, 128 MB | An `InvalidDataException` |

Raise the limits in *Program.cs* before `builder.Build()`. The following code allows request bodies of up to 100,000,000 bytes, both in Kestrel and in IIS:

```csharp
// Kestrel: applications without IIS, containers, Linux
builder.WebHost.ConfigureKestrel(serverOptions =>
{
    serverOptions.Limits.MaxRequestBodySize = 100_000_000;
});

// IIS with the in-process hosting model
builder.Services.Configure<IISServerOptions>(options =>
{
    options.MaxRequestBodySize = 100_000_000;
});
```

IIS checks the size of a request before it reaches your application. Add the same value to the *web.config* file of the published application:

```xml
<configuration>
  <system.webServer>
    <security>
      <requestFiltering>
        <requestLimits maxAllowedContentLength="100000000" />
      </requestFiltering>
    </security>
  </system.webServer>
</configuration>
```

The code works as follows:

- `ConfigureKestrel` sets the limit of Kestrel for all requests. With out-of-process hosting behind IIS, IIS sets the limit and Kestrel's limit doesn't apply.
- `IISServerOptions.MaxRequestBodySize` and `maxAllowedContentLength` both apply with in-process hosting, which is the default for IIS. Raise both, or the lower one rejects the request.
- To raise the limit for a single endpoint only, such as the action that saves documents from the editor, put the attribute `[RequestSizeLimit(100_000_000)]` on it instead. It can't exceed `maxAllowedContentLength` in IIS.

Microsoft describes the limits in [Upload files in ASP.NET Core](https://learn.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads). Choose a limit that fits your largest documents: base64 encoding makes the data that a page sends larger than the document itself.

### Save Large Documents From the Document Editor

`saveDocument` in the Document Editor returns binary formats such as DOCX as base64-encoded data, and the page usually posts it to a controller of your application, as [Store Documents on the Server](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/load-save/#store-documents-on-the-server) shows. For large documents, raise the limit of this action with `[RequestSizeLimit]`.

Server code can also save the document of an open editor without a request from the browser: `WebSocketHandler.GetInstance` returns the handler of an editor by its connection ID, and its `SaveText` method writes the document into a byte array on the server, as [Reach a Session on the Server](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/websockets/#reach-a-session-on-the-server) shows.

### Merge Documents With Thousands of Pages

A typical invoice is merged in a few hundred milliseconds. A document with many nested merge blocks and hundreds or thousands of pages takes seconds or minutes, which is too long for an HTTP request:

- **Merge in the background.** Accept the request, merge the document in a background service and notify the client when it's ready, as [Keep the Service Responsive](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/rest-services/#keep-the-service-responsive) in REST Services describes.
- **Create one document per record.** When the data contains many records, such as one invoice per customer, a merge with `append` set to `false` creates a separate document for each record, and the `DataRowMerged` event delivers each of them. [Create One Document or Separate Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/mail-merge/overview/#create-one-document-or-separate-documents) shows the code.
- **Design long tables with care.** In Text Control's benchmark of 2023, run with version 31.0 and the Classic engine, the time for one merge block grew faster than its number of rows: one row took 0.23 seconds, and the benchmark went up to 1,280 rows on 59 pages. The same rows split into two merge blocks merged faster, and formulas in merge blocks add a calculation for each row. The blog article [MailMerge Class Performance Benchmark](https://www.textcontrol.com/blog/2023/04/21/mailmerge-performance-benchmark/) shows the measurements.

### Replace Text in Many Large Files

`ServerTextControl` works on large DOCX files with the same members as on small ones. In a benchmark of Text Control on an eight-core Ryzen 7 processor, loading an 80-page DOCX file with about 30,000 words, replacing five placeholders and saving it took 0.46 seconds. Processing the file 400 times, 32,000 pages in total, took 73.32 seconds, 0.18 seconds per document, with one `ServerTextControl` that loaded and saved it again and again.

The following program replaces a placeholder in all DOCX files of the folder *Documents* and saves the results in *Output*:

```csharp
using TXTextControl;

string searchText = "%%company_name%%";
string replacementText = "Text Control, LLC";

Directory.CreateDirectory("Output");

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();

    foreach (string file in Directory.GetFiles("Documents", "*.docx"))
    {
        tx.Load(file, StreamType.WordprocessingML);

        int start = 0;
        int position;

        while ((position = tx.Find(searchText, start, FindOptions.MatchCase)) != -1)
        {
            tx.Select(position, searchText.Length);
            tx.Selection.Text = replacementText;

            // Continue behind the inserted text
            start = position + replacementText.Length;
        }

        tx.Save(Path.Combine("Output", Path.GetFileName(file)), StreamType.WordprocessingML);
    }
}
```

The code works as follows:

- One `ServerTextControl` is created once and loads one file after another; [Load](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/load-method/llms-full.txt) replaces the previous document.
- [Find](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/find-method/llms-full.txt) returns the position of the next occurrence, or -1 when there is none. [Select](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/select-method/llms-full.txt) selects it, and setting [Selection.Text](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/selection/text-property/llms-full.txt) replaces it.
- [Save](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/save-method/llms-full.txt) writes the changed document as DOCX.

[Find and Replace Text](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/text/#find-and-replace-text) explains the search options, and the blog article [High-Performance Text Replacement in Large DOCX Files using C# .NET](https://www.textcontrol.com/blog/2025/07/30/high-performance-text-replacement-in-large-docx-files-using-csharp-dotnet/) contains the benchmark. For batches of hundreds or thousands of documents, Text Control recommends several processes instead of several threads in one process, which can wait for each other at TX Text Control's critical sections, as [Multiple Processes](https://docs.textcontrol.com/tx-text-control-dotnet-server/performance/multiple-processes/llms-full.txt) describes.

### Large PDF Documents

- **Display.** The Document Viewer can render PDF documents in the browser with PDF.js, as described above.
- **Text.** To read the text of a PDF, such as for a search index, [PDF.Contents.Lines](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-documentserver-pdf/contents-lines/llms-full.txt) returns its lines with their page and position, without loading the document into a `ServerTextControl` in your code, as [Text Extraction](https://docs.textcontrol.com/tx-text-control-dotnet-server/pdf/text-extraction/llms-full.txt) shows.

### Keep Documents Small

Images can take up most of the size of a PDF document. When a document is saved as PDF, [SaveSettings.ImageCompressionQuality](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/imagecompressionquality-property/llms-full.txt) compresses its images, and [ImageMaxResolution](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/savesettings/imagemaxresolution-property/llms-full.txt) reduces images with a higher resolution. In a test of Text Control with a 10-page document and two large images, the PDF took 14.68 MB with lossless compression and 1.48 MB with a quality of 70. [Reduce the Size of PDF Files](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/saving-converting/#reduce-the-size-of-pdf-files) shows the code, and the blog article [Best Practices for Image Compression when Exporting to PDF in .NET C#](https://www.textcontrol.com/blog/2025/08/22/best-practices-for-image-compression-when-exporting-to-pdf-in-dotnet-csharp/) the measurements.

### Next Steps

- [Memory Usage](https://docs.textcontrol.com/tx-text-control-dotnet-server/performance/memory-usage/llms-full.txt): the memory that documents and editing sessions need.
- [Multiple Processes](https://docs.textcontrol.com/tx-text-control-dotnet-server/performance/multiple-processes/llms-full.txt): process many documents in parallel.
- [Performance Best Practices](https://docs.textcontrol.com/tx-text-control-dotnet-server/performance/best-practices/llms-full.txt): the measures that make document processing faster.
- [Loading Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-viewer/loading/llms-full.txt): the ways to get a document into the Document Viewer.

---

## Nearby

- **Previous:** [Memory Usage](https://docs.textcontrol.com/tx-text-control-dotnet-server/performance/memory-usage/llms-full.txt)
- **Next:** [Performance Best Practices](https://docs.textcontrol.com/tx-text-control-dotnet-server/performance/best-practices/llms-full.txt)
