Используйте SignalR в качестве вещателя для событий EventBus

Недавно я начал новый проект с AspBoilerplate (Abp) и использовал SignalR в качестве своего рода механизма вещания, чтобы сообщать подключенным клиентам, изменились ли некоторые записи в базе данных, были ли они добавлены или удалены. Если я использую SignalR Hub в качестве прокси для моего AppService, все работает нормально, и клиент получает уведомление

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

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

Но если я попытаюсь использовать преимущества EventBus of Abp, я реализовал свой AppSevice для отправки событий в 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;
    }
}

Затем я попытался использовать концентратор напрямую как EventHandler, но это не удалось, потому что abp создает свой собственный экземпляр классов EventHandler всякий раз, когда ему нужно обработать событие. Но вот код только для полноты:

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

После этого я создал отдельный класс Listener и попытался использовать контекст концентратора, подобный этому, и использовать довольно пустой концентратор:

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

В последнем решении все идет к линии

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

но на стороне клиента в моей реализации javascript метод никогда не вызывается. Клиентская сторона (на основе DurandalJ) не менялась между использованием Hub в качестве прокси и новым способом, которым я хочу идти.

Клиентский плагин для работы с сигнализатором

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

просмотреть с помощью плагина:

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

Так что кто-нибудь понял, почему onCreated никогда не вызывается, когда я звоню

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

?

Для проверки работоспособности связи signalR я добавил метод, который напрямую вызывает метод клиента. При вызове этого метода обновление успешно отправляется клиенту. так что я думаю, что проблема заключается в том, что при удаленном вызове всех клиентов, использующих IHubContext, есть какие-либо подсказки, что может пойти не так при использовании IHubContext?

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

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

2 ответа

Решение

После долгих поисков в нескольких направлениях я наконец нашел решение.

Если вы используете настраиваемый Resolver зависимостей в HubConfiguration, как я сделал. Например, реализация из хикалкана:

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

ты больше не можешь использовать

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

если вы также не установите свой GlobalHost.DependencyResolver для экземпляра WindsorDependencyResolver или не разрешите вручную ссылку на 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();

или же

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

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

Во-первых, вы зарегистрировали EntryChangeEventHandler для DI? Если нет, реализуйте также интерфейс ITransientDependency для EntryChangeEventHandler.

Ваша проблема может быть связана с сериализацией. Это не может сериализовать eventData.Entry. Вы можете попробовать отправить еще один объект DTO.

Также вы можете реализовать

IEventHandler<EntityChangedEventData<Project>>

для прослушивания всех изменений в объекте Project (включая вставку, обновление и удаление). Проект - это просто образец сущности здесь.

В первом случае TestHub может не работать, если он не зарегистрирован в DI. Вы также можете реализовать ITransientDependency для класса TestHub. И вы должны сделать SignalR, чтобы получить его из контейнера DI. Вы можете использовать для этого такой класс:

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

И затем установите его при запуске:

GlobalHost.DependencyResolver = new WindsorDependencyResolver ();

Может быть, мой ответ был немного запутанным:) Я надеюсь, вы понимаете это.

Другие вопросы по тегам