SignalR 从更改事件处理程序解析集线器上下文
SignalR resolving a hub context from a change event handler
我遇到一个问题,我似乎无法从 ChangedEventHandler
向连接的 Signal R 客户端发送新数据。文档说我可以使用以下方法获取集线器上下文:-
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.addToList(insertedCustomer);
然而,没有任何内容发送到客户端(在 fiddler 上检查)或报告任何错误。在我创建概念证明时,我的 onchange 事件从 Application_Start
开始连接。我应该指出集线器确实在启动时工作并从初始 GetAll
调用
中检索数据
protected void Application_Start()
{
...
_sqlTableDependency.OnChanged += _sqlTableDependency_OnChanged;
_sqlTableDependency.Start();
...
}
private void _sqlTableDependency_OnChanged(object sender, RecordChangedEventArgs<BiddingText> e)
{
switch (e.ChangeType)
{
case ChangeType.Insert:
foreach (var insertedCustomer in e.ChangedEntities)
{
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.addToList(insertedCustomer);
biddingTextList.Add(insertedCustomer);
}
break;
}
}
当我在集线器上设置断点时 context
我恢复了 ChatHub。
我的Javascript代码:
$.connection.hub.url = "http://localhost:37185/signalr";
// Reference the auto-generated proxy for the hub.
var chat = $.connection.chatHub;
chat.client.initialText = function(data) {
var index;
//console.log(data.length);
for (index = 0; index < data.List.length; ++index) {
$('#list').append("<li>" + data.List[index].text + "</li>");
}
};
chat.client.addToList = function(data) {
console.log(data);
$('#list').append("<li>" + data.text + "</li>");
};
// Start the connection.
$.connection.hub.start({ jsonp: true }).done(function () {
chat.server.getAll(1831);
});
我的中心代码:
public class ChatHub : Microsoft.AspNet.SignalR.Hub
{
private readonly IMediator mediator;
public ChatHub(IMediator mediator)
{
this.mediator = mediator;
}
public void GetAll(int saleId)
{
var model = mediator.Request(new BiddingTextQuery { SaleId = saleId});
Clients.Caller.initialText(model);
}
}
不确定这是否相关,但每次我使用 GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
时 Clients.Connection.Identity
都不一样
有人能帮忙吗?
您需要跟踪连接到集线器的客户端,然后向它们发送新消息,类似这样
这是我为 Hubs 编写的基础 class
/// <summary>
/// base class for Hubs in the system.
/// </summary>
public class HubBase : Hub {
/// <summary>
/// The hub users
/// </summary>
protected static ConcurrentDictionary<Guid, HubUser> Users = new ConcurrentDictionary<Guid, HubUser>();
/// <summary>
/// Called when the connection connects to this hub instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" />
/// </returns>
public override System.Threading.Tasks.Task OnConnected() {
Guid userName = RetrieveUserId();
string connectionId = Context.ConnectionId;
HubUser user = Users.GetOrAdd(userName, _ => new HubUser {
UserId = userName,
ConnectionIds = new HashSet<string>()
});
lock (user.ConnectionIds) {
user.ConnectionIds.Add(connectionId);
}
return base.OnConnected();
}
/// <summary>
/// Called when a connection disconnects from this hub gracefully or due to a timeout.
/// </summary>
/// <param name="stopCalled">true, if stop was called on the client closing the connection gracefully;
/// false, if the connection has been lost for longer than the
/// <see cref="P:Microsoft.AspNet.SignalR.Configuration.IConfigurationManager.DisconnectTimeout" />.
/// Timeouts can be caused by clients reconnecting to another SignalR server in scaleout.</param>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" />
/// </returns>
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled) {
try {
Guid userName = RetrieveUserId();
string connectionId = Context.ConnectionId;
HubUser user;
Users.TryGetValue(userName, out user);
if (user != null) {
lock (user.ConnectionIds) {
user.ConnectionIds.RemoveWhere(cid => cid.Equals(connectionId));
if (!user.ConnectionIds.Any()) {
HubUser removedUser;
Users.TryRemove(userName, out removedUser);
}
}
}
} catch {
//Bug in SignalR causing Context.User.Identity.Name to sometime be null
//when user disconnects, thus remove the connection manually.
lock (Users) {
HubUser entry = Users.Values.FirstOrDefault(v => v.ConnectionIds.Contains(Context.ConnectionId));
if (entry != null) entry.ConnectionIds.Remove(Context.ConnectionId);
}
}
return base.OnDisconnected(stopCalled);
}
private Guid RetrieveUserId() {
Cookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket decryptedCookie = FormsAuthentication.Decrypt(authCookie.Value);
var user = JsonConvert.DeserializeObject<User>(decryptedCookie.UserData);
return user.Id;
}
}
那么Hub代码就是
/// <summary>
/// A hub for sending alerts to users.
/// </summary>
public class AlertHub : HubBase, IAlertHub {
/// <summary>
/// Sends the alert.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="userId">The user identifier.</param>
public void SendAlert(string message, Guid userId) {
HubUser user;
Users.TryGetValue(userId, out user);
if (user != null) {
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<AlertHub>();
context.Clients.Clients(user.ConnectionIds.ToList()).sendAlert(message);
}
}
/// <summary>
/// Send alert to user.
/// </summary>
/// <param name="returnId">The return identifier.</param>
/// <param name="userId">The user identifier.</param>
public void ReturnProcessedAlert(Guid returnId, Guid userId) {
HubUser user;
Users.TryGetValue(userId, out user);
if (user != null) {
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<AlertHub>();
context.Clients.Clients(user.ConnectionIds.ToList()).returnProcessedAlert(returnId);
}
}
}
我在设置 Nancy API 以向 SignalR 客户端发布一些事件时遇到了一些类似的问题。
我遇到的核心问题是我未能确保 Nancy 和 SignalR 在 SignalR 全局级别使用相同的 DI 容器。
SignalR 和 Nancy 一样,有一个默认值 DependencyResolver
,用于解决集线器中的任何依赖关系。当我未能为 Nancy 和 SignalR 实现相同的依赖源时,我基本上得到了两个独立的应用程序。
小免责声明:您尚未发布您的配置代码,因此我的解决方案基于一些假设(以及您在 Twitter 上联系时来自 David Fowler 的以下 Twitter 回答:
@rippo you have a custom dependency resolver and global has has another. You need to use one container
(https://twitter.com/davidfowl/status/635000470340153344)
现在一些代码:
首先,您需要实现自定义 SignalR 依赖解析器,并确保它使用与应用程序其余部分相同的依赖源。
这是我用于 Autofac 容器的实现:
using Autofac;
using Autofac.Builder;
using Autofac.Core;
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
namespace LabCommunicator.Server.Configuration
{
internal class AutofacSignalrDependencyResolver : DefaultDependencyResolver, IRegistrationSource
{
private ILifetimeScope LifetimeScope { get; set; }
public AutofacSignalrDependencyResolver(ILifetimeScope lifetimeScope)
{
LifetimeScope = lifetimeScope;
var currentRegistrationSource = LifetimeScope.ComponentRegistry.Sources.FirstOrDefault(s => s.GetType() == GetType());
if (currentRegistrationSource != null)
{
((AutofacSignalrDependencyResolver)currentRegistrationSource).LifetimeScope = lifetimeScope;
}
else
{
LifetimeScope.ComponentRegistry.AddRegistrationSource(this);
}
}
public override object GetService(Type serviceType)
{
object result;
if (LifetimeScope == null)
{
return base.GetService(serviceType);
}
if (LifetimeScope.TryResolve(serviceType, out result))
{
return result;
}
return null;
}
public override IEnumerable<object> GetServices(Type serviceType)
{
object result;
if (LifetimeScope == null)
{
return base.GetServices(serviceType);
}
if (LifetimeScope.TryResolve(typeof(IEnumerable<>).MakeGenericType(serviceType), out result))
{
return (IEnumerable<object>)result;
}
return Enumerable.Empty<object>();
}
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
var typedService = service as TypedService;
if (typedService != null)
{
var instances = base.GetServices(typedService.ServiceType);
if (instances != null)
{
return instances
.Select(i => RegistrationBuilder.ForDelegate(i.GetType(), (c, p) => i).As(typedService.ServiceType)
.InstancePerLifetimeScope()
.PreserveExistingDefaults()
.CreateRegistration());
}
}
return Enumerable.Empty<IComponentRegistration>();
}
bool IRegistrationSource.IsAdapterForIndividualComponents
{
get { return false; }
}
}
}
接下来,在您的 SignalR 配置中,您需要分配自定义依赖解析器:
注意:我的应用程序使用的是 owin,所以你可能不需要 HubConfiguration
位,但你需要 GlobalHost
位(这是我在我的东西不工作时搞砸的那个).
var resolver = new AutofacSignalrDependencyResolver(container);
'Owin config options.
var config = new HubConfiguration()
{
Resolver = resolver,
EnableDetailedErrors = true,
EnableCrossDomain = true
};
GlobalHost.DependencyResolver = resolver;
'More owin stuff
app.MapHubs(config);
希望这能帮助解决您的问题。
我遇到一个问题,我似乎无法从 ChangedEventHandler
向连接的 Signal R 客户端发送新数据。文档说我可以使用以下方法获取集线器上下文:-
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.addToList(insertedCustomer);
然而,没有任何内容发送到客户端(在 fiddler 上检查)或报告任何错误。在我创建概念证明时,我的 onchange 事件从 Application_Start
开始连接。我应该指出集线器确实在启动时工作并从初始 GetAll
调用
protected void Application_Start()
{
...
_sqlTableDependency.OnChanged += _sqlTableDependency_OnChanged;
_sqlTableDependency.Start();
...
}
private void _sqlTableDependency_OnChanged(object sender, RecordChangedEventArgs<BiddingText> e)
{
switch (e.ChangeType)
{
case ChangeType.Insert:
foreach (var insertedCustomer in e.ChangedEntities)
{
var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.addToList(insertedCustomer);
biddingTextList.Add(insertedCustomer);
}
break;
}
}
当我在集线器上设置断点时 context
我恢复了 ChatHub。
我的Javascript代码:
$.connection.hub.url = "http://localhost:37185/signalr";
// Reference the auto-generated proxy for the hub.
var chat = $.connection.chatHub;
chat.client.initialText = function(data) {
var index;
//console.log(data.length);
for (index = 0; index < data.List.length; ++index) {
$('#list').append("<li>" + data.List[index].text + "</li>");
}
};
chat.client.addToList = function(data) {
console.log(data);
$('#list').append("<li>" + data.text + "</li>");
};
// Start the connection.
$.connection.hub.start({ jsonp: true }).done(function () {
chat.server.getAll(1831);
});
我的中心代码:
public class ChatHub : Microsoft.AspNet.SignalR.Hub
{
private readonly IMediator mediator;
public ChatHub(IMediator mediator)
{
this.mediator = mediator;
}
public void GetAll(int saleId)
{
var model = mediator.Request(new BiddingTextQuery { SaleId = saleId});
Clients.Caller.initialText(model);
}
}
不确定这是否相关,但每次我使用 GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
Clients.Connection.Identity
都不一样
有人能帮忙吗?
您需要跟踪连接到集线器的客户端,然后向它们发送新消息,类似这样
这是我为 Hubs 编写的基础 class
/// <summary>
/// base class for Hubs in the system.
/// </summary>
public class HubBase : Hub {
/// <summary>
/// The hub users
/// </summary>
protected static ConcurrentDictionary<Guid, HubUser> Users = new ConcurrentDictionary<Guid, HubUser>();
/// <summary>
/// Called when the connection connects to this hub instance.
/// </summary>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" />
/// </returns>
public override System.Threading.Tasks.Task OnConnected() {
Guid userName = RetrieveUserId();
string connectionId = Context.ConnectionId;
HubUser user = Users.GetOrAdd(userName, _ => new HubUser {
UserId = userName,
ConnectionIds = new HashSet<string>()
});
lock (user.ConnectionIds) {
user.ConnectionIds.Add(connectionId);
}
return base.OnConnected();
}
/// <summary>
/// Called when a connection disconnects from this hub gracefully or due to a timeout.
/// </summary>
/// <param name="stopCalled">true, if stop was called on the client closing the connection gracefully;
/// false, if the connection has been lost for longer than the
/// <see cref="P:Microsoft.AspNet.SignalR.Configuration.IConfigurationManager.DisconnectTimeout" />.
/// Timeouts can be caused by clients reconnecting to another SignalR server in scaleout.</param>
/// <returns>
/// A <see cref="T:System.Threading.Tasks.Task" />
/// </returns>
public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled) {
try {
Guid userName = RetrieveUserId();
string connectionId = Context.ConnectionId;
HubUser user;
Users.TryGetValue(userName, out user);
if (user != null) {
lock (user.ConnectionIds) {
user.ConnectionIds.RemoveWhere(cid => cid.Equals(connectionId));
if (!user.ConnectionIds.Any()) {
HubUser removedUser;
Users.TryRemove(userName, out removedUser);
}
}
}
} catch {
//Bug in SignalR causing Context.User.Identity.Name to sometime be null
//when user disconnects, thus remove the connection manually.
lock (Users) {
HubUser entry = Users.Values.FirstOrDefault(v => v.ConnectionIds.Contains(Context.ConnectionId));
if (entry != null) entry.ConnectionIds.Remove(Context.ConnectionId);
}
}
return base.OnDisconnected(stopCalled);
}
private Guid RetrieveUserId() {
Cookie authCookie = Context.Request.Cookies[FormsAuthentication.FormsCookieName];
FormsAuthenticationTicket decryptedCookie = FormsAuthentication.Decrypt(authCookie.Value);
var user = JsonConvert.DeserializeObject<User>(decryptedCookie.UserData);
return user.Id;
}
}
那么Hub代码就是
/// <summary>
/// A hub for sending alerts to users.
/// </summary>
public class AlertHub : HubBase, IAlertHub {
/// <summary>
/// Sends the alert.
/// </summary>
/// <param name="message">The message.</param>
/// <param name="userId">The user identifier.</param>
public void SendAlert(string message, Guid userId) {
HubUser user;
Users.TryGetValue(userId, out user);
if (user != null) {
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<AlertHub>();
context.Clients.Clients(user.ConnectionIds.ToList()).sendAlert(message);
}
}
/// <summary>
/// Send alert to user.
/// </summary>
/// <param name="returnId">The return identifier.</param>
/// <param name="userId">The user identifier.</param>
public void ReturnProcessedAlert(Guid returnId, Guid userId) {
HubUser user;
Users.TryGetValue(userId, out user);
if (user != null) {
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<AlertHub>();
context.Clients.Clients(user.ConnectionIds.ToList()).returnProcessedAlert(returnId);
}
}
}
我在设置 Nancy API 以向 SignalR 客户端发布一些事件时遇到了一些类似的问题。
我遇到的核心问题是我未能确保 Nancy 和 SignalR 在 SignalR 全局级别使用相同的 DI 容器。
SignalR 和 Nancy 一样,有一个默认值 DependencyResolver
,用于解决集线器中的任何依赖关系。当我未能为 Nancy 和 SignalR 实现相同的依赖源时,我基本上得到了两个独立的应用程序。
小免责声明:您尚未发布您的配置代码,因此我的解决方案基于一些假设(以及您在 Twitter 上联系时来自 David Fowler 的以下 Twitter 回答:
@rippo you have a custom dependency resolver and global has has another. You need to use one container (https://twitter.com/davidfowl/status/635000470340153344)
现在一些代码:
首先,您需要实现自定义 SignalR 依赖解析器,并确保它使用与应用程序其余部分相同的依赖源。
这是我用于 Autofac 容器的实现:
using Autofac;
using Autofac.Builder;
using Autofac.Core;
using Microsoft.AspNet.SignalR;
using System;
using System.Collections.Generic;
using System.Linq;
namespace LabCommunicator.Server.Configuration
{
internal class AutofacSignalrDependencyResolver : DefaultDependencyResolver, IRegistrationSource
{
private ILifetimeScope LifetimeScope { get; set; }
public AutofacSignalrDependencyResolver(ILifetimeScope lifetimeScope)
{
LifetimeScope = lifetimeScope;
var currentRegistrationSource = LifetimeScope.ComponentRegistry.Sources.FirstOrDefault(s => s.GetType() == GetType());
if (currentRegistrationSource != null)
{
((AutofacSignalrDependencyResolver)currentRegistrationSource).LifetimeScope = lifetimeScope;
}
else
{
LifetimeScope.ComponentRegistry.AddRegistrationSource(this);
}
}
public override object GetService(Type serviceType)
{
object result;
if (LifetimeScope == null)
{
return base.GetService(serviceType);
}
if (LifetimeScope.TryResolve(serviceType, out result))
{
return result;
}
return null;
}
public override IEnumerable<object> GetServices(Type serviceType)
{
object result;
if (LifetimeScope == null)
{
return base.GetServices(serviceType);
}
if (LifetimeScope.TryResolve(typeof(IEnumerable<>).MakeGenericType(serviceType), out result))
{
return (IEnumerable<object>)result;
}
return Enumerable.Empty<object>();
}
public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Func<Service, IEnumerable<IComponentRegistration>> registrationAccessor)
{
var typedService = service as TypedService;
if (typedService != null)
{
var instances = base.GetServices(typedService.ServiceType);
if (instances != null)
{
return instances
.Select(i => RegistrationBuilder.ForDelegate(i.GetType(), (c, p) => i).As(typedService.ServiceType)
.InstancePerLifetimeScope()
.PreserveExistingDefaults()
.CreateRegistration());
}
}
return Enumerable.Empty<IComponentRegistration>();
}
bool IRegistrationSource.IsAdapterForIndividualComponents
{
get { return false; }
}
}
}
接下来,在您的 SignalR 配置中,您需要分配自定义依赖解析器:
注意:我的应用程序使用的是 owin,所以你可能不需要 HubConfiguration
位,但你需要 GlobalHost
位(这是我在我的东西不工作时搞砸的那个).
var resolver = new AutofacSignalrDependencyResolver(container);
'Owin config options.
var config = new HubConfiguration()
{
Resolver = resolver,
EnableDetailedErrors = true,
EnableCrossDomain = true
};
GlobalHost.DependencyResolver = resolver;
'More owin stuff
app.MapHubs(config);
希望这能帮助解决您的问题。