为什么在我非常简单的方法中无法在运行时访问集线器上下文连接 ID?

Why is the hub context connection ID inaccessible at runtime in my VERY simple method?

首先让我说这一切目前都完美无缺,除了一件事 - 进度条的通知更新发送给所有客户端(正如您所期望的那样,鉴于我使用的代码示例发送到 ...Clients.All).

我想要做的就是将通知发送回发起当前呼叫集线器的客户端。仅此而已,没有别的。此网站上没有 "logging in" 的概念,因此没有可用的用户身份信息。

我的方法是:

public void NotifyUpdates(decimal val)
{
    var hubContext = GlobalHost.ConnectionManager.GetHubContext<EventsForceHub>();

    if (hubContext != null)
    {
        //"updateProgress" is javascript event trigger name
        await hubContext.Clients.All.updateProgress(val); 
    }
}

所以回到我看来,我订阅了 "updateProgress",它工作正常 - 进度条会根据需要更新。但是,如果另一个客户端恰好连接到集线器,当一个 运行 执行异步任务并导致 NotifyUpdates 方法为 运行 时,所有连接的客户端都会看到任务栏更新,这是他们有点困惑!

在调试中,如果我在 运行 时检查 hubContext,我可以看到一个 Clients 属性 并且有一个 Connection 属性 有一个 Identity 属性 和唯一的 GUID。完美的!正是我想用的。但是...我无法访问它!如果我尝试:

var currentConnection = hubContext.Clients.Connection;

...然后我就得到一个

"does not contain a definition for 'Connection'"

错误,我根本看不懂。

我也试过从该方法访问 Context.ConnectionId,但是 Context 在那个时候是 null,所以我有点困惑。使用 NotifyUpdates 将信息发送回客户端的服务器方法是通过普通 asp.net 按钮调用的,而不是通过 AJAX.

结构说明

我认为这里存在一定程度的混乱。这是一个非常简单的网页,上面有一个 asp.net 按钮控件。该按钮的事件处理程序通过服务 call/repository.

从服务器调用异步方法 return 数据

在异步方法内部,它必须处理每个 returned 数据行并进行三到四个远程 Web api 调用。在每个循环中,我都会用一个完整的百分比回调上面显示的 NotifyUpdates SignalR 方法,这样它就可以通过指定方法名称的事件处理程序更新回客户端(updateProgress,如上所示) .可能有几十条数据线,每条数据线都需要对远程服务器进行多次 Web API 调用以添加数据。每次迭代可能需要几秒钟,因此我通过 updateProgress 方法调用将 "update progress" 发送回客户端。

如果要将通知发回客户端 你不应该打电话

hubContext.Clients.All.updateProgress(val);

改为尝试

访问当前用户的ConnectionId并使用Clients.Client

hubContext.Clients.Client(Context.ConnectionId);

新答案

根据您的意见,我做了以下小测试:

它将允许客户端通过 clientName 连接到集线器,并且每个客户端都在监听发送给它们的更新。我们将为他们定义一个组,以便能够从服务器端通知他们。

我做了一个虚拟的进度模拟器 class 来向用户抛出一些更新值。

代码:

中心 class:

public class EventsForceHub : Hub {
    public static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<EventsForceHub>();

    // allow users to join to hub and get s dedicated group/channel for them, so we can update them
    public async Task JoinGroup(string clientName) {
        string clientID = Context.ConnectionId;
        ClientInfo.clients.Add(clientID, new MyAppClient(clientID, clientName));
        await Groups.Add(clientID, clientName);

        // this is just mockup to simulate progress events (this uis not needed in real application)
        MockupProgressGenerator.DoJob(clientName, 0);
    }

    public static void NotifyUpdates(decimal val, string clientName) {
        // update the given client on his group/channel
        hubContext.Clients.Group(clientName).updateProgress(val);
    }
}

一些小帮手classes:

// client "storage"
public static class ClientInfo  {
    public static Dictionary<string, MyAppClient> clients = new Dictionary<string, MyAppClient>();
    // .. further data and methods
}

