ASP.NET - ASP.NET Core WebSockets

WebSockets are a communication protocol that allows a client and server to maintain a persistent, two-way communication channel over a single connection. Unlike traditional HTTP communication, where the client generally sends a request and waits for a response, WebSockets allow both sides to send messages whenever necessary after the connection has been established. This makes WebSockets particularly useful for applications that require real-time communication, such as live dashboards, multiplayer games, collaborative applications, monitoring systems, and chat applications. 

1. Understanding WebSockets

Traditional web applications generally work using the request-response model. A browser sends an HTTP request to the server, the server processes it, and then returns an HTTP response. Once the response is delivered, that particular communication cycle is finished.

For applications that need continuously changing information, repeatedly making HTTP requests can introduce unnecessary overhead. For example, a stock-price dashboard might need to repeatedly ask the server whether a price has changed.

WebSockets provide a different approach. Once the connection is established, it remains open, allowing the server to send information to the client immediately when new information becomes available.

The communication can therefore be represented as:

Client                         Server
  |                              |
  |------ WebSocket Request ---->|
  |                              |
  |<----- Connection Accepted ---|
  |                              |
  |------ Message -------------->|
  |<----- Message ---------------|
  |<----- Message ---------------|
  |------ Message -------------->|
  |                              |
  |--------- Close ------------->|

The important feature is that communication can happen in both directions without requiring a new HTTP request for every message.

2. WebSocket Connection Establishment

A WebSocket connection begins with an HTTP request. The client asks the server to upgrade the connection to WebSocket communication.

In ASP.NET Core, the application first enables WebSocket middleware:

app.UseWebSockets();

ASP.NET Core then provides access to WebSocket functionality through HttpContext.WebSockets. The application can determine whether the incoming request is a WebSocket request using:

context.WebSockets.IsWebSocketRequest

If it is a valid WebSocket request, the application can accept it using:

await context.WebSockets.AcceptWebSocketAsync();

Microsoft's current ASP.NET Core documentation describes this as the standard process for accepting WebSocket requests. 

3. Configuring WebSocket Middleware

WebSocket behavior can be customized through WebSocketOptions.

For example:

var webSocketOptions = new WebSocketOptions
{
    KeepAliveInterval = TimeSpan.FromMinutes(2)
};

app.UseWebSockets(webSocketOptions);

KeepAliveInterval determines how frequently the server sends ping frames to help ensure that connections remain active through proxies and other network infrastructure.

ASP.NET Core also supports options such as KeepAliveTimeout and AllowedOrigins. The latter can be used to restrict which origins are permitted to establish WebSocket connections. 

4. Accepting a WebSocket Request

A simple ASP.NET Core implementation can look like this:

app.Use(async (context, next) =>
{
    if (context.Request.Path == "/ws")
    {
        if (context.WebSockets.IsWebSocketRequest)
        {
            using var webSocket =
                await context.WebSockets.AcceptWebSocketAsync();

            await HandleWebSocket(webSocket);
        }
        else
        {
            context.Response.StatusCode =
                StatusCodes.Status400BadRequest;
        }
    }
    else
    {
        await next(context);
    }
});

Here, /ws is used as the WebSocket endpoint.

The application first checks the requested path. It then determines whether the request is actually a WebSocket request. If so, it accepts the connection and passes the resulting WebSocket object to a method responsible for handling communication.

5. Sending Messages

Once a connection has been established, the server can send information to the client using SendAsync.

For example:

var message = "Hello from ASP.NET Core";

var bytes = Encoding.UTF8.GetBytes(message);

await webSocket.SendAsync(
    new ArraySegment<byte>(bytes),
    WebSocketMessageType.Text,
    true,
    CancellationToken.None);

The message is converted into bytes before being sent.

The WebSocketMessageType.Text value indicates that the message contains text. WebSockets can also transmit binary information.

The true parameter indicates that this is the final part of the message.

6. Receiving Messages

The server can receive information from the client using ReceiveAsync.

For example:

var buffer = new byte[1024];

var result = await webSocket.ReceiveAsync(
    new ArraySegment<byte>(buffer),
    CancellationToken.None);

