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);

希望这能帮助解决您的问题。