When deploying the TX Text Control document editor in an ASP.NET Core (.NET 5) Web Application, the synchronization service can be deployed separately to one or more Windows VMs. This can be useful when separating the web application from the Text Control stack for deployment reasons (for example when deploying your web application to Azure App Services running on Linux) or when load balancing the TCP synchronization service separately.

In order to do this, a custom WebSocketMiddleware can be implemented.

What is a WebSocketMiddleware?

TX Text Control uses a WebSocket connection from the browser to an application layer that handles the traffic between the client and the TCP synchronization service. The WebSocketHandler (part of TX Text Control), routes the synchronization calls from and to the TCP service. The WebSocketMiddleware is creating an instance of the WebSocketHandler.

WebSocketHandler

The ASP.NET (Core) MVC NuGet package that is required to implement the document editor comes with a standard, out-of-the-box WebSocketMiddleware that connects to a TCP service that runs on the same machine.

The following class CustomWebSocketMiddleware implements a custom WebSocketMiddleware:

using Microsoft.AspNetCore.Http;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using TXTextControl.Web;
public class CustomWebSocketMiddleware {
private RequestDelegate m_next;
private IPAddress m_serviceAddress;
private int m_servicePort;
// list of available backend TCP services
internal readonly List<byte[]> DefaultServiceAddress =
new List<byte[]>() {
new byte[] { 127, 0, 0, 2 },
new byte[] { 127, 0, 0, 1 }
};
internal const int DefaultServicePort = 4278;
public CustomWebSocketMiddleware(RequestDelegate next) {
m_next = next;
m_serviceAddress = new IPAddress(DefaultServiceAddress[0]);
m_servicePort = DefaultServicePort;
}
public async Task Invoke(HttpContext context) {
if (context.WebSockets.IsWebSocketRequest &&
context.WebSockets.WebSocketRequestedProtocols.Contains("TXTextControl.Web")) {
// server id from query string (provided in view code)
int serverId = int.Parse(context.Request.Query["server"]);
// create a new WebSocketHandler with random server
var ws = new WebSocketHandler(
new IPAddress(DefaultServiceAddress[serverId]),
m_servicePort);
await ws.Invoke(context);
}
else if (m_next != null) {
await m_next.Invoke(context);
}
}
}
view raw custom.cs hosted with ❤ by GitHub

As described in a previous blog article, the custom WebSocketMiddleware must be added to the Startup.cs:

app.UseMiddleware<CustomWebSocketMiddleware>();
view raw test.cs hosted with ❤ by GitHub

In the view code, an additional query string is provided to the WebSocketURL property:

@using TXTextControl.Web.MVC
@{
Random rand = new Random();
string serverId = rand.Next(0, 2).ToString();
// build WebSocketURL including a server id in query string
var sProtocol = (Context.Request.IsHttps) ? "wss://" : "ws://";
var sWebSocketURL = sProtocol + Context.Request.Host
+ "/TXWebSocket?server=" + serverId;
}
@Html.TXTextControl().TextControl(settings => {
settings.WebSocketURL = sWebSocketURL;
}).Render()
view raw index.cshtml hosted with ❤ by GitHub

This workflow is just a simplified idea of what can be done using a custom WebSocketMiddleware. A random value (1 or 0) is passed as a query string to the WebSocketMiddleware. In the WebSocketMiddleware itself, a specific IP is used from a list of IP addresses based on the given random index value from the query string.

// server id from query string (provided in view code)
int serverId = int.Parse(context.Request.Query["server"]);
// create a new WebSocketHandler with random server
var ws = new WebSocketHandler(
new IPAddress(DefaultServiceAddress[serverId]),
m_servicePort);
view raw test.cs hosted with ❤ by GitHub

Following this simple workflow, a random load balancer has been implemented that routes the TCP traffic to 2 different servers.

Pro tip

A custom WebSocketMiddleware can be also used to create an instance of the WebSocketHandler without passing a value from the view code. Typically, when changing the IP address of the TCP backend or when providing this IP dynamically.