Skip to main content
Corrected the constructor
Source Link
Bercovici Adrian
  • 9.2k
  • 18
  • 81
  • 171
public class SocketWare {
        private RequestDelegate next;
        public SocketWare(RequestDelegate _next) {
            this.next = _next;
        }
        public async Task Invoke(HttpContext context) {
            if (!context.WebSockets.IsWebSocketRequest) {
                return;
            }
            var socket=await context.WebSockets.AcceptWebSocketAsync();
            await RunAsync(socket);
        }
        private async Task RunAsync(WebSocket socket) {
            try {
                var client = new ChatClient(socket, this.store,this.channelRegistry);
                await client.RunAsync();
            } catch (Exception ex) {

                throw;
            }
            
        }
        

    }
public class SocketWare {
        private RequestDelegate next;
        public SocketWare(RequestDelegate _next) {
            this.next = _next;
        }
        public async Task Invoke(HttpContext context) {
            if (!context.WebSockets.IsWebSocketRequest) {
                return;
            }
            var socket=await context.WebSockets.AcceptWebSocketAsync();
            await RunAsync(socket);
        }
        private async Task RunAsync(WebSocket socket) {
            try {
                var client = new ChatClient(socket, this.store,this.channelRegistry);
                await client.RunAsync();
            } catch (Exception ex) {

                throw;
            }
            
        }
        

    }
public class SocketWare {
        private RequestDelegate next;
        public SocketWare(RequestDelegate _next) {
            this.next = _next;
        }
        public async Task Invoke(HttpContext context) {
            if (!context.WebSockets.IsWebSocketRequest) {
                return;
            }
            var socket=await context.WebSockets.AcceptWebSocketAsync();
            await RunAsync(socket);
        }
        private async Task RunAsync(WebSocket socket) {
            try {
                var client = new ChatClient(socket);
                await client.RunAsync();
            } catch (Exception ex) {

                throw;
            }
            
        }
        

    }
Source Link
Bercovici Adrian
  • 9.2k
  • 18
  • 81
  • 171

The only thing you need in your Startup is to add the UseWebsockets middleware. Then you can define your own middleware and filter connections if they are websocket type like below:

Startup

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
            app.UseWebSockets();
            app.UseMiddleware<SocketWare>();
        }

Middleware

public class SocketWare {
        private RequestDelegate next;
        public SocketWare(RequestDelegate _next) {
            this.next = _next;
        }
        public async Task Invoke(HttpContext context) {
            if (!context.WebSockets.IsWebSocketRequest) {
                return;
            }
            var socket=await context.WebSockets.AcceptWebSocketAsync();
            await RunAsync(socket);
        }
        private async Task RunAsync(WebSocket socket) {
            try {
                var client = new ChatClient(socket, this.store,this.channelRegistry);
                await client.RunAsync();
            } catch (Exception ex) {

                throw;
            }
            
        }
        

    }

In my middleware i prefer to keep my business logic in a separate class that gets the Websocket injected in it like below:

Client

public class ChatClient
{
   private Task writeTask;
   private Task readTask;
   private WebSocket socket;
   private CancellationTokenSource cts=new CancellationTokenSource();
   ChatClient(WebSocket socket)
   {
       this.socket=socket;
   }
   public async Task RunAsync()
   {
      this.readTask=Task.Run(async ()=>await ReadLoopAsync(cts.Token),cts.Token);
      this.writeTask=Task.Run(async()=>await WriteLoopAsync(cts.Token),cts.Token);
      await Task.WhenAny(this.readTask,this.writeTask);
   }
   public async Task WriteLoopAsync()
   {
       Memory<byte> buffer=ArrayPool<byte>.Shared.Rent(1024);
       try {
           while (true) {
              var result= await this.socket.ReceiveAsync(buffer,....);
              var usefulBuffer=buffer.Slice(0,result.Count).ToArray();
              var raw=Encoding.Utf8.GetString(usefulBuffer);
              //deserialize it to whatever you need
              //handle message as you please (store it somwhere whatever)
            }
        } catch (Exception ex) {

               //socket error handling
               //break loop or continue with go to
        }
   }
   public async Task ReadLoopAsync()
   {
          try {
            while (true) {
              
                var data = await this.[someMessageProvider].GetMessageAsync() //read below !!!
                var bytes = Encoding.UTF8.GetBytes(data);
                //send the message on the websocket
                await this.socket.SendAsync(data, WebSocketMessageType.Text, true, CancellationToken.None);
            }
        } catch (Exception ex) {

            //do incorrect message/socket disconnect logic
        }
   }
}

Now regarding producing messages and consuming them. In your case you could define your producers as some Controller routes like below .You would hit a route , produce a message and publish it to some message broker. I would use a Message Queue (RabbitMQ) or even a Redis Pub/Sub as a message bus. You would publish messages from your route(s) and then consume them in your ReadLoopAsync method from WebSocketClient (look above).

Producing messages

public UpdateController:Controller
{
   private IConnection
   [HttpPost]
   [someroute]
   public void UpdateScoreboard(string someMessage)
   {
       this.connection.Publish("someChannel",someMessage);
   }
   [HttpPost]
   [someotherroute]
   public void DeletePlayer(string someOtherMessage)
   {
       this.connection.Publish("someChannel",someMessage);
   }
}
  • Redis pub/sub
    Check redis pub/sub here
    Also check my repository on github here in which i am using exactly what you need (websockets, redis,pub sub)

  • RabbitMq
    Another option as a message bus is to use RabbitMQ , for more info regarding C# API here

  • In Memory

    You could also avoid using a third party and use some in memory data structure like a BlockingCollection.You could inject it as a Singleton service both in your Controller(s) and your socket Middleware(s)