Use SignalR as Broadcaster for EventBus Events

I recently started a new project with AspBoilerplate (Abp) and use SignalR as a kind of broadcast mechanism to tell connected clients if some records in the database have changed or have been added or removed. If I use SignalR Hub as a proxy for my AppService everything works fine 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 take advantage of the EventBus Abp, I have implemented an 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 the EventHandler, but that failed because abp creates its own instance of the EventHandler classes whenever it needs to handle an event. But here's the code just for completeness:

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

      

After that, I created a separate Listener class and tried to use a hub context like this and use a rather 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 works up to the line

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

      

but client side in my javascript implementation the method is never called. The client side (based on DurandalJs) hasn't changed between using the Hub as a proxy and the new way I want to do it.

Client-side signalr plugin

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 with 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, did anyone understand why onCreated is never called when I call

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

      

?

To test if signalR communication works for everyone, I added a method that calls the client method directly. When this method is called, the update succeeds to the client. so i think the problem is with remote invocation for all clients using the IHubContext any hints what might go wrong how to use the IHubContext?

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

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

      

+3


source to share


2 answers


After a long search in several directions, I finally found a solution.

If you are using a custom Dependency Resolver in HubConfiguration like me. For example, the implementation from hikalkan:

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

      

you can no longer use

_hubContext = GlobalHost.ConnectionManager.GetHubContext<TestHub>();

      



unless you also set your GlobalHost.DependencyResolver to the WindsorDependencyResolver instance or manually resolve the IConnectionManager reference.

GlobalHost.DependencyResolver = new AutofacDependencyResolver(container);
IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();

// A custom HubConfiguration is now unnecessary, since MapSignalR will
// use the resolver from GlobalHost by default.
app.MapSignalR();

      

or

IDependencyResolver resolver = new AutofacDependencyResolver(container);
IHubContext hubContext = resolver.Resolve<IConnectionManager>().GetHubContext<MyHub>();

app.MapSignalR(new HubConfiguration
{
    Resolver = resolver
});

      

+1


source


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

Your problem might be serialization related. It cannot serialize eventData.Entry. You can try sending another DTO object.

Alternatively, you can implement

IEventHandler<EntityChangedEventData<Project>>

      

to listen to all changes to the Project object (including insert, update, and delete). The project is just a sample object.



In your first case, TestHub cannot work if it is not registered with DI. You can also implement ITransientDependency for TestHub class. And you have to do SignalR to get it from the DI container. You can use a class like this for this:

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 install it on startup:

GlobalHost.DependencyResolver = new WindsorDependencyResolver ();

Maybe my answer was a little confusing :) Hope you can understand this.

+3


source







All Articles