3

I have an ASP.NET Core MVC project with RabbitMQ (by means of EasyNetQ) and SignalR.

Next, I have a subscription on a RabbitMQ message that should send a notification to the client via SignalR.

But sadly, the hub always resolves to null.

An interesting observation is that when the application is still starting and there are still unacknowledged messages in the queue, the service actually resolves just fine.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSignalR();
    services.RegisterEasyNetQ("host=localhost;virtualHost=/");
}

public void Configure(IApplicationBuilder app)
{
    app.UseSignalR(route =>
    {
        route.MapHub<MyHub>("/mypath");
    });

    app.Use(async (context, next) =>
    {
        var bus = context.RequestServices.GetRequiredService<IBus>();

        bus.SubscribeAsync<MyMessage>("MySubscription", async message =>
        {
            var hubContext = context.RequestServices
                .GetRequiredService<IHubContext<MyHub>>();

            // hubContext is null 
            await hubContext.Clients.All.SendAsync("MyNotification");
        });

        await next.Invoke();
    });
}

I suspect that perhaps I'm doing something wrong with regards to registering the subscription inside an app.Use but I can't seem to find any useful examples so this was the best I could figure.

I'm on ASP.NET Core 3 preview 5, I don't know if that has anything to do with my problem.

So the question is: how do I get the hub context inside the message subscription handler?

UPDATE

I've checked the GetRequiredService docs and the call should actuall throw an InvalidOperationException if the service couldn't be resolved, but it doesn't. It returns null, which as far as I can tell, shouldn't be possible (unless the default container supports registration of null-valued instances).

Sandor Drieënhuizen
  • 6,310
  • 5
  • 37
  • 80

1 Answers1

4

I've managed to solve the issue with help from this issue by implementing an IHostedService instead.

public void ConfigureServices(IServiceCollection services)
{
    services.AddSignalR();
    services.RegisterEasyNetQ("host=localhost;virtualHost=/");
    services.AddHostedService<MyHostedService>();
}

public void Configure(IApplicationBuilder app)
{
    app.UseSignalR(route =>
    {
        route.MapHub<MyHub>("/mypath");
    });    
}

public class MyHostedService : BackgroundService
{
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public ServiceBusHostedService(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var scope = _serviceScopeFactory.CreateScope();
        var bus = scope.ServiceProvider.GetRequiredService<IBus>();

        bus.SubscribeAsync<MyMessage>("MySubscription", async message =>
        {
            var hubContext = scope.ServiceProvider.GetRequiredService<IHubContext<MyHub>>();

            await hubContext.Clients
                .All
                .SendAsync("MyNotification", cancellationToken: stoppingToken);
        });

        return Task.CompletedTask;
    }
}
Sandor Drieënhuizen
  • 6,310
  • 5
  • 37
  • 80
  • Hi, I have a question, why we don't need to put the While loop or Timer Callback in ExecuteAsync for the bus.SubscribeAsync<>(); ? thank you in advance. – Armin Shoeibi May 16 '21 at 10:09
  • 1
    @Armin Shoeibi: since the service is registered as a hosted service, it will not need a loop to prevent the application from stopping. The bus message subscription is a callback that is issued by EasyNetQ. – Sandor Drieënhuizen May 18 '21 at 09:48