Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Signalr - It's possible to wait reponse from client?

Tags:

c#

signalr

I am a beginner in using Signalr and am checking out some examples.

Is it possible to send a message to the client from the server and wait for a return from it? Or is it possible to guarantee that after the answer the same session will be used?

My question is because in a given process, within a transaction, I need to ask the user if he wants to continue with the changes. I have not been able to ask this question before because validations should be done in the same session where changes have been made (but not yet confirmed).

like image 902
Carlos Henrique Biazin Esteves Avatar asked Jan 16 '18 19:01

Carlos Henrique Biazin Esteves


2 Answers

Reiterating the comment from Jaime Yule, WebSockets are bidirectional communication and do not follow the Request/Response architecture for messaging. Given the very fluid nature of communication around WebSockets, these bullet points are good to keep in mind for your current (& future) scenarios:

  • SignalR is great if you're going to use it for fire & forget (Display a pop-up to a user and that's it)
  • It's not designed around request-response like you're asking, and trying to use it as such is an anti-pattern
  • Messages may be sent from either end of the connection at any time, and there is no native support for one message to indicate it is related to another
  • This makes the protocol poorly suited for transactional requirements
like image 166
Mark C. Avatar answered Oct 19 '22 07:10

Mark C.


It is possible, but i would not recommend (relying on) it.
And it's not a pretty solution (using a static event and being pretty complex for such a simple thing).

Story goes like this:

Make sure client and server know the connectionId - They probably know that already, but i could not figure out a way to access it.

Await NotificationService.ConfirmAsync
... which will call confirm on the client
... which will await the user supplied answer
... and send it back to the server using Callback from The hub.
... which will notify the Callback from the NotificationService over a static event
... which will hand off the message back to ConfirmAsync (using a AutoResetEvent)
... which is hopefully still waiting :)

Client and server both have a 10 second timeout set.

The hub:

    // Setup as /notification-hub
    public class NotificationHub : Hub {
        public string ConnectionId() => Context.ConnectionId;
        public static event Action<string, string> Response;
        public void Callback(string connectionId, string message) {
            Response?.Invoke(connectionId, message);
        }
    }

Service:

    // Wire it up using DI
    public class NotificationService {
        private readonly IHubContext<NotificationHub> _notificationHubContext;
        public NotificationService(IHubContext<NotificationHub> notificationHubContext) {
            _notificationHubContext = notificationHubContext;
        }

        public async Task<string> ConfirmAsync(string connectionId, string text, IEnumerable<string> choices) {
            await _notificationHubContext.Clients.Client(connectionId)
                                         .SendAsync("confirm", text, choices);
            var are = new AutoResetEvent(false);
            string response = null;
            void Callback(string connId, string message) {
                if (connectionId == connId) {
                    response = message;
                    are.Set();
                }
            }
            NotificationHub.Response += Callback;
            are.WaitOne(TimeSpan.FromSeconds(10));
            NotificationHub.Response -= Callback;
            return response;
        }
    }

Client side js:

    var conn = new signalR.HubConnectionBuilder().withUrl("/notification-hub").build();
    var connId;

    // using Noty v3 (https://ned.im/noty/)
    function confirm(text, choices) {
        return new Promise(function (resolve, reject) {
            var n = new Noty({
                text: text,
                timeout: 10000,
                buttons: choices.map(function (b) {
                    return Noty.button(b, 'btn', function () {
                        resolve(b);
                        n.close();
                    });
                })
            });
            n.show();
        });
    }

    conn.on('confirm', function(text, choices) {
        confirm(text, choices).then(function(choice) {
            conn.invoke("Callback", connId, choice);
        });
    });

    conn.start().then(function() {
        conn.invoke("ConnectionId").then(function (connectionId) {
            connId = connectionId;
            // Picked up by a form and posted to the server
            document.querySelector(".connection-id-input").value = connectionId;
        });
    });

For me this is way to complex to put it into the project i am working on.
It really looks like something that will come back and bite you later...

like image 4
Florian Fida Avatar answered Oct 19 '22 07:10

Florian Fida