Hot questions for Using EventBus in signalr

Question:

I recently started a new project with the AspBoilerplate (Abp) and to use SignalR as some kind of broadcasting mechanism to tell the connected clients if some records in the databse changed or were added or removed. If i use the SignalR Hub as a proxy to my AppService everything works ok and the client is notified

public class TestHub : Hub
{
    IMyAppService = _service
    public TestHub(IMyAppService service)
    {
        _service = service;
    }

    public void CreateEntry(EntryDto entry)
    {
        _service.Create(entry);
        Clients.All.entryCreated(entry);
    }
}

But if i try to leverage the advantages of the EventBus of Abp so i implemented my AppSevice to send Events to the EventBus:

class MyAppService : ApplicationService, IMyAppService 
{
    public IEventBus EventBus { get; set; }

    private readonly IMyRepository _myRepository;


    public LicenseAppService(ILicenseRepository myRepository)
    {
        EventBus = NullEventBus.Instance;
        _myRepository = myRepository;
    }

    public virtual EntryDto CreateLicense(EntryDto input)
    {            
        var newEntry = Mapper.Map<EntryDto >(_myRepository.Insert(input));

        EventBus.Trigger(new EntryCreatedEventData { Entry = newEntry});
        return newEntry;
    }
}

Then i tried to use the hub directly as EventHandler, but this failed because abp creates its own instance of the EventHandler classes whenever it needs to handle an event. But here the code just for completeness:

public  class TestHub : Hub,
    IEventHandler<EntryCreatedEventData>
{ 
      public void Handle(EntryCreatedEventData data)
      {
           Clients.All.entryCreated(data.Entry);
      }
}

After this i created a seperate Listener class and tried to use the hub context like this and use an pretty empty Hub:

public  class TestHub : Hub
{ 
}

public  class EntryChangeEventHandler : IEventHandler<EntryCreatedEventData>
{ 
      private IHubContext _hubContext;
      public EntryChangeEventHandler()
      {
        _hubContext = GlobalHost.ConnectionManager.GetHubContext<TestHub>();

      public void Handle(EntryCreatedEventData data)
      {
        _hubContext.Clients.All.entryCreated(eventData.Entry);
      }
}

In the last solution everything runs up to the line

_hubContext.Clients.All.entryCreated(eventData.Entry);

but on the client side in my javascript implementation the method is never called. The client side (based on DurandalJs) didn't change between using the Hub as proxy and the new way i want to go.

Client side plugin for working with signalr

define(["jquery", "signalr.hubs"],
function ($) {
    var myHubProxy


    function connect(onStarted, onCreated, onEdited, onDeleted) {

        var connection = $.hubConnection();
        myHubProxy = connection.createHubProxy('TestHub');

        connection.connectionSlow(function () {
            console.log('We are currently experiencing difficulties with the connection.')
        });
        connection.stateChanged(function (data) {
            console.log('connectionStateChanged from ' + data.oldState + ' to ' + data.newState);
        });

        connection.error(function (error) {
            console.log('SignalR error: ' + error)
        });

        myHubProxy .on('entryCreated', onCreated);
        myHubProxy .on('updated', onEdited);
        myHubProxy .on('deleted', onDeleted);
        connection.logging = true;
        //start the connection and bind functions to send messages to the hub
        connection.start()
            .done(function () { onStarted(); })
            .fail(function (error) { console.log('Could not Connect! ' + error); });
    }    

    return signalr =
        {
            connect: connect
        };
});

view using the plugin:

define(['jquery', 'signalr/myHub],
    function ($, myHubSR) {
        return function () {
            var that = this;
            var _$view = null;

            that.attached = function (view, parent) {
                _$view = $(view);
            }

            that.activate = function () {
                myHubSR.connect(that.onStarted, that.onCreated, that.onEdited, that.onDeleted);
            }

            that.onStarted= function () { 
                //do something 
            }
            that.onCreated= function (data) { 
                //do something
            }
            that.onEdited = function (data) { 
                //do something
            }
            that.onDeleted= function (data) {
                //do something 
            } 
       }       
});       

So anyone got a clue why onCreated is never called when i call

_hubContext.Clients.All.entryCreated(eventData.Entry);

?

For testing if the signalR communication works at all i added a method that directly calls a client method. Calling this method the update is pushed to the client successfully. so i think the problem is wiht the remote call to all clients using the IHubContext any clues what could go wrong in the usage of IHubContext?

public class TestHub : Hub
{
    public TestHub ()
        :base()
    { }

    public void Test()
    {
        this.Clients.All.entryCreated(new EntryDto());
    }
}

Answer:

First, have you registered EntryChangeEventHandler to DI? If not, implement also ITransientDependency interface for EntryChangeEventHandler.

Your problem might be related to serializing. It may not serialize eventData.Entry. You can try to send another DTO object.

Also, you can implement

IEventHandler<EntityChangedEventData<Project>>

in order to listen all changes in a Project entity (including insert, update and delete). Project is just a sample entity here.

For your first case, TestHub can not work if it's not registered to DI. You may implement also ITransientDependency for TestHub class. And you should make SignalR to get it from DI container. You can use such a class for it:

public class WindsorDependencyResolver : DefaultDependencyResolver
{
    public override object GetService(Type serviceType)
    {
        return IocManager.Instance.IocContainer.Kernel.HasComponent(serviceType) ? IocManager.Instance.IocContainer.Resolve(serviceType) : base.GetService(serviceType);
    }

    public override IEnumerable<object> GetServices(Type serviceType)
    {
        return IocManager.Instance.IocContainer.Kernel.HasComponent(serviceType) ? IocManager.Instance.IocContainer.ResolveAll(serviceType).Cast<object>() : base.GetServices(serviceType);
    }
}

And then set it on startup:

GlobalHost.DependencyResolver = new WindsorDependencyResolver();

Maybe my answer was a bit confusing :) I hope you can understand it.