Multiple Processes
An application with the Document Editor is more than one process. Next to your ASP.NET Core application runs the backend TXTextControl.Web.Server.Core, which synchronizes the editing sessions, and the backend starts a worker process for every editor that connects. In containers, you decide whether the backend shares the container of your application or runs in a container of its own.
Both layouts are described below: what runs where, how the application and the backend find each other and what happens when one of them stops. Applications without the Document Editor, which only process documents or use the Document Viewer, run as a single process and need none of this.
The Processes of an Application
dotnet MyApp.dll your application, the entry point of the container
|
+-- dotnet TXTextControl.Web.Server.Core.dll the backend, started by DocumentEditorWorkerManager
|
+-- dotnet TXTextControl.Web.Server.Core.Process.dll one worker process for each open editor
- Your application hosts the WebSocket middleware, which passes the traffic of each editor over TCP to the backend.
ServerTextControl,MailMergeand the Document Viewer work inside this process and start no other processes. - The backend listens on TCP port 4283 by default and starts the worker processes. It communicates with them over pipes, which need no ports.
- A worker process holds the document of one editing session in memory until your code saves it. It ends when the editor closes or when a lost connection isn't restored in time.
The hosted service DocumentEditorWorkerManager starts the backend when your application starts and stops the backend and all worker processes when the application shuts down. It doesn't restart a backend that ends on its own: the application keeps running, and editors can't connect until the application restarts.
Choose Where the Backend Runs
| In the application container | In its own container | |
|---|---|---|
| Setup | AddHostedService<DocumentEditorWorkerManager>(), nothing else | A second image, and the address of the backend in the application |
| Scaling | Application and backend scale together | The backend scales separately from the application |
| When a container stops | Application, backend and editing sessions stop together | The sessions in a stopped backend container end; the application keeps running |
| Use it for | Most applications | Deployments that separate real-time editing from the application, as Text Control recommends for many concurrent users |
Scaling Beyond One Instance describes Text Control's recommendation for larger deployments.
Run the Backend in the Application Container
The image from Dockerfile already runs the backend: the application starts it inside the container, and the backend's messages appear in the container output with the prefix [TXTextControl.Web.Server.Core].
Without configuration, the backend listens on all network interfaces of the container, and it accepts TCP connections from any client. Other containers in the same network could connect to port 4283. Bind the backend to the loopback address of the container instead, with the address and port in the DocumentEditorWorker configuration section and the same values in Program.cs, as Prepare the Application shows. In a container, environment variables set the section, with two underscores in place of the colon:
docker run 8080:8080 \
DocumentEditorWorker__ListenAddress=127.0.0.1 \
DocumentEditorWorker__Port=4283 \
myapp
The application log then shows "Listen address "127.0.0.1" provided via options." and the backend "Starting TCP listener on port 4283 for IPv4.".
Run the Backend in Its Own Container
A separate backend needs its own image, a listen address that other containers can reach and an application that connects to it by name.
Create the Backend Project
The backend image contains the backend files, the Core engine it uses and their native libraries and fonts. A class library that references the two packages publishes all of them. Create Backend/Backend.csproj next to the folder of your application:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TXTextControl.TextControl.Core.SDK" Version="34.0.5" />
<PackageReference Include="TXTextControl.Web.DocumentEditor.Backend" Version="34.5.0" />
</ItemGroup>
</Project>
The project needs no code. Use the same package versions as your application.
Build the Backend Image
Create Backend/Dockerfile. It follows the Dockerfile of the application and starts the backend instead:
# syntax=docker/dockerfile:1
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY Backend/Backend.csproj Backend/
RUN =type=secret,id=nugetconfig \
dotnet restore Backend/Backend.csproj linux-x64 /run/secrets/nugetconfig
COPY Backend/ Backend/
RUN dotnet publish Backend/Backend.csproj Release linux-x64 false \
/app/publish
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
WORKDIR /app
COPY =build /app/publish .
USER $APP_UID
EXPOSE 4283
ENTRYPOINT ["dotnet", "TXTextControl.Web.Server.Core.dll", "--listen-address", "0.0.0.0", "--port", "4283"]
--listen-address 0.0.0.0 makes the backend reachable from the application container, and --port sets its port. Build both images from the folder that contains nuget.config, the Dockerfile of the application and the two project folders:
docker buildx build id=nugetconfig,src=nuget.config myapp .
docker buildx build Backend/Dockerfile id=nugetconfig,src=nuget.config mybackend .
Connect the Application to the Backend
The application doesn't start a backend of its own. Remove AddHostedService<DocumentEditorWorkerManager>() from Program.cs and pass the name and port of the backend to the middleware:
using TXTextControl.Web;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();
var app = builder.Build();
app.UseRouting();
app.UseWebSockets();
// Connect the editors to the backend container
app.UseTXWebSocketMiddleware("backend", 4283);
app.UseAuthorization();
app.MapStaticAssets();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}")
.WithStaticAssets();
app.Run();
The code works as follows:
UseTXWebSocketMiddleware("backend", 4283)resolves the host namebackendto an address once, when the application starts, and connects every editor to this address and port.- If the name can't be resolved at that moment, the application doesn't start. The log shows a
SocketExceptionthrown in the constructor ofTXTextControl.Web.WebSocketMiddleware. - If the backend isn't running when an editor connects, the editor doesn't load, and the browser console shows "WebSocketHandler.DoTalkingAsync(): Could not connect to TextControl TCP server" with the address and port.
Start Both With Docker Compose
A compose.yaml file starts both containers in one network, in which each service is reachable by its name:
services:
app:
image: myapp
ports:
- "8080:8080"
depends_on:
- backend
backend:
image: mybackend
expose:
- "4283"
restart: unless-stopped
The file works as follows:
backendis the service name that the application passes toUseTXWebSocketMiddleware.exposemakes port 4283 reachable for the other services without publishing it on the host. Don't add aportsentry for the backend.depends_onstarts the backend before the application, so its name exists when the application resolves it.restart: unless-stoppedrestarts the backend container when the backend process ends. Editing sessions of the ended process are lost.
A backend container that is recreated can get a new address. Because the application resolved the name at startup, restart the application container as well.
When an editor connects, the output of the backend container shows "Starting web editor worker process with connection ID" followed by the ID of the connection.
Next Steps
- Scaling Containers: run several application and backend containers.
- Kubernetes: run the containers in a cluster.
- Document Editor Architecture: the backend, its configuration file and the life of an editing session.
- WebSocket Architecture: the connection between the browser, the middleware and the backend.