SignalR从更改事件处理程序解析集线器上下文

时间:2015-08-22 07:37:15

标签: c# signalr signalr-hub signalr.client

我遇到一个问题,我似乎无法从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);
    }

}

不确定这是否相关,但每次使用Clients.Connection.IdentityGlobalHost.ConnectionManager.GetHubContext<ChatHub>();都不同

有人可以帮忙吗?

2 个答案:

答案 0 :(得分:4)

我有一些类似的问题,一段时间后设置Nancy API将一些事件发布给SignalR客户端。

我遇到的核心问题是我未能确保Nancy和SignalR在SignalR全球级使用相同的DI容器。

SignalR,作为Nancy,有一个默认的DependencyResolver,用于解决集线器中的任何依赖关系。当我未能为Nancy和SignalR实现相同的依赖源时,我基本上最终得到了两个独立的应用程序。

小免责声明:您尚未发布您的配置代码,因此我的解决方案基于一些假设(以及当您在推特上联系时David Fowler的以下Twitter回答:

  

@rippo你有一个自定义依赖解析器,而global有另一个。您需要使用一个容器   (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);

希望这有助于解决您的问题。

答案 1 :(得分:1)

您需要跟踪连接到集线器的客户端,然后向他们发送新消息,如下所示

这是我为我的中心写的基类

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