I am trying to using SignalR to send a message to all connected clients.
I have found several examples and added what I think to be all the required bits. I did successfully get my client to connect to my Hub. I cannot get my server to connect to my Hub and send a message to all the connected clients.
When I call DatabaseChangeListener::Notify() it never hits the code in the Hub.
Can anyone suggest what else I need to do?
I am using .NET Core 2.1 preview 2 in web application with React and Redux.
I am using SignalR 1.0.0-preview2-final
I am using SignalR.Client 1.0.0-preview2-final
In Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// remove all other code for this question
services.AddSignalR();
}
public void Configure(IApplicationBuilder app)
{
// remove all other code for this question
app.UseSignalR(routes =>
{
routes.MapHub<SignalRHub>("/hubs/update");
});
}
My Hub
[Authorize]
public class SignalRHub : Hub
{
public async Task Send(string message)
{
await Clients.All.SendAsync("SendMessage", Context.User.Identity.Name, message);
}
}
My class to notify clients
public class DatabaseChangeListener : IDatabaseChangeListener
{
private readonly IHubContext<SignalRHub> _hubContext;
public DatabaseChangeListener(IHubContext<SignalRHub> hubContext)
{
_hubContext = hubContext;
}
public void Notify()
{
_hubContext.Clients.All.SendAsync("SendMessage", "something changed, Yo");
}
}
You need to make the connection to the hub via client side and then by using your _hubContext, you should be able to send a message to the client based off of the connection made to the hub.
Connection to the hub from client side using JS.
const connection = new signalR.HubConnectionBuilder()
.withURL("/YourHub")
.build();
Then after the connection is made, you can make the method to send a message to the client from the server using JS as well.
connection.on("SendMessage", message => {
document.getElementById("IdOfElementToDisplayMessage").innerHTML = message;
});
Finally add:
connection.start().catch(err => console.error(err.toString()));
Now you have established the connection to the hub via client side and can now reference the connection to the hub from the IHubContext.
To send a message from the server to the client you can use _hubContext.
In your case you can call Notify() and then await _hubContext.Clients.All.SendAsync("SendMessage", "something changed, Yo"); which should send your message to the SendMessage method created in JS: connection.on("SendMessage", message => { ...
If your _hubContext variable is null during the execution then the injection of the IHubContext needs to be checked.
Related
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.
I have a scenario where one of the clients is sending a request to Hub Class method AddMessage, which in turn should broadcast that message to all clients including the one who initiated it.
The problem is that I am able to call the Hub method AddMessage from the client as shown in the following code, but I couldn't find a way to handle the broadcast message on the client side which is initiated in the Hub class using the following line.
Clients.All.NotifyMessageToClients(name, message);
SignalR Hub Class
using System;
using Microsoft.AspNet.SignalR;
using System.Threading.Tasks;
public class SignalRChatHub : Hub
{
public void AddMessage(string name, string message)
{
// Following call is supposed to notify all clients with passed parameters.
// They could have a method called NotifyMessageToClients to fetch the broadcasted message
Clients.All.NotifyMessageToClients(name, message);
}
}
SignalR Client
using System;
using Microsoft.AspNet.SignalR.Client;
public partial class Default : System.Web.UI.Page
{
HubConnection hubConnection;
IHubProxy stockTickerHubProxy;
public Default()
{
hubConnection = new HubConnection("http://localhost:6898/");
stockTickerHubProxy = hubConnection.CreateHubProxy("SignalRChatHub");
}
async public void SendAddNotification(string msgFrom, string msg)
{
// Following line calls Addmessage method in SignalRChatHub class
await stockTickerHubProxy.Invoke("Addmessage", "Ajendra", "Hello StackOverflow");
}
// I might need the method NotifyMessageToClients here... to receive broadcasted message
}
I have some idea about how to achieve the same in jQuery but not in C# by creating a client as I did above. How would I achieve this?
If the above approach doesn't make sense in any way, please suggest me the right one.
You need to listen to events from the server like this:
public partial class Default : System.Web.UI.Page
{
HubConnection hubConnection;
IHubProxy stockTickerHubProxy;
public Default()
{
hubConnection = new HubConnection("http://localhost:6898/");
stockTickerHubProxy = hubConnection.CreateHubProxy("SignalRChatHub");
// listen to server events...
// n is "name" and m is "message", but you can change to "a" and "b" or anything else...
stockTickerHubProxy.On<string, string>("NotifyMessageToClients", (n, m) =>
{
Console.WriteLine("Message received from server. Name: {0} | Message: {1}", n, m);
});
}
// "async" methods should return Task instead of void....
// unless they are event handlers for UI applications...
public async Task SendAddNotification(string msgFrom, string msg)
{
// first, start the connection...
await stockTickerHubProxy.Start();
// Following line calls Addmessage method in SignalRChatHub class
await stockTickerHubProxy.Invoke("Addmessage", "Ajendra", "Hello StackOverflow");
// you don't stop the connection, otherwise you won't be able to receive calls from the server
}
}
...if you need to update UI in WPF, for example, you should implement your event like this:
stockTickerHubProxy.On<string, string>("NotifyMessageToClients", (a,b) =>
Dispatcher.InvokeAsync(() =>
{
// update UI...
textBox.Text += string.Format("Name: {0} | Message: {1}", a, b);
})
);
I suggest reading this guide for deeper details.
I'd like to know if an Hub method called from js client is
executed completely also if client disconnected during execution
For example..
public virtual void Join(int userId)
{
using (var context = new HubConnectionContext(this, RoomId, userId))
{
T workflow = GetWorkflow(context);
workflow.OnUserJoin();
}
}
I can be sure that the Dispose Method of HubConnectionContext is called also if client disconnect during using block?
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.
Is it possible to host SignalR as a part of WCF websocket service and not as a part of ASP.net web site. I am aware about pushing mesage from a web service to signalR clients but is it also possible tht when the socket connection is opened from browser it maps to a web serivce contract?
You can self-host the SignalR server:
Taken from (https://github.com/SignalR/SignalR/wiki/QuickStart-Hubs):
Getting Started
To get started, Install the following packages:
Install-Package Microsoft.Owin.Hosting -pre
Install-Package Microsoft.Owin.Host.HttpListener -pre
Install-Package Microsoft.AspNet.SignalR.Owin -pre
using System;
using Microsoft.AspNet.SignalR;
using Microsoft.Owin.Hosting;
using Owin;
namespace SignalR.Hosting.Self.Samples
{
class Program
{
static void Main(string[] args)
{
string url = "http://172.0.0.01:8080";
using (WebApplication.Start<Startup>(url))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
// This will map out to http://localhost:8080/signalr by default
// This means a difference in the client connection.
app.MapHubs();
}
}
public class MyHub : Hub
{
public void Send(string message)
{
Clients.All.addMessage(message);
}
}
}
You can host the SignarR hub in any .Net application, like:
public class Program
{
public static void Main(string[] args)
{
// Connect to the service
var hubConnection = new HubConnection("http://localhost/mysite");
// Create a proxy to the chat service
var chat = hubConnection.CreateProxy("chat");
// Print the message when it comes in
chat.On("addMessage", message => Console.WriteLine(message));
// Start the connection
hubConnection.Start().Wait();
string line = null;
while((line = Console.ReadLine()) != null)
{
// Send a message to the server
chat.Invoke("Send", line).Wait();
}
}
}
Ref: https://github.com/SignalR/SignalR/wiki/QuickStart-Hubs
If there any specific reason you want to use WCF? you can write your service as SignarR hub only.