SignalR: Using the caller method outside of the hub context - asp.net

I am trying to use the Caller method outside my Hub Context. I have a helper class which works fine when broadcasting a message to all users like so:
hub.Clients.All.newLessonAlert(notif);
It won't allow me to use the Caller method within this class but this works fine in the hub context class. Why is this? I have also tried to move all of my functions inside the context class but I now get this unhanded exception:
Using a Hub instance not created by the HubPipeline is unsupported
Is there a straightforward way to continue to use my helper class and identify connections to the hub?

I solved this in the following way:
I created a OnConnected method in my Hub class. This assigned the currently connected user to a group.
[HubName("NotificationsHub")]
public class NotificationHub : Hub
{
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
public override Task OnConnected()
{
string userid = Context.Request.User.Identity.GetUserId();
Groups.Add(Context.ConnectionId, userid);
return base.OnConnected();
}
}
Modified my HubHelper class to now broadcast this alert to the currently connected user (specified by the user's ID)
public void HighScoreAlert(int gameid, int score, string userID)
{
string message = "High Score achieved on " + gameid;
hub.Clients.Group(userID).score(message);
}
For the controller action I pass in the user's ID and then call the HubHelper method featured above.
Hope this helps someone

Related

Is there a way to avoid using magic strings with the HubConnection class

I have a strongly typed Hub on the server:
public Foo : Hub<Bar> {}
Bar is supposed to be an interface including methods available on the client side. But that solves only half of the problem (the server half). On the client side, I still have to use magic strings to define handlers for calls to the methods of Bar:
hubConnection.On<int>("MethodInsideBar", param => DoSomething(param));
Is there a way to avoid doing this ? Shouldn't there be a way to implement Bar client side and link the calls from the server to that implementation ?
You can use the SignalR.Strong NuGet
Sample Code:
Foo.cs
public interface IBar
{
Task MethodInsideBar(int n);
}
public class Foo : Hub<IBar> {}
Client.cs:
public class MySpoke : IBar
{
public Task MethodInsideBar(int n)
{
//
return Task.CompletedTask;
}
}
var conn = new SignalR.Client.HubConnection()
.WithUrl("http://localhost:53353/MyHub")
.Build();
await conn.StartAsync();
var registration = conn.RegisterSpoke<IBar>(new MySpoke())
BlazorPage.razor
#using Microsoft.AspNetCore.SignalR.Client
#using SignalR.Strong
#inject NavigationManager Nav
#implements IBar
#code {
private HubConnection? hubConnection;
public Task MethodInsideBar(int n)
{
//
return Task.CompletedTask;
}
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(Nav.ToAbsoluteUri("/foo"))
.WithAutomaticReconnect()
.Build();
await hubConnection.StartAsync();
hubConnection.RegisterSpoke<IBar>(this);
await base.OnInitializedAsync();
}
}
server.cs
public class FooBar
{
private readonly IHubContext<Foo, IBar>? _hubContext;
// dependency injected IHubContext
public FooBar(IHubContext<Foo, IBar>? hubContext)
{
_hubContext = hubContext;
}
public void CallBar(int n)
{
_hubContext?.Clients.All.MethodInsideBar(n);
}
}
On the client side, I still have to use magic strings to define
handlers for calls to the methods of Bar:
hubConnection.On<int>("MethosInsideBar", param => DoSomething(param));
Is there a way to avoid doing this ? Shouldn't
there be a way to implement Bar client side and link the calls from
the server to that implementation ?
As far as I know, the Strongly typed hubs only apply to the server side, we could inject the strongly-typed HubContext in the controller, then, call the hub method. It can prevent the method name is misspelled or missing from the client.
On the client side, we still need to use the Invoke method call the public methods on hubs, and define a method using the on method of the HubConnection to receive messages from the hub.
When calling the public hub methods from client, if you want to use the Strongly typed Hubs, you could inject the Strongly typed hubcontext into the controller, then use JQuery Ajax call the controller's action method, then use the Strongly typed hubs method. Refer this thread: SignalR - Call statically typed hub from Context.

How to create SignalR groups from Blazor app