// client type
public class MyAppClient {
    public string Id { get; set; }
    public string Name { get; set; }
    // .. further prooerties and methods

    public MyAppClient(string id, string name) {
        Id = id;
        Name = name;
    }
}

// this a completely made up and dumb class to simulate slow process and give some simple progress events
public static class MockupProgressGenerator {
    public static void DoJob(string clientName, int status) {
        if (status < 100) {
            Task.Delay(1000).ContinueWith(a =>
            {
                EventsForceHub.NotifyUpdates(status += 20, clientName);
                DoJob(clientName, status);
            });
        }
    }
}

让我们看看两个简单的 JS 客户端:

    $(function () {
        var eventsForceHub = $.connection.eventsForceHub;

        $.connection.hub.start().done(function () {
            $('body').append("Joining with Name: Jerry");
            eventsForceHub.server.joinGroup("Jerry");
        });

        eventsForceHub.client.updateProgress = function (val) {
            // message received
            $('body').append('<br>').append("New Progress message: " + val);
        };
    });

为了简单起见,相同的代码,不同的参数,我什至把它放在两个不同的 html 页面中,并在稍微不同的时间声明执行。

    $(function () {
        var eventsForceHub = $.connection.eventsForceHub;

        $.connection.hub.start().done(function () {
            $('body').append("Joining with Name: Tom");
            eventsForceHub.server.joinGroup("Tom");
        });

        eventsForceHub.client.updateProgress = function (val) {
            // message received
            $('body').append('<br>').append("New Progress message: " + val);
        };
    });

查看实际效果:


第一个答案

我制作了一个小型网络应用程序来验证您的说法。您可以创建以下内容以便能够将问题与其他可能的问题区分开来。

我创建了一个空的 Web 应用程序并包含了 SignalR。

这是枢纽 class:

public class EventsForceHub : Hub {
    public void NotifyUpdates(decimal val) {
        var hubContext = GlobalHost.ConnectionManager.GetHubContext<EventsForceHub>();

        if (Context != null) {
            string clientID = Context.ConnectionId;         // <-- on debug: Ok has conn id.
            object caller = Clients.Caller;                 // <-- on debug: Ok, not null
            object caller2 = Clients.Client(clientID);      // <-- on debug: Ok, not null
            Clients.Caller.updateProgress(val);             // Message sent
            Clients.Client(clientID).updateProgress(val);   // Message sent
        }

        if (hubContext != null) {
            //"updateProgress" is javascript event trigger name
            hubContext.Clients.All.updateProgress(val);     // Message sent
        }
    }
}

这是网页:

<script src="Scripts/jquery-1.10.2.min.js"></script>
<script src="Scripts/jquery.signalR-2.2.2.min.js"></script>
<script src="signalr/hubs"></script>
<script type="text/javascript">
    $(function () {
        var eventsForceHub = $.connection.eventsForceHub;

        $.connection.hub.start().done(function () {
            // send mock message on start
            console.log("Sending mock message: " + 42);
            eventsForceHub.server.notifyUpdates(42);
        });

        eventsForceHub.client.updateProgress = function (val) {
            // message received
            console.log("New Progress message: " + val);
        };
    });
</script>

尽量少构建一个应用程序来隔离问题。我没有遇到过你提到的任何问题。

为了简单和使用调试器,我去掉了 awaitasync

实际上,SignalR 会为您处理好这件事。您将在每次请求时获得一个新的集线器实例 class,无需在方法中强制异步。

此外,GlobalHost 被定义为 static,它应该在您的 Hub class 实例之间共享。在实例方法中使用似乎不是一个好主意。我认为您想改用 ContextClients 对象。但是,在调试时我们可以验证使用 GlobalHost 也有效。

一些调试器屏幕截图显示了 callerIdClients.CallerClients.Client(clientID) 的运行时值:

更好地了解 SignalR 将对您实现目标大有帮助。

调试愉快!