3

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

ISmellYu
  • 89
  • 1
  • 7
  • This isn't criticism it's advice. I'll tell you why you can't figure it out. It's because his template doesn't meet your needs and it doesn't make sense to use it. You'll quickly learn that these concepts won't make sense 95% of the time people use them. Do yourself a favor, scrap that template and start over. You don't implement SOLID and Repository patterns and mumbo jumbo and design your code around those by default. When a problem creeps up you implement what you need. You don't have the use case for those in your app. What's the point of of the interface for SignalR? – Train Apr 13 '22 at 01:12
  • @Train I wanted to learn something about clean architecture and SOLID that's why I picked this template, I used to build web apps all contained in 1 BIG project, as the growth of the services etc.. project was already full of stuff, so I though that picking something new can teach me organise stuff into separated projects. About your question mentioning signalr, I wanted to have strongly typed hub and be able to inject it to services by interface – ISmellYu Apr 13 '22 at 19:51

2 Answers2

2

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

Train
  • 3,420
  • 2
  • 29
  • 59
  • Your answer provided me with tons of valuable information about project structure and when to use templates like Clean Architecture, it was like a bucket of cold water on the head. When I was adding features to my app I had to spend like 1 hour(sometimes longer) to think how to fit my feature into the project. I ended up creating "Communication" project with MediatR, "Identity" project and your simple approach, thanks for your detailed answer – ISmellYu Apr 14 '22 at 11:11
  • Glad I could help. I know what it's like learning these concepts and sometimes it's overwhelming. – Train Apr 14 '22 at 15:31
0

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;
}
Wowo Ot
  • 1,362
  • 13
  • 21