为什么在我非常简单的方法中无法在运行时访问集线器上下文连接 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>
尽量少构建一个应用程序来隔离问题。我没有遇到过你提到的任何问题。
为了简单和使用调试器,我去掉了 await
和 async
。
实际上,SignalR 会为您处理好这件事。您将在每次请求时获得一个新的集线器实例 class,无需在方法中强制异步。
此外,GlobalHost
被定义为 static
,它应该在您的 Hub class 实例之间共享。在实例方法中使用似乎不是一个好主意。我认为您想改用 Context
和 Clients
对象。但是,在调试时我们可以验证使用 GlobalHost
也有效。
一些调试器屏幕截图显示了 callerId
、Clients.Caller
和 Clients.Client(clientID)
的运行时值:
更好地了解 SignalR 将对您实现目标大有帮助。
调试愉快!
首先让我说这一切目前都完美无缺,除了一件事 - 进度条的通知更新发送给所有客户端(正如您所期望的那样,鉴于我使用的代码示例发送到 ...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>
尽量少构建一个应用程序来隔离问题。我没有遇到过你提到的任何问题。
为了简单和使用调试器,我去掉了 await
和 async
。
实际上,SignalR 会为您处理好这件事。您将在每次请求时获得一个新的集线器实例 class,无需在方法中强制异步。
此外,GlobalHost
被定义为 static
,它应该在您的 Hub class 实例之间共享。在实例方法中使用似乎不是一个好主意。我认为您想改用 Context
和 Clients
对象。但是,在调试时我们可以验证使用 GlobalHost
也有效。
一些调试器屏幕截图显示了 callerId
、Clients.Caller
和 Clients.Client(clientID)
的运行时值:
更好地了解 SignalR 将对您实现目标大有帮助。
调试愉快!