# WebSocket Architecture

> Host the Document Editor's WebSocket connection: the URL, UseTXWebSocketMiddleware order and overloads, WebSocketHandler, the backend TCP link, IIS and nginx.

- **Product:** TX Text Control .NET Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET Server > Integration Guide > WebSocket Architecture
- **4275 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/websockets/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/websockets/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/websockets/llms-full.txt

---

Every keystroke, selection and formatting command in the Document Editor travels from the browser over a WebSocket connection to your ASP.NET Core application and on to the backend process. Hosting the editor means keeping this path open: a URL that reaches the middleware, the middleware at the right place in the pipeline and servers, load balancers and firewalls that let the connection through.

[Document Editor Architecture](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/architecture/llms-full.txt) introduces the three parts, the life of an editing session and the configuration of the backend process. The sections below go one level deeper into the connection itself, for developers who host the editor or run into connection problems. The Document Viewer uses HTTP requests instead of WebSockets, so none of this applies to it.

### The Path of a Connection

[The Three Parts](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/architecture/#the-three-parts) shows the chain from the browser to the backend process. Along this chain, a connection takes four steps:

1. **The browser opens the connection.** The frontend connects to the WebSocket URL of your application and requests the sub-protocol `TXTextControl.Web`, which identifies it as a TX Text Control connection. While the ribbon tabs are loading, the editor opens a second connection to the same URL and closes it again when they have arrived.
2. **ASP.NET Core accepts it.** `app.UseWebSockets()` enables WebSocket requests in the application, and `app.UseTXWebSocketMiddleware()` takes over those that belong to the editor.
3. **A handler forwards the traffic.** For each connection, the middleware creates a `WebSocketHandler` that passes the messages between the browser and the backend process. A handler is never reused.
4. **The backend process does the work.** The handler talks to `TXTextControl.Web.Server.Core` over a plain TCP connection on a fixed port.

The same path also serves the editor script over HTTP, for example `/api/TXWebSocket/GetResource?name=tx-document-editor.min.js`, which JavaScript pages and the Angular and React components load from the backend.

### How the WebSocket URL Is Built

| Frontend | Where the URL comes from |
|---|---|
| ASP.NET Core view | The HTML helper builds it from the address of the page and [TextControlSettings.APIRoute](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-web-mvc/textcontrolsettings/apiroute-property/llms-full.txt), which defaults to `api/`. The result is `/api/TXWebSocket` on the host of the page |
| JavaScript | The mandatory [webSocketURL](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/componentsettings/websocketurl-property/llms-full.txt) setting of [TXTextControl.init](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/txtextcontrol/init-method/llms-full.txt) |
| Angular and React | The `webSocketURL` input or prop of the component |
| Blazor | The component runs in the application that hosts the middleware and needs no connection setting |

A URL starts with `wss` when the backend runs on HTTPS and with `ws` when it runs on HTTP. A frontend on another origin, such as an Angular or React development server, connects without a CORS policy.

The middleware reacts to every request whose path contains `/txwebsocket`, regardless of case. That is why older articles that connect to `/TXWebSocket` without the `api` segment still work. Use `/api/TXWebSocket`, which matches the default of the HTML helper.

#### Host the Application in a Subfolder

By default, the HTML helper expects the handler at the root of the host. When the application runs in a subfolder, such as a virtual application in IIS, add the folder to `APIRoute`:

```cshtml
@using TXTextControl.Web.MVC

@Html.TXTextControl().TextControl(settings => {
    settings.APIRoute = "WebApplication120/api";
}).Render()
```

The editor then connects to `/WebApplication120/api/TXWebSocket`. [TextControlSettings.StaticFilesPath](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-web-mvc/textcontrolsettings/staticfilespath-property/llms-full.txt) sets the path of the static files in the same situation. A wrong route or static files path keeps an editor in a subfolder from connecting. The editor then shows "Can't connect to the server.", and the console of the browser's developer tools shows that it tried the root of the host instead of the subfolder:

![A page at localhost/WebApplication120 whose Document Editor shows "Can't connect to the server. Please refresh the website to re-connect.", with the browser console reporting that the WebSocket connection to ws://localhost/api/TXWebSocket failed.](https://docs.textcontrol.com/assets/img/tx-text-control-dotnet-server/editor-cant-connect-console.7b0d2982.webp)

### Add the Middleware to the Pipeline

The order of the calls in *Program.cs* decides which middleware sees a request first. The README of the `TXTextControl.Web.DocumentEditor.Backend` package places the WebSocket calls after `app.UseRouting()` and before any endpoint mappings. The complete *Program.cs* of an MVC application is in [Configure Program.cs](https://docs.textcontrol.com/tx-text-control-dotnet-server/getting-started/document-editor/aspnet-core/#configure-program-cs) of the Getting Started tutorial; the part that matters here is the order in its pipeline:

```csharp
app.UseHttpsRedirection();
app.UseRouting();

// Accept WebSocket requests
app.UseWebSockets();

// A middleware that checks the WebSocket handshake goes here

// Hand the connections of the editor to a WebSocketHandler
app.UseTXWebSocketMiddleware();

app.UseAuthorization();
app.MapStaticAssets();
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}")
    .WithStaticAssets();
```

The code works as follows:

- `UseWebSockets` enables WebSocket support in ASP.NET Core. The editor can't connect without it, so it comes before `UseTXWebSocketMiddleware`.
- A middleware that validates the handshake, for example an access token, goes between the two calls. It runs before the connection is established, and if it rejects the request, the handshake fails. [WebSocket Security](https://docs.textcontrol.com/tx-text-control-dotnet-server/security/websockets/llms-full.txt) describes such a middleware.
- `UseTXWebSocketMiddleware` without parameters connects to the backend process that `DocumentEditorWorkerManager` starts on the same machine with its default port.
- Web API backends for Angular, React and JavaScript frontends use the same calls with `MapControllers` instead of the controller route.
- The authentication and authorization of ASP.NET Core apply to the WebSocket handshake only when `UseAuthentication` and `UseAuthorization` come before `UseTXWebSocketMiddleware`. [Require Signed-In Users](https://docs.textcontrol.com/tx-text-control-dotnet-server/security/authentication/#require-signed-in-users) shows the order.

#### Restrict the Origins

CORS policies don't protect the WebSocket connection. `WebSocketOptions.AllowedOrigins`, passed to `UseWebSockets`, accepts handshakes only from the origins you list; [Restrict the Origins](https://docs.textcontrol.com/tx-text-control-dotnet-server/security/websockets/#restrict-the-origins) in WebSocket Security shows the code and what the list protects against. `WebSocketOptions.KeepAliveInterval` sets how often the server sends ping frames that keep proxies from closing idle connections; the default is two minutes.

#### Choose an Overload

`UseTXWebSocketMiddleware` has six overloads:

| Call | Use it when |
|---|---|
| `UseTXWebSocketMiddleware()` | The backend process runs on the same machine with its default settings |
| `UseTXWebSocketMiddleware(string hostNameOrAddress, int servicePort)` | The backend listens on another address or port; a host name is resolved with DNS |
| `UseTXWebSocketMiddleware(IPAddress serviceAddress, int servicePort)` | The same with an `IPAddress` |
| `UseTXWebSocketMiddleware(string webSocketPath)` | The middleware should react to a path part other than `/txwebsocket` |
| `UseTXWebSocketMiddleware(string hostNameOrAddress, int servicePort, string webSocketPath)` and `UseTXWebSocketMiddleware(IPAddress serviceAddress, int servicePort, string webSocketPath)` | Both |

A call with a single string, such as `UseTXWebSocketMiddleware("127.0.0.1")`, selects the overload for the path, not the host. Older articles call it this way because they were written before the path overloads existed. Always pass host and port together. The address and port must match the ones the backend process listens on, as [Configure the Backend Process](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/architecture/#configure-the-backend-process) describes.

### Reach a Session on the Server

Each open editor has its own `WebSocketHandler`, and your server code can find it by the connection ID of the editor. `WebSocketHandler.GetInstance` returns the handler, which can save the current document, load a document into the editor or work on the selection, without any action in the browser. The following controller saves the document of an editor as DOCX:

```csharp
using Microsoft.AspNetCore.Mvc;
using TXTextControl.Web;

[ApiController]
[Route("[controller]")]
public class EditorSessionController : ControllerBase
{
    [HttpPost("Save")]
    public IActionResult Save([FromQuery] string connectionId)
    {
        // Find the handler of the editor with this connection ID
        WebSocketHandler handler = WebSocketHandler.GetInstance(connectionId);

        if (handler == null)
        {
            return NotFound();
        }

        handler.SaveText(out byte[] data, BinaryStreamType.WordprocessingML);

        // Store the data, for example in a database
        return Ok();
    }
}
```

The page sends the connection ID once the editor is ready:

```javascript
TXTextControl.addEventListener("textControlLoaded", function () {
  const connectionId = TXTextControl.connectionID;

  document.getElementById("save").addEventListener("click", function () {
    fetch("/EditorSession/Save?connectionId=" + encodeURIComponent(connectionId), { method: "POST" });
  });
});
```

The code works as follows:

- [TXTextControl.connectionID](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/txtextcontrol/connectionid-property/llms-full.txt) returns the unique ID of the connection. It's available after the `textControlLoaded` event.
- `GetInstance` returns the handler of the connection with that ID, or `null` when the editor is no longer connected.
- `SaveText` writes the document into a byte array in the [BinaryStreamType](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-web/binarystreamtype/enumeration/llms-full.txt) you pass. The handler also has `LoadText` to replace the document in the editor and a `Selection` property that loads and saves the selected part with [Selection.Load](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-web/selection/load-method/llms-full.txt) and [Selection.Save](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol-web/selection/save-method/llms-full.txt).

A background service that calls `GetInstance` and `SaveText` regularly keeps the last state of a document on the server even when the connection of the browser drops. The blog article [Best Practices: Reliable Auto-Save in TX Text Control Using the WebSocketHandler and Background Services](https://www.textcontrol.com/blog/2025/06/20/best-practices-reliable-auto-save-in-tx-text-control-using-the-websockethandler-and-background-services/) implements it, and [How to Manipulate the Document using the WebSocketHandler](https://www.textcontrol.com/blog/2023/11/22/how-to-manipulate-the-document-using-the-websockethandler/) edits the selection with `ServerTextControl` and loads the result back.

### The Connection to the Backend Process

Between the middleware and `TXTextControl.Web.Server.Core`, the traffic is plain TCP on a fixed port: the backend process listens, and each handler connects to it. [Configure the Backend Process](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/architecture/#configure-the-backend-process) sets its address, port and logging, and [Run the Backend Separately](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/architecture/#run-the-backend-separately) moves it to another machine or container.

`DocumentEditorWorkerManager` passes `ListenAddress` and `Port` from `DocumentEditorWorkerOptions` to the process as the command-line arguments `--listen-address` and `--port`, and forwards the output of the process to the logging of your application. A backend that you start yourself accepts the same arguments: `dotnet ./TXTextControl.Web.Server.Core.dll --listen-address <IP_ADDRESS> --port <PORT_NUMBER>`. The blog article [Configuring Web.Server.Core for TX Text Control Document Editor: Changing Ports and IP Versions](https://www.textcontrol.com/blog/2026/02/12/configuring-web-server-core-for-tx-text-control-document-editor-changing-ports-and-ip-versions/) walks through the settings.

### Host the Connection

A WebSocket connection passes every server, proxy and load balancer between the browser and your application, and each of them has to allow it. WebSocket traffic uses the same security layer as the HTTP or HTTPS connection it starts from, and firewalls must let it through, as [Browser Requirements](https://docs.textcontrol.com/tx-text-control-dotnet-server/installation/requirements/browsers/llms-full.txt) describes. A session belongs to one backend process, so behind a load balancer every connection of an editor must reach the same node; configure sticky sessions, as [Scaling and Load Balancing](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/architecture/#scaling-and-load-balancing) explains. The [Deployment Guide](https://docs.textcontrol.com/tx-text-control-dotnet-server/deployment/llms-full.txt) covers the complete setup of each platform.

#### IIS, nginx and Azure App Service

| Server | What the connection needs | Setup |
|---|---|---|
| IIS | The Windows feature **WebSocket Protocol**. On Windows desktop, select it under **Internet Information Services** > **World Wide Web Services** > **Application Development Features** in **Turn Windows features on or off**. IIS Express always has WebSockets enabled. A sub-application runs in a subfolder, so set `APIRoute` as described in [Host the Application in a Subfolder](#how-the-websocket-url-is-built). | [Prepare the Server](https://docs.textcontrol.com/tx-text-control-dotnet-server/deployment/windows/iis/#prepare-the-server) |
| nginx | The `Upgrade` and `Connection` headers of the request, passed on to the application: the WebSocket handshake is an HTTP request that asks to upgrade the connection, and nginx doesn't forward these hop-by-hop headers by default. Without them, the handshake fails and the editor doesn't load. | [Configure nginx](https://docs.textcontrol.com/tx-text-control-dotnet-server/deployment/linux/ubuntu/#configure-nginx) |
| Azure App Service | **Web sockets** switched on and **64 Bit** as the platform on Windows, and **Session affinity** on several instances | [Azure](https://docs.textcontrol.com/tx-text-control-dotnet-server/deployment/azure/llms-full.txt) |

#### YARP and Kubernetes

- **YARP.** The YARP reverse proxy forwards WebSocket connections by default. For several destinations, enable `SessionAffinity` in the configuration of the cluster, so that the connections of an editor stay on one destination.
- **Kubernetes.** Whether an ingress forwards WebSockets, how long it keeps idle connections open and how it keeps a client on one pod are settings of the ingress controller or Gateway API implementation you use. Check its documentation before you deploy the editor behind it.

#### Pass Tokens Without the URL

A token in the query string of the WebSocket URL ends up in the request logs of web servers and reverse proxies. [DocumentEditorSettings.webSocketProtocols](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/javascript/documenteditorsettings/websocketprotocols-property/llms-full.txt) sends it in the `Sec-WebSocket-Protocol` header of the handshake instead. [Access Tokens](https://docs.textcontrol.com/tx-text-control-dotnet-server/security/access-tokens/#pass-the-token-from-the-frontend) shows the frontend code, and [WebSocket Security](https://docs.textcontrol.com/tx-text-control-dotnet-server/security/websockets/#check-the-handshake) the middleware that checks the token.

### Diagnose Connection Problems

When an editor doesn't connect or loses its connection, follow the path above from the browser to the backend: the browser console, the close reason of the `webSocketClosed` event, the URL and route, the backend process and its port, proxies and firewalls, and the versions of the client and server packages. [WebSockets](https://docs.textcontrol.com/tx-text-control-dotnet-server/troubleshooting/websockets/llms-full.txt) in Troubleshooting goes through these checks and their fixes.

### Recognize Outdated Setups

Articles about earlier versions describe setups that the current packages replace: a Windows service as the backend up to version 32.0, backend files copied from the setup folder in 33.0, `app.UseMiddleware<WebSocketMiddleware>()` instead of `app.UseTXWebSocketMiddleware()` and port numbers of earlier versions. The address and port overloads of `UseTXWebSocketMiddleware` also replace hand-written middleware classes that create a `WebSocketHandler` themselves. [A Setup From an Older Article Doesn't Connect](https://docs.textcontrol.com/tx-text-control-dotnet-server/troubleshooting/websockets/#a-setup-from-an-older-article-doesn-t-connect) in Troubleshooting and [From the Old Document Editor Backend to the Backend Package](https://docs.textcontrol.com/tx-text-control-dotnet-server/installation/upgrading/previous-versions/#from-the-old-document-editor-backend-to-the-backend-package) describe how to update such an application.

### Next Steps

- [Document Editor Architecture](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/architecture/llms-full.txt): the parts of the editor and the configuration of the backend process.
- [WebSocket Security](https://docs.textcontrol.com/tx-text-control-dotnet-server/security/websockets/llms-full.txt): protect the WebSocket endpoint of your application.
- [ASP.NET Core](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/aspnet-core/llms-full.txt): host the editor, the viewer and document processing in one application.
- [Deployment Guide](https://docs.textcontrol.com/tx-text-control-dotnet-server/deployment/llms-full.txt): run the application on Windows, Linux, Docker and Azure.

---

## Nearby

- **Previous:** [REST Services](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/rest-services/llms-full.txt)
- **Next:** [ASP.NET on .NET Framework](https://docs.textcontrol.com/tx-text-control-dotnet-server/integration/dotnet-framework/llms-full.txt)
