Inject signalr hub only by interface

1.1k views Asked by At

So recently I started a project with Ardalis Clean Architecture as template it was all nice but when signalR came into my project i can't figure it. I'm trying to inject interface that my hub implements and call it's method, but everytime when it's called it throws NullReferenceException, it seems like all of the signalR components are null within this injected interface. Registered all hubs and registered it's interfaces using AutoFac. Trying to avoid situation when I'm forced to reference signalR package within core layer.

Core layer:

public class UpdateTimerNotificationHandler : INotificationHandler<UpdateTimerNotification>
{
    private readonly ITimerHub _timerHub;
    public UpdateTimerNotificationHandler(ITimerHub timerHub)
    {
        _timerHub = timerHub;
    }

    public Task Handle(UpdateTimerNotification notification, CancellationToken cancellationToken)
    {
        return _timerHub.UpdateTimerAsync(notification);
    }
}
public interface ITimerHub
{
    Task UpdateTimerAsync(UpdateTimerNotification updateTimerNotification);
}

Infrastructure layer:

public class TimerHub : Microsoft.AspNetCore.SignalR.Hub, ITimerHub
{
    private readonly IAccountRepository _accountRepository;
    public TimerHub(IAccountRepository accountRepository)
    {
        _accountRepository = accountRepository;
    }

    public Task UpdateTimerAsync(UpdateTimerNotification updateTimerNotification)
    {
        return Clients.All.SendAsync("UpdateTimer", updateTimerNotification);
    }
}
private void RegisterHubs(ContainerBuilder builder)
    {
        foreach (var assembly in _assemblies)
        {
            builder.RegisterHubs(assembly);
        }
        builder.RegisterType<TimerHub>().As<ITimerHub>();
    }

Web layer:

builder.Host.ConfigureContainer<ContainerBuilder>(containerBuilder =>
{
    containerBuilder.RegisterModule(new DefaultCoreModule());
    containerBuilder.RegisterModule(
        new DefaultInfrastructureModule(builder.Environment.EnvironmentName == "Development"));
});

builder.Logging.ClearProviders();
builder.Logging.AddConsole();

var app = builder.Build();
GlobalHost.DependencyResolver = new AutofacDependencyResolver(app.Services.GetAutofacRoot());

I was trying manually registering hubs with no luck, still same issue

2

There are 2 answers

2
Train On BEST ANSWER

The good news is SignalR already implements IHubContext<T> In your case you don't need to inject ITimerHub interface. If your TimerHub Already Implements ITimerHub that's good enough In your case it would look like this

public class HomeController : Controller
{
    private readonly IHubContext<TimerHub> _hubContext;

    public HomeController(IHubContext<TimerHub> hubContext)
    {
        _hubContext = hubContext;
    }
}

Also you didn't show your startup.cs class.

    public void ConfigureServices(IServiceCollection services)
    {
        ...
        services.AddSignalR();
        ...
    }

and

 public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
 {
     ...
     app.MapHub<TimerHub>("/yourEndPointGoesHere");
 }

If you really wanted to, which I don't recommend is [look at it here][1] There is an example on using IHubContext in generic code.

I understand you're trying to learn something new. And yes, it's important to decouple application so you're headed in the right direction in what you want to achieve. However I wouldn't recommend this approach you are taking. His approach doesn't apply to 99% of the projects out there. Let me explain my point of view. Don't get pulled in by the buzz words in his videos and blogs. It's important to understand that these principals are SUBJECTIVE to your application.

  1. You don't have 15,000 classes, services, views, and N Layers etc... in your app.

  2. You don't need the flexibility of a domain driven approach. I've seen massive and I mean massive projects, ones that are 25 years old and have millions of lines of code. Let me tell you you're not swapping out your data layer all willy nilly like he makes it seem to be. On a big project there is no "it makes it easy" way to do that. Putting it in Repos and a data access layer doesn't really help. You can put in a data access layer, or in your services. You still need to test out 150,000 lines of code. The only time it's been useful for me is when I've had 4 data sources all having a getBy... function that needs to aggregate info from 4 sources. You don't need it for unit testing either. Just create a mock variable in your unit tests no need to mock your db connection. I find it more useful to have your unit tests actually hooked up to a database even though it's a dependency, it's actually useful.

  3. He said it himself "You can go with a minimalist API and work your way up from there" Which is what you should do. What's the point of SOLID and Repos in a project with no code? For example the I in solid is implementation of interfaces. Interfaces do 2 things -

A. Tell your application what it should and shouldn't do. so, what are you enforcing that could break or needs this kind of abstraction?

B. Decouple the application. Where do you have 3+ different classes being injected in one piece of code with the same DoSomething() based on the type?

He touches over other things that only apply when you have 500 different things going on, and his case it's still overkill.

If you want to break it up you can take a simple approach.

-MainApiProject
-ServicesProject (you can also put interfaces in here)
-InterfacesProject(if you need them between multiple projects and have a lot of them)
-UtilitiesProject

Then look at what he's doing and if you see you need it take it. I can go on but this is getting long as is. [1]: https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-6.0

0
Wowo Ot On

This is what worked for me use (both Hub and Interface):

private readonly IHubContext<MessageHub,IMsgHub> HubContext;

public BMController(IHubContext<MessageHub, IMsgHub> ctx)
{
    HubContext = ctx;
}