3

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());
    }
}
BoeseB
  • 635
  • 4
  • 16
  • Could be off but where is the `entryCreated` function defined on the client side? – rdoubleui Jul 08 '15 at 14:14
  • thx for you comment you are right in the posted code there was an error cause i renamed the classes and methods. had to change the line myHubProxy .on('entryCreated', onCreated); in the real code the method name is written correctly – BoeseB Jul 08 '15 at 14:19
  • Is the OnCreated callback the only one that's not being reached? – rdoubleui Jul 09 '15 at 05:31
  • Neither one is called, just the onStartedCallback is reached – BoeseB Jul 09 '15 at 05:40
  • So I agree, it must be an issue within the JS code, most likely I'd assume it's a scope issue. The onStarted is the one passed as the done delegate, the others are attached on myHubProxy. – rdoubleui Jul 09 '15 at 06:11
  • i edited the question with a new test, that showed if i call the clients method directly from a TestHub instance the js code is correctly invoked on the client. So i guess the problem is with the IHubContext – BoeseB Jul 09 '15 at 06:14

2 Answers2

3

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.

hikalkan
  • 2,041
  • 13
  • 17
  • Thanks for your fast response. The EntryChangeEventHandler is created successfully, i also implemented the WindsorDependencyResolver and added an IConventionalDependencyRegistrar for SignalR Hubs. The Dto gets serialized successfully in other places and its the same dto as in the working first example where the hub directly communicates with the appservice – BoeseB Jul 09 '15 at 05:33
2

After long searching in several directions i finally found a solution.

If you use a custom dependency Resolver in the HubConfiguration like i did. 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 a instance of WindsorDependencyResolver or manually resolve a reference to IConnectionManager.

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
});
animuson
  • 50,765
  • 27
  • 132
  • 142
BoeseB
  • 635
  • 4
  • 16