3

I am trying to implement live notifications in my web application. Only the users which are administrators in my web app should see the notifications.
So I setup the web socket in my startup.cs file which I think is not the right way

Startup.cs

var webSocketOptions = new WebSocketOptions()
{
    KeepAliveInterval = TimeSpan.FromSeconds(120),
    ReceiveBufferSize = 4 * 1024
};
app.UseWebSockets(webSocketOptions);
app.Use(async (context, next) =>
    {
         if (context.Request.Path == "/ws")
         {
             if (context.WebSockets.IsWebSocketRequest)
             {
                  WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
                        
             }
             else
             {
                 context.Response.StatusCode = 400;
             }
         }
         else
         {
            await next();
         }
   });

and this is my Javascript

window.onload = () => {
    if (/*User is Admin*/) {

        //Establish Websocket
        var socket = new WebSocket("wss:localhost:44301/ws");

        console.log(socket.readyState);

        socket.onerror = function (error) {
            console.log('WebSocket Error: ' + error);
        };

        socket.onopen = function (event) {          
            console.log("Socket connection opened")
        };

        // Handle messages sent by the server.
        socket.onmessage = function (event) {
            var data = event.data;
            console.log(data);
            //Draw some beautiful HTML notification
        };
    }
}

now this all works, but I don't know how to send messages from my server controllers, something like this

[HttpGet]
public async Task<IActionResult> Foo(WebSocket webSocket)
{
    //What I am trying to do is send message from the open web socket connection.
    var buffer = new byte[1024 * 4];
    buffer = Encoding.UTF8.GetBytes("Foo");

    await webSocket.SendAsync(new ArraySegment<byte>(buffer),WebSocketMessageType.Text,true,CancellationToken.None);
    return View()
}

I don't know how to approach this. What I wanna do is if the user is admin, open web socket and send some data from the other users actions, (which means writing messages from that opened web socket from some of my controllers)

1

3 Answers 3

0

I had similar problem, with messaging system. One way to this in .NET CORE is to use SIGNALR, so you must create for every user that you want to communicate connection. In your case every user have connection with admin. You should on back-end create class for storing this connections in hash table, and one class that implements method for sending. In Startup.cs i have only this:

services.AddSignalR();
services.AddSingleton<ChatHub>();
services.AddAuthentication(options =>
            {
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
                   .AddJwtBearer(options =>
                   {
                       options.TokenValidationParameters = new TokenValidationParameters
                       {
                           ValidateIssuerSigningKey = true,
                           IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration.GetSection("AppSettings:Secret").Value)),
                           ValidateIssuer = false,
                           ValidateAudience = false
                       };
                       options.Events = new JwtBearerEvents
                       {
                           OnMessageReceived = context =>
                           {
                               var accessToken = context.Request.Query["access_token"];

                               // If the request is for our hub...
                               var path = context.HttpContext.Request.Path;
                               if (!string.IsNullOrEmpty(accessToken) &&
                                   (path.StartsWithSegments("/chat")))
                               {
                                   // Read the token out of the query string
                                   context.Token = accessToken;
                               }
                               return Task.CompletedTask;
                           }
                       };
                   });

ChatHub is class that Extends Hub with method send and overrided methods OnConnectedAsync and OnDisconnectedAsync used to add or remove connection from hash. You make request on front when you want to send message. If you want to this from your controller (if you use JWT this is not neccessary) you just need to inject ChatHub in your controllers if you want and to call send with message.

In ChatHub this is important line

await Clients.Client(connectionId).SendAsync("recievedMessage", messageToSend);

On front I used angular, so code is different, so please visit this link: c# SignalR.

0

SignalR is your friend: https://learn.microsoft.com/en-us/aspnet/core/tutorials/signalr?view=aspnetcore-3.1

Your client would be something like this:

const connection = new signalR.HubConnectionBuilder()
    .withUrl("/MyHub")
    .configureLogging(signalR.LogLevel.Information)
    .build();

connection.start()
    .then(function() {
    });

connection.on("UpdateText",
    (data) => {
        if (!data) {
            console.log("No Update Rerading Data");
            return;
        }
        // Do whatever needs to be done
    });

Your Hub:

public sealed class MyHub : Hub
{
    public MyHub()
    { }
}

Then you would create a signalR provider that you could call from anywhere in your system. For example:

await _signalRProvider
    .BroadcastAsync("MyHub", "UpdateText", "AdminsGroup", new { text = "Whatssaaaap!" })
    .ConfigureAwait(false);

All what it would is use HttpClient to post json type payload to signalR endpoint for a specific group (or use if you prefer).

$"{_endpoint}/api/v1/hubs/{hubName}/groups/{groupName}"

Of course you would need to register hub etc. I skipped that part as it is all documented in the link above.

As an extra: If you are using Microsoft Azure this becomes even simpler as you can use SignalR serverless approach: https://learn.microsoft.com/en-us/azure/azure-signalr/signalr-quickstart-azure-functions-javascript

0

To be able to send from a controller to the connected web sockets you'd have to be able to access these connections, as an object that holds these connections.

This answer shows how to handle a single connection with a separate class and to keep the connection alive (await client.RunAsync();).

After doing this, in your case, you'd probably create another class, ConnectionContainer. It could have just a ConcurrentDictionary to hold all incoming connections. Then you'd have to make this class available to a controller. It can be done with dependency injection. In Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(new ConnectionContainer());
    ...

}

And in the controller's ctor add ConnectionContainer as a parameter and save it as a private field:

 public class BroadcastController
 {
     private readonly ConnectionContainer _connectionContainer;

     public BroadcastController(ConnectionContainer connectionContainer)
     {
         _connectionContainer = connectionContainer;
     }
 }

Your Foo method could either use the connections directly, or you could implement a broadcast method in ConnectionContainer (you may have reuse for that, and it'd be nicely encapsulated).

Btw, if you'd like to use websockets for json/text messages with asp.net core, I agree with the other answers - signalR will be easier to use (less code). If you'd like full control over the websocket and to have it fully optimized to your needs, your code would be better, as you could transfer binary data directly to the web client (which isn't supported by signalR). The binary data can be used in the client with a DataView.

Not the answer you're looking for? Browse other questions tagged or ask your own question.