The received data can then be converted back into text:

var message = Encoding.UTF8.GetString(
    buffer, 0, result.Count);

A typical WebSocket server continuously receives messages while the connection remains open.

For example:

while (webSocket.State == WebSocketState.Open)
{
    var result = await webSocket.ReceiveAsync(
        new ArraySegment<byte>(buffer),
        CancellationToken.None);

    if (result.MessageType == WebSocketMessageType.Close)
    {
        await webSocket.CloseAsync(
            WebSocketCloseStatus.NormalClosure,
            "Connection closed",
            CancellationToken.None);

        break;
    }

    var message = Encoding.UTF8.GetString(
        buffer, 0, result.Count);

    Console.WriteLine(message);
}

This creates a basic server-side communication loop.

7. Echo WebSocket Example

A common example used to understand WebSockets is an echo server.

The client sends:

Hello Server

The server receives the message and immediately sends it back:

Hello Server

A simplified implementation could be:

async Task Echo(WebSocket webSocket)
{
    var buffer = new byte[1024];

    while (webSocket.State == WebSocketState.Open)
    {
        var result = await webSocket.ReceiveAsync(
            new ArraySegment<byte>(buffer),
            CancellationToken.None);

        if (result.MessageType == WebSocketMessageType.Close)
        {
            await webSocket.CloseAsync(
                WebSocketCloseStatus.NormalClosure,
                "Closing",
                CancellationToken.None);

            break;
        }

        await webSocket.SendAsync(
            new ArraySegment<byte>(
                buffer, 0, result.Count),
            result.MessageType,
            result.EndOfMessage,
            CancellationToken.None);
    }
}

This basic pattern demonstrates the fundamental WebSocket workflow: receive, process, and send.

8. Handling Connection Closure

A WebSocket connection should be closed properly rather than simply abandoning the connection.

When a client requests closure, the server can respond with:

await webSocket.CloseAsync(
    WebSocketCloseStatus.NormalClosure,
    "Connection closed",
    CancellationToken.None);

Applications should also account for unexpected client disconnections.

For example, a user's network connection may suddenly disappear, their browser may close, or their device may go offline.

ASP.NET Core provides WebSocket mechanisms for detecting these situations. Keep-alive configuration can also help detect connections that have become unresponsive. 

9. Keeping the Request Pipeline Alive

One important aspect of ASP.NET Core WebSockets is that the request pipeline must remain active for the duration of the WebSocket connection.

If an application accepts a WebSocket and immediately finishes the request-processing method, later attempts to use the connection can fail.

Microsoft specifically warns that returning from the middleware or controller action before WebSocket processing is complete can result in exceptions because the underlying HTTP response may already have completed. 

Therefore, asynchronous programming is particularly important when working with WebSockets.

Applications should use:

await

rather than blocking operations such as:

Task.Wait()

or:

Task.Result

Blocking operations can cause threading and responsiveness problems.

10. WebSockets and HTTP/2

Modern ASP.NET Core also supports WebSockets over HTTP/2.

HTTP/2 WebSockets can take advantage of HTTP/2 capabilities such as header compression and multiplexing. ASP.NET Core's current documentation notes that HTTP/2 WebSockets use the CONNECT method rather than the traditional GET request used with HTTP/1.1 WebSocket connections.

This is important when designing applications that need to operate across newer HTTP infrastructure.

11. WebSocket Security

Security is an important consideration when implementing WebSockets.

One important point is that traditional CORS protections do not apply to WebSocket connections in the same way they apply to ordinary HTTP requests. Browsers send an Origin header with WebSocket requests, and ASP.NET Core can use AllowedOrigins to restrict accepted origins. 

For example:

var options = new WebSocketOptions();

options.AllowedOrigins.Add(
    "https://example.com");

app.UseWebSockets(options);

However, the Origin header should not be treated as an authentication mechanism because it can be manipulated by clients outside the browser security model.

Authentication and authorization should therefore be handled separately.

Other security considerations include:

  • Use HTTPS and secure WebSockets in production.

  • Validate incoming messages.

  • Limit message sizes where appropriate.

  • Authenticate clients before allowing access to sensitive operations.

  • Avoid exposing unnecessary WebSocket endpoints.

  • Carefully manage connection lifetime.

  • Protect sensitive information transmitted through the connection.

12. WebSockets Versus SignalR

WebSockets and SignalR are related but are not the same thing.

WebSockets provide the underlying communication mechanism for persistent, two-way communication.

SignalR is a higher-level framework that simplifies real-time application development. SignalR can use WebSockets when available and provides transport fallback when WebSockets cannot be used. It also provides a higher-level programming model for communicating between clients and servers. 

Raw WebSockets may be appropriate when an application requires direct control over the communication protocol or when implementing a specialized communication system.

SignalR is often more convenient for general-purpose real-time applications because it handles many communication details for the developer.

13. Common Applications of WebSockets

WebSockets are useful when information needs to move between the client and server with minimal delay.

Common examples include:

Live Dashboards

A monitoring dashboard can receive updated server information immediately instead of repeatedly requesting the latest values.

Multiplayer Games

Game clients can exchange player movements, actions, and game-state information continuously.

Chat Applications

Messages can be delivered immediately to connected users without requiring the browser to repeatedly refresh or poll the server.

Financial Applications

Applications displaying rapidly changing market information can use persistent connections to deliver updates.

Collaborative Applications

Multiple users editing the same document can receive updates about changes made by other users.

Monitoring Systems

Servers, machines, or devices can continuously send status information to a monitoring application.

14. Advantages of WebSockets

The main advantages include:

Persistent communication: The connection remains open rather than requiring a new connection for every message.

Two-way communication: Both client and server can initiate communication.

Low communication overhead: Once the connection has been established, messages can be exchanged without repeatedly creating ordinary HTTP request-response cycles.

Real-time behavior: Information can be delivered as soon as it becomes available.

Suitable for frequent updates: Applications generating many small updates can benefit from persistent communication.

15. Limitations of WebSockets

WebSockets are not automatically the best choice for every application.

A persistent connection consumes server and network resources. Applications with thousands or millions of simultaneous connections need careful resource management.

WebSocket infrastructure can also require additional configuration when applications are deployed behind proxies, load balancers, or web servers.

Another consideration is application complexity. With raw WebSockets, developers are responsible for designing message formats, handling connection states, dealing with disconnections, and implementing application-level communication patterns.

For many general-purpose real-time applications, Microsoft recommends considering SignalR instead of directly implementing raw WebSockets because SignalR provides additional functionality and transport fallback.

16. WebSockets in ASP.NET Core: Overall Flow

The complete process can be summarized as follows:

Client
   |
   | WebSocket request
   v
ASP.NET Core
   |
   | UseWebSockets()
   v
Check WebSocket request
   |
   | AcceptWebSocketAsync()
   v
Persistent WebSocket Connection
   |
   +---- Receive message
   |
   +---- Process message
   |
   +---- Send response
   |
   +---- Continue communication
   |
   v
Close connection

The essential ASP.NET Core APIs involved are:

UseWebSockets()
        |
        v
IsWebSocketRequest
        |
        v
AcceptWebSocketAsync()
        |
        v
ReceiveAsync()
        |
        v
Process data
        |
        v
SendAsync()
        |
        v
CloseAsync()

17. Conclusion

ASP.NET Core WebSockets provide a direct way to create persistent, two-way communication between web clients and servers. Instead of repeatedly creating HTTP requests to check for new information, an application can establish a connection and exchange messages continuously.

The core implementation involves enabling WebSocket middleware, identifying WebSocket requests, accepting connections, receiving and sending messages asynchronously, handling disconnections, and closing connections correctly. ASP.NET Core also provides configuration options for keep-alive behavior and allowed origins, while modern versions support WebSockets over HTTP/2. 

WebSockets are especially valuable for applications where timely communication is essential. However, developers should consider connection management, security, scalability, and application complexity before choosing raw WebSockets. For many standard real-time scenarios, SignalR provides a higher-level alternative while still using WebSockets when appropriate.