Signalr - 是否可以等待客户端的响应?

7
我是一个使用Signalr的初学者,正在查看一些示例。
从服务器向客户端发送消息并等待其返回是可能的吗?或者能否保证在回答后使用相同的会话?
我的问题是因为在给定的进程中,在事务中,我需要询问用户是否要继续更改。由于验证应在进行更改的同一会话中进行(但尚未确认),因此我尚未能够提出此问题。

1
在SignalR中并不存在“response”这样的东西,你可以在收到通知后从客户端向服务器发起调用。编辑你的帖子并附上你尝试过的代码,我可以提供一些帮助。 - Jaime Yule
谢谢你的反馈。实际上,我还没有开发出代码。我正在学习,并在互联网上寻找相关示例。如果你知道任何相关材料或有这个解决方案的示例,请告诉我。 - Carlos Henrique Biazin Esteves
请在此线程中查看我的答案(https://dev59.com/r6jja4cB1Zd3GeqP8UQP#75100129),因为现在可以使用.NET 7实现。 - knoxgon
5个回答

13

重申Jaime Yule的评论,WebSockets(网络套接字)是双向通信协议,并不遵循请求/响应架构来传递消息。考虑到WebSockets通信非常灵活的特性,下面这些要点很有必要时刻谨记,无论是现在还是将来:

  • 如果你只需要进行“发出并忘记”的操作(向用户显示弹出式窗口等),那SignalR是一个很好的选择。
  • 它并不是像你所期望的那样设计成请求-响应模式,试图将其用作此类方式是一种反模式。
  • 消息可以在连接的任何一端随时发送,没有原生支持一个消息指示它与其他消息有关。
  • 这使得该协议不适合交易型需求。

1
好的。非常感谢! - Carlos Henrique Biazin Esteves
请在此线程中检查我的答案,因为使用.NET 7现在已经有可能了。 - knoxgon

4
这是可能的,但我不建议(依赖)这样做。而且这不是一个好看的解决方案(使用静态事件并且对于这么简单的事情而言相当复杂)。
故事背景如下:
确保客户端和服务器知道连接ID - 他们可能已经知道了,但我无法找到一种访问它的方法。
等待NotificationService.ConfirmAsync,
... 这将调用客户端上的 confirm,
... 等待用户提供的答案,
... 并使用Hub的Callback将其发送回服务器。
... 这将通过静态事件通知NotificationService的回调函数,
... 这将把消息交还给ConfirmAsync(使用AutoResetEvent)。
... 希望此时仍在等待:)
客户端和服务器都设置了10秒的超时时间。
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);
        }
    }

服务:

    // 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;
        }
    }

客户端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;
        });
    });

对我来说,这太复杂了,无法将其纳入我正在开发的项目中。
它看起来真的像是以后会反噬你的东西...


3

使用客户端结果,现在可以在.NET7中实现这一点。

今天,我在dotnet的Github页面中强调了这个问题,并得到了SignalR开发人员的很好回应。

这需要服务器使用ISingleClientProxy.InvokeAsync来能够向客户端发送请求并等待响应。

文档摘录:

除了调用客户端之外,服务器还可以从客户端请求结果。这需要服务器使用ISingleClientProxy.InvokeAsync,并要求客户端从其.On处理程序返回结果。

来自客户端(js/ts)

hubConnection.on("GetMessage", async () => {
  let promise = new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(new { data: "message" });
    }, 100);
  });
  return promise;
});

从服务器(C#)发送

//By calling Client(...) on an instance of IHubContext<T>
async Task<object> SomeMethod(IHubContext<MyHub> context)
{
    string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
        "GetMessage");
    return result;
}

//---------------------------//

//Or by calling Client(...) or Caller on the Clients property in a Hub method
public class ChatHub : Hub
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        var message = await Clients.Client(connectionId).InvokeAsync<string>(
            "GetMessage");
        return message;
    }
}

1
从服务器发送消息并等待客户端返回是可能的吗?或者是否可以保证在回答后将使用相同的会话?这些都不可能。目前没有办法等待客户端的响应,甚至无法知道客户端是否收到了消息。有一些讨论正在GitHub上实施此功能。另外这里还有一个request特性。在那之前,解决方法是从服务器发送“通知”,并采用“发出并忘记”的态度,让客户端通过向服务器发送HTTP请求来获取所需的数据。

-2
使用带有Invoke的以下表单会等待并直接返回响应(就像“真正”的同步方法调用一样)。
        var persons = hubProxy.Invoke<IEnumerable<Person>>("GetPersonsSynchronous", SearchCriteria, noteFields).Result;

        foreach (Person person in persons)
        {
            Console.WriteLine($"{person.LastName}, {person.FirstName}");
        }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接