ASP.NET Core WebSockets Implementation: A Deep Dive into the Middleware and Transport Layers

ASP.NET Core WebSockets are implemented as a middleware component in WebSocketMiddleware.cs that validates the handshake and exposes IHttpWebSocketFeature, while the actual network I/O is handled by server-specific engines like WebSocketsAsyncIOEngine.cs in the transport layer.

The WebSocket implementation in the dotnet/aspnetcore repository follows a layered architecture that separates protocol negotiation from low-level socket operations. This design allows applications to enable WebSocket support with a single middleware registration while relying on server-specific transports like Kestrel or IIS to handle the actual frame I/O. Understanding this architecture helps developers troubleshoot connection issues and optimize their real-time communication pipelines.

Architecture Overview

The implementation spans three distinct layers that work together to upgrade HTTP connections to WebSocket connections. First, the middleware layer detects valid WebSocket requests and manages the handshake negotiation. Second, the feature layer exposes configuration options and the IHttpWebSocketFeature interface that application code interacts with. Third, the server transport layer performs the actual protocol upgrade and manages the raw socket I/O for reading and writing WebSocket frames.

The Middleware Layer

WebSocketMiddleware and Handshake Validation

The entry point for WebSocket support is the WebSocketMiddleware class located in src/Middleware/WebSockets/src/WebSocketMiddleware.cs. This middleware intercepts incoming HTTP requests and checks for the required upgrade headers. When it detects a valid WebSocket handshake request, it performs critical validation steps including origin checking, header validation, and protocol negotiation.

The middleware creates a WebSocketHandshake object that encapsulates the negotiation state. It checks for the presence of either IHttpUpgradeFeature (for HTTP/1.1) or IHttpExtendedConnectFeature (for HTTP/2 extended CONNECT) on the HttpContext.Features collection. If the request meets all requirements, the middleware injects an IHttpWebSocketFeature into the HttpContext, making the WebSocket functionality available to downstream components.

Request Pipeline Integration

During the upgrade process, the middleware disables request timeouts to accommodate long-lived WebSocket connections. It then delegates the actual transport creation to the server-specific implementation before returning a fully functional System.Net.WebSockets.WebSocket instance to the application code. This abstraction allows the same middleware to work across different server implementations without code changes.

Configuration and Features

WebSocketOptions

Configuration is centralized in the WebSocketOptions class found in src/Middleware/WebSockets/src/WebSocketOptions.cs. This class exposes settings for:

  • Keep-alive intervals – Controls how often the server sends ping frames to maintain the connection
  • Allowed origins – A whitelist of origins permitted to connect, preventing unauthorized cross-origin WebSocket requests
  • Compression settings – Enables per-message deflate compression negotiation

IHttpWebSocketFeature Interface

The IHttpWebSocketFeature interface is the contract that application code uses to interact with WebSocket functionality. When you call context.WebSockets.AcceptWebSocketAsync(), you are invoking the implementation provided by this feature. The feature exposes two critical properties: IsWebSocketRequest (boolean indicating if the current request is a valid WebSocket upgrade) and AcceptAsync (the method that performs the actual handshake and returns a WebSocket instance).

Server Transport Layer

WebSocketsAsyncIOEngine

The actual network operations happen in WebSocketsAsyncIOEngine, located in src/Servers/IIS/IIS/src/Core/IO/WebSocketsAsyncIOEngine.cs. Despite the IIS path in its location, this engine is used by both Kestrel and IIS servers to handle low-level WebSocket frame I/O.

This engine reads and writes raw WebSocket frames on the underlying network stream. It manages the opaque transport that the middleware wraps using System.Net.WebSockets.WebSocket.CreateFromStream(). The engine handles the complexities of frame parsing, masking, and payload delivery without exposing these details to application code.

Compression Support

For applications requiring compression, the middleware negotiates per-message deflate support using constants defined in src/Middleware/WebSockets/src/WebSocketDeflateConstants.cs. The HandshakeHelpers class manages the negotiation of compression parameters during the initial handshake, allowing the transport layer to compress payloads transparently.

Enabling WebSockets in Your Application

To add WebSocket support to your application, you must register the middleware and configure options in your Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Configure services
builder.Services.AddWebSockets(options =>
{
    options.KeepAliveInterval = TimeSpan.FromSeconds(30);
    options.AllowedOrigins.Add("https://example.com");
});

var app = builder.Build();

// Add middleware to pipeline
app.UseWebSockets();

// Map WebSocket endpoint
app.Map("/ws", async context =>
{
    if (!context.WebSockets.IsWebSocketRequest)
    {
        context.Response.StatusCode = 400;
        return;
    }

    var webSocket = await context.WebSockets.AcceptWebSocketAsync();
    var buffer = new byte[4 * 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);
        }
        else
        {
            await webSocket.SendAsync(
                new ArraySegment<byte>(buffer, 0, result.Count),
                result.MessageType, 
                result.EndOfMessage, 
                CancellationToken.None);
        }
    }
});

For low-level access to the feature directly (bypassing the convenience HttpContext.WebSockets property):

public async Task Invoke(HttpContext context)
{
    var wsFeature = context.Features.Get<IHttpWebSocketFeature>();
    if (wsFeature?.IsWebSocketRequest == true)
    {
        var ws = await wsFeature.AcceptAsync(new WebSocketAcceptContext());
        // ws is a System.Net.WebSockets.WebSocket ready for I/O
    }
}

Summary

  • ASP.NET Core WebSockets implementation is split between middleware (WebSocketMiddleware.cs) for handshake validation and server-specific transport engines (WebSocketsAsyncIOEngine.cs) for frame I/O.
  • The middleware injects IHttpWebSocketFeature into the HttpContext.Features collection, which provides the AcceptWebSocketAsync() method used by application code.
  • Configuration is managed through WebSocketOptions, supporting keep-alive intervals, origin validation, and compression settings.
  • The transport layer is shared between Kestrel and IIS, using the WebSocketsAsyncIOEngine class to handle raw socket operations.
  • Extension methods in WebSocketMiddlewareExtensions.cs provide the UseWebSockets() and AddWebSockets() registration patterns.

Frequently Asked Questions

Where is WebSocketMiddleware defined in the ASP.NET Core source code?

WebSocketMiddleware is defined in src/Middleware/WebSockets/src/WebSocketMiddleware.cs within the dotnet/aspnetcore repository. This class handles the detection of WebSocket upgrade requests, validates the handshake headers, and creates the WebSocketHandshake object that manages the connection upgrade process.

How does ASP.NET Core handle WebSocket compression?

Compression support is implemented through WebSocketDeflateConstants.cs in the middleware layer, which defines the compression algorithm constants. During the handshake, the WebSocketMiddleware negotiates per-message deflate compression if enabled in WebSocketOptions. The actual compression and decompression happen at the transport layer, transparent to application code.

Can ASP.NET Core WebSockets work with HTTP/2?

Yes, the implementation supports HTTP/2 extended CONNECT requests through the IHttpExtendedConnectFeature interface. When running on HTTP/2, the middleware checks for this feature instead of the traditional IHttpUpgradeFeature used in HTTP/1.1, allowing WebSocket connections to benefit from HTTP/2 multiplexing and header compression where supported.

What is the difference between the WebSocket middleware and the transport layer?

The middleware layer (WebSocketMiddleware.cs) handles protocol negotiation, origin validation, and the public API surface exposed via HttpContext.WebSockets. The transport layer (WebSocketsAsyncIOEngine.cs) handles the actual binary framing, network reads/writes, and connection management. This separation allows the same middleware to work across different server implementations (Kestrel and IIS) while each server provides its own optimized transport mechanism.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →