Invoking SignalR Hub not working for Asp.Net Core Web API
Asked Answered
C

3

6

I'm a newb to SignalR. I'm trying to set up a Asp.Net Core WebAPI so that other clients can connect to it using SignalR and get real-time data. My Hub class is:

public class TimeHub : Hub
{
    public async Task UpdateTime(string message)
    {
        await Clients.All.SendAsync("ReceiveMessage", message);
    }
}

I have a relay class as follows:

public class TimeRelay : ITimeRelay
{
    private readonly IHubContext<TimeHub> _timeHubContext;

    public TimeRelay(IHubContext<TimeHub> context)
    {

        _timeHubContext = context;
        Task.Factory.StartNew(async () =>
        {
            while (true)
            {
                await context.Clients.All.SendAsync("UpdateTime", DateTime.Now.ToShortDateString());
                Thread.Sleep(2000);
            }
        });
    }
}

Startup class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

    services.AddSignalR();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseDeveloperExceptionPage();

    app.UseHttpsRedirection();

    app.UseSignalR((x) =>
    {
        x.MapHub<TimeHub>("/timeHub");
    });
    app.UseMvc();
}

The client is a console application and the code is:

class Program
{    
    static Action<string> OnReceivedAction = OnReceived;

    static void Main(string[] args)
    {
        Connect();
        Console.ReadLine();
    }

    private static async void Connect()
    {
        var hubConnectionBuilder = new HubConnectionBuilder();

        var hubConnection = hubConnectionBuilder.WithUrl("http://localhost:60211/timeHub").Build();

        await hubConnection.StartAsync();

        var on = hubConnection.On("ReceiveMessage", OnReceivedAction);

        Console.ReadLine();    
        on.Dispose();
        await hubConnection.StopAsync();
    }

    static void OnReceived(string message)
    {
        System.Console.WriteLine($"{message}");
    }
}

I tried debugging the application. The client got connected to the TimeHub succesfully. The no of connections in Clients.All changed from 0 to 1, when the client got connected. But, when await context.Clients.All.SendAsync("UpdateTime", DateTime.Now.ToShortDateString()); is executed, the UpdateTime function in TimeHub is not getting executed and the client is not getting any message.

I tried using "UpdateTime", "SendMessage", and "ReceiveMessage" as method in Clients.All.SendAsync in TimeRelay class. Nothing worked. Could someone point out my mistake in this.

Chantey answered 22/8, 2018 at 13:38 Comment(5)
Where did you call TimeRelay? Is TimeRelay and TimeHub in the same project? What is your expected result for TimeRelay? In general, Clients.All.SendAsync, it used to call clients methods intead of server methods. If you perfer to call TimeHub.UpdateTime from TimeRelay, try register TimeHub class and call TimeHub.UpdateTime() or, call ReceiveMessage directly from TimeRelay by await context.Clients.All.SendAsync("ReceiveMessage", DateTime.Now.ToShortDateString());.Gravity
For now, I'm invoking TimeRelay from the HomeController. To answer you. Yes, TimeRelay and TimeHub are in the same project. I will try registering TimeHub. Also, I did try "ReceiveMessage" in Clients.All.SendAsync, but it didn't work.Chantey
I am wondering whether it is reasonable to start a new thread in Controller action. I made a test with invoking TimeHub in controller index action, it will only work for the second request. I will keep checking this behavior.Gravity
@TaoZhou Thanks. Starting the thread in Controller is a just temporary method. Frankly, for now, I was not able to figure out a way to start it another way. To begin with, I was trying to get the SignalR working first.Chantey
@AlenAlex Like mentioned in a comment above Clients.All.SendAsync will call the client method directly. However also mentioned above is instantiating the hub and calling hub methods manually. This is very wrong, never get a hub instance as it will not have the correct properties set up on it. If you want to call a "hub method" from your controller you'll need to refactor your code so the hub method and the controller call the same code, and then you can send to the client with Clients.All.SendAsync from your hub and controller with the data returned by your refactored method.Fulguration
C
2

I got it to work and thought I will answer it here. Thanks @TaoZhou for the tip.

My mistake was sending "UpdateTime" from server and waiting on "ReceiveMessage" at the client.

Ideally the code should look like the following:

SignalR Server:
await context.Clients.All.SendAsync("UpdateTime", DateTime.Now.ToShortDateString());

SignalR Client:
var on = hubConnection.On("UpdateTime", OnReceivedAction);

In this case any message send from the server would be received at the client instantly.
Please refer the code provided in the question for more info.

Chantey answered 4/9, 2018 at 17:19 Comment(0)
G
3

For Clients, it will be null if there is no client connecting to server. For starting Asp.Net Core SignalR and Console App at the same time, the Clients may be null since Index may be called before Console App connects the signalR server.

Try steps below:

  1. Change TimeHub

    public class TimeHub: Hub
    {
    public async Task UpdateTime(string message)
    {
        if (Clients != null)
        {
            await Clients.All.SendAsync("ReceiveMessage", message);
        }
    }
    }
    
  2. Register TimeHub

     services.AddSingleton<TimeHub>();  
    
  3. Controller

     public class HomeController : Controller
    {
    private readonly TimeHub _timeHub;
    public HomeController(TimeHub timeHub)
    {
        _timeHub = timeHub;
    }
    public IActionResult Index()
    {
        Task.Factory.StartNew(async () =>
        {
            while (true)
            {
                try
                {
                    await _timeHub.UpdateTime(DateTime.Now.ToShortDateString());
                    Thread.Sleep(2000);
                }
                catch (Exception ex)
                {
    
                }
            }
        });
        return View();
    }
    
Gravity answered 23/8, 2018 at 8:56 Comment(3)
Hi @TaoZhou. Thanks. This works as required now. But, I believe that this is not the right way of doing it. I believe IHubContext is the right way of doing it. It just doesn't seem to work for me as said in the original question.Chantey
@AlenAlex What about this way? Change TimeRelaylike public TimeRelay(IHubContext<TimeHub> context) and replace the while(true) with if (Clients != null) { await Clients.All.SendAsync("ReceiveMessage", message); }Gravity
what does the service.addsingleton call do?Whitley
C
2

I got it to work and thought I will answer it here. Thanks @TaoZhou for the tip.

My mistake was sending "UpdateTime" from server and waiting on "ReceiveMessage" at the client.

Ideally the code should look like the following:

SignalR Server:
await context.Clients.All.SendAsync("UpdateTime", DateTime.Now.ToShortDateString());

SignalR Client:
var on = hubConnection.On("UpdateTime", OnReceivedAction);

In this case any message send from the server would be received at the client instantly.
Please refer the code provided in the question for more info.

Chantey answered 4/9, 2018 at 17:19 Comment(0)
L
-1

For me, the major mistake was to add ChatHub as follows in Program.cs

builder.Services.AddScoped<ChatHub>();

So I change it to

builder.Services.AddSingleton<NotifyHub>();

To understand dependency injection please have a look at https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-7.0

Lance answered 14/5, 2023 at 11:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.