I have a (serverside) blazor app and I want to let users fill in a small form and press a button to create SignalR groups that they can then send messages to.
I have a Hub class that looks like this:
public class RoomHub : Hub
{
public async Task JoinRoomAsync(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
public async Task LeaveRoomAsync(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
}
public async Task BroadcastToRoomAsync(string groupName, string message)
{
await Clients.Group(groupName).SendAsync("OnMessage", message);
}
}
and a Service class that gets called from my blazor component, which looks like this:
public class RoomService : IRoomService
{
private ICosmosDbService _dbService;
private RoomHub _roomHub;
public RoomService(ICosmosDbService dbService, RoomHub roomHub)
{
this._dbService = dbService;
this._roomHub = roomHub;
}
public async Task<Room> CreateRoom(string name)
{
Room r = new Room();
r.Id = Guid.NewGuid().ToString();
r.Name = name;
await _dbService.AddItemAsync(r);
await _roomHub.JoinRoomAsync(r.Name);
return r;
}
public async Task SendToRoom(Room r, string message)
{
await _roomHub.BroadcastToRoomAsync(r.Name, message);
return;
}
}
When I add the RoomHub class to my services in Startup.cs and run my application, when I press the button to create a Group it tells me the Hub's Context variable is null and fails.
I've tried looking around for other ways to do this, and arrived at the conclusion that it has something to do with injecting an IHubContext<RoomHub> object instead, but the object this provides does not seem related at all to my Hub class and I can't use it to create groups directly because I don't have access to the ConnectionId I need to do so.
I feel like there's a gap between the Hub and HubContext that I do not understand. What is the correct way to create a SignalR Group, starting from a button press on a Blazor component?
Before you can access your Hub, you need to build and start your Hub connection using HubConnection and HubConnectionBuilder. This needs to include the url for your Hub and the handler methods for the data received from the Hub.
Start by adding a HubConnection field in your Service class.
private HubConnection _hubConnection;
Depending on your Service lifetime and other considerations, you can build your connection in the Service class constructor or it's own method. For an example, we'll add a StartConnectionAsync task.
public async Task StartConnectionAsync()
{
// Create the connection
_hubConnection = new HubConnectionBuilder()
.WithUrl(_hubUrl) // _hubUrl is your base Url + Hub Url
.Build();
// Add Handler for when a client receives a broadcast message
_hubConnection.On<string>("OnMessage", this.SomeEventHandler);
// Then you start the connection
await _hubConnection.StartAsync();
}
Without using a typed Hub, you'll call your Hub methods using magic strings. e.g.
await _hubConnection.SendAsync("JoinRoomAsync", groupName);
This should get you started. Based on what you posted above, I think this github repo is similar to what you're intending to do.

Send messages from server to client via SignalR in .NET Core

I want to send messages from the server (from a class, not a controller) via the SignalR Hub.
The hub works for messages originating from the client but not for messages from the server.
I've tried multiple methods of which non seem to work. For example, I tried retrieving the hub context using:
GlobalHost.ConnectionManager.GetHubContext<MyHub>()
with no success.
What is the best and up-to-date method of doing this in .NET Core?
Temporary Solution:
Having a websocket client inside the host api. Then making it connect to itself. This is not an ideal solution but works as a temporary fix.
You can inject the context in your class as service. Your class must be initialized via DI and added as a service. There is no difference between class or controller.
public class SomeClass
{
public IHubContext<ChatHub, IChatClient> _strongChatHubContext { get; }
public SomeClass(IHubContext<ChatHub, IChatClient> chatHubContext)
{
_strongChatHubContext = chatHubContext;
}
public async Task SendMessage(string message)
{
await _strongChatHubContext.Clients.All.ReceiveMessage(message);
}
}
You can also get service like following by injecting IHttpContextAccessor
var _strongChatHubContext = httpContextAccessor.HttpContext.RequestServices.GetRequiredService<IHubContext<ChatHub, IChatClient>>()
reference:
https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-2.1
First you need to somewhere instantiate your hub (normally when your app is bootstrapping).
MyHub myHub = new MyHub();
Then on your class inject the context:
private readonly IHubContext<NotifyHub, ITypedHubClient> hubContext;
And in your class method just call the hub:
hubContext.Clients.All.yourHubMethod(yourPayload);

Broadcasting message to client at scheduled time using SignalR

I want to broadcast message using SignalR from server to all clients at particular server time. This broadcast message should be automatic where all clients keep listening to server method and once we reach the scheduled time on server, server should broadcast the message.
But I don't find a way to this. I was successful by invoking from a client and broadcasting it to all clients but not automatically.
How this can be achieved.
First you need some sort of hub:
public class MyHub : Hub {}
You then can connect to this hub with clients or whatever you want to do. Then you need some type of looping class that sends to your hub (you could also do this within the hub as a static timer but we'll do this as a separate class).
public class Looper
{
public Timer Interval
public Looper(TimeSpan interval)
{
Interval = new Timer(Loop, null, TimeSpan.Zero, interval);
}
private static void Loop(object state)
{
GlobalHost.ConnectionManager.GetHubContext<ChatHub>().Clients.All.executeYourMethod();
}
}
Now we need to instantiate this class somewhere, we'll do that inside our hub as a static:
public class MyHub : Hub
{
public static Looper MyInterval = new Looper(TimeSpan.FromSeconds(1)); // Send every 1 second
}
Hope this helps!
You can just add a timer to your hub class and broadcast to each client at the required intervals.

How to use SignalR hub instance outside of the hubpipleline

I am using SignalR to broadcast messages to all my clients. I need to trigger the broadcasting outside of my hub class i.e. something like below:
var broadcast = new chatHub();
broadcast.Send("Admin","stop the chat");
I am getting error message as:
Using a Hub instance not created by the HubPipeline is unsupported.
You need to use GetHubContext:
var context = GlobalHost.ConnectionManager.GetHubContext<chatHub>();
context.Clients.All.Send("Admin", "stop the chat");
This is described in more detail at http://www.asp.net/signalr/overview/signalr-20/hubs-api/hubs-api-guide-server#callfromoutsidehub.
A small update for those who might be wondering where the GlobalHost has gone. SignalR has been completely rewritten for .net core. So if you are using the SignalR.Core package (Difference between SignalR versions), you get an instance of SignalR hub context by injecting it into your service:
public class MyNeedyService
{
private readonly IHubContext<MyHub> ctx;
public MyNeedyService(IHubContext<MyHub> ctx)
{
this.ctx = ctx;
}
public async Task MyMethod()
{
await this.ctx.All.SendAsync("clientCall");
}
}
And in Startup.cs:
services.AddSignalR()/*.AddAzureSignalR("...")*/;
Microsoft docu is here: Send SignalR messages from outside the hub.

Resources