Search code examples
c#signalrsignalr-hubsignalr.client

SignalR resolving a hub context from a change event handler


I have an issue where I can't seem to send new data to the connected Signal R clients from a ChangedEventHandler. The docs says that I can get the hub context by using:-

var context = GlobalHost.ConnectionManager.GetHubContext<ChatHub>();
context.Clients.All.addToList(insertedCustomer);

However nothing gets sent to the clients (checked on fiddler) or any errors reported. My onchange event is wired up at the moment from Application_Start as I am creating a proof of concept. I should point out the hub does work on start up and retrieves the data from the initial GetAll call

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

When I put a breakpoint on the hub context I get my ChatHub back.

My Javascript code:

$.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);
});

My Hub code:

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

}

Not sure if this is relevant but the Clients.Connection.Identity is different everytime I use GlobalHost.ConnectionManager.GetHubContext<ChatHub>();

Can anyone help?


Solution

  • I had some similar issues a while back setting up a Nancy API to publish some events to SignalR clients.

    The core issue I had was that I had failed to ensure Nancy and SignalR was using the same DI container at the SignalR global level.

    SignalR, as Nancy, has a default DependencyResolver that is used to resolve any dependencies in your hubs. When I failed to implement the same source of dependencies for Nancy and SignalR I basically ended up with two separate applications.

    Small disclaimer: You have not posted your config code, so my solution here is based on some assumptions (as well as the following twitter answer from David Fowler when you reached out on 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)

    Now some code:

    First you need to implement a custom SignalR depependency resolver, and ensure it uses the same source of dependencies as the rest of your application.

    This is the implementation I used for an Autofac container:

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

    Next, in your SignalR config, you need to assign the custom dependency resolver:

    Note: My app was using owin, so you may not need the HubConfiguration bit, but you need the GlobalHost bit (which is the one I messed up when my stuff was not working).

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

    Hope this will help resolve your issue.