Send signalr message from server to all clients - c#

This is related to SignalR + posting a message to a Hub via an action method, but my question is a bit different:
I'm on version 0.5.2 of signalr, using hubs. In older versions, you were encouraged to create methods on the hub to send messages to all clients, which is what I have:
public class MyHub : Hub
{
public void SendMessage(string message)
{
// Any other logic here
Clients.messageRecieved(message);
}
...
}
So in 0.5.2, I want to send a message to all the clients (say from somewhere in the controller). How can I get access to the MyHub instance?
The only way I've seen referenced is:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
hubContext.Clients.messageRecieved("hello");
Which is fine, but I want to call the method on my hub.

You can do this by using a static method:
SignalR v.04-
public class MyHub : Hub
{
internal static void SendMessage(string message)
{
var connectionManager = (IConnectionManager)AspNetHost.DependencyResolver.GetService(typeof(IConnectionManager));
dynamic allClients = connectionManager.GetClients<MyHub>();
allClients.messageRecieved(message);
}
...
}
SignalR 0.5+
public class MyHub : Hub
{
internal static void SendMessage(string message)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
context.Clients.messageRecieved(message);
}
...
}
You can then call this like so:
MyHub.SendMessage("The Message!");
Good article on the SignalR API: http://weblogs.asp.net/davidfowler/archive/2012/05/04/api-improvements-made-in-signalr-0-5.aspx
Provided by Paolo Moretti in comments

I had same problem, in my example addNotification is client-side method:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<SignalR.NotificationsHub>();
hubContext.Clients.addNotification("Text here");
On you client side you can add code to call your hub method in addNotification:
var notification = $.connection.notificationHub;
notification.addNotification = function (message) {
notification.addServerNotification(message); // Server Side method
}
$.connection.hub.start();
Hub:
[HubName("notificationHub")]
public class NotificationsHub : Hub
{
public void addServerNotification(string message)
{
//do your thing
}
}
UPDATE:
Reading your question over and over agian I really don't find a reason to do that. Hub methods are usually there to be called from client side, or I misunderstood you, anyway here is an update. If you want to do a server side thing and then notify clients.
[HttpPost]
[Authorize]
public ActionResult Add(Item item)
{
MyHubMethodCopy(item);
var hubContext = GlobalHost.ConnectionManager.GetHubContext<SignalR.NotificationsHub>();
hubContext.Clients.addNotification("Items were added");
}
private void MyHubMethodCopy(Item item)
{
itemService.AddItem(item);
}

Update for ASP.NET Core 2.x and 3.x:
You can easily access the Hub instance anywhere that you have Dependency Injection:
public class HomeController : Controller
{
private readonly IHubContext<MyHub> _myHubContext;
public HomeController(IHubContext<MyHub> myHubContext)
{
_myHubContext = myHubContext;
}
public void SendMessage(string msg)
{
_myHubContext.Clients.All.SendAsync("MessageFromServer", msg);
}
}
If it gives you syntax errors, make sure you have:
using Microsoft.AspNetCore.SignalR;
and that you do NOT HAVE:
using Microsoft.AspNet.SignalR;

Related

C# gRPC with GrpcDotNetNamedPipes -- how to check if Client is connected to service?

I am using C# gRPC with GrpcDotNetNamedPipes to do interprocess communication on the same machine.
Currently, I am having a problem in that if my service is not set up, and I invoke a service request from my client side -- the client side just locks up waiting until the service is available.
I am unsure how to check if client has been connected to the service.
Example code below:
///Autogenerated gRPC code that contains COM API
public static partial class PluginCOM
{
public partial class PluginCOMClient : grpc::ClientBase<PluginCOMClient>
{
//Autogenerated code from protofile
//...
}
}
/// Client class that
public class PluginClient : PluginCOM.PluginCOMClient
{
public PluginClient() : base(new GrpcDotNetNamedPipes.NamedPipeChannel(".", "Service"))
{
}
public bool Test() => Test(new Empty()).Loaded;
}
public static class Tester
{
static void Test()
{
client = new PluginClient();
client.Test(); /// Deadlocks here and waits until service is available
}
}
Calling the Tester.Test() function dead locks when attempting to call client.Test().
I would like something like:
public static class Tester
{
static void Test()
{
client = new PluginClient();
if (/* code here to check if client is connected */)
{
client.Test();
}
}
}
I've had the same problem, but after looking at issues on official GitHub page finally found the solution:
https://github.com/cyanfish/grpc-dotnet-namedpipes/issues/17
By default connection timeout is set by Infinite, but you can set up timeout by yourself using NamedPipeChannelOptions:
public PluginClient() : base(new GrpcDotNetNamedPipes.NamedPipeChannel(".","Service",
new GrpcDotNetNamedPipes.NamedPipeChannelOptions { ConnectionTimeout = 350 }))
{
}

SignalR .NET Client connecting to Azure SignalR Service in a Blazor .NET Core 3 application

I'm trying to make a connection between my ASP.NET Core 3.0 Blazor (server-side) application and the Azure SignalR Service. I'll end up injecting my SignalR client (service) in to a few Blazor components so they'll update my UI/DOM in realtime.
My issue is that I'm receiving the following message when I call my .StartAsync() method on the hub connection:
Response status code does not indicate success: 404 (Not Found).
BootstrapSignalRClient.cs
This file loads my configuration for the SignalR Service including the URL, connection string, key, method name, and hub name. These settings are captured in the static class SignalRServiceConfiguration and used later.
public static class BootstrapSignalRClient
{
public static IServiceCollection AddSignalRServiceClient(this IServiceCollection services, IConfiguration configuration)
{
SignalRServiceConfiguration signalRServiceConfiguration = new SignalRServiceConfiguration();
configuration.Bind(nameof(SignalRServiceConfiguration), signalRServiceConfiguration);
services.AddSingleton(signalRServiceConfiguration);
services.AddSingleton<ISignalRClient, SignalRClient>();
return services;
}
}
SignalRServiceConfiguration.cs
public class SignalRServiceConfiguration
{
public string ConnectionString { get; set; }
public string Url { get; set; }
public string MethodName { get; set; }
public string Key { get; set; }
public string HubName { get; set; }
}
SignalRClient.cs
public class SignalRClient : ISignalRClient
{
public delegate void ReceiveMessage(string message);
public event ReceiveMessage ReceiveMessageEvent;
private HubConnection hubConnection;
public SignalRClient(SignalRServiceConfiguration signalRConfig)
{
hubConnection = new HubConnectionBuilder()
.WithUrl(signalRConfig.Url + signalRConfig.HubName)
.Build();
}
public async Task<string> StartListening(string id)
{
// Register listener for a specific id
hubConnection.On<string>(id, (message) =>
{
if (ReceiveMessageEvent != null)
{
ReceiveMessageEvent.Invoke(message);
}
});
try
{
// Start the SignalR Service connection
await hubConnection.StartAsync(); //<---I get an exception here
return hubConnection.State.ToString();
}
catch (Exception ex)
{
return ex.Message;
}
}
private void ReceiveMessage(string message)
{
response = JsonConvert.DeserializeObject<dynamic>(message);
}
}
I have experience using SignalR with .NET Core where you add it so the Startup.cs file using .AddSignalR().AddAzureSignalR() and map a hub in the app config and doing it this way requires certain 'configuration' parameters to be established (i.e. connection string).
Given my situation, where does HubConnectionBuilder get the connection string or a key to authenticate to the SignalR Service?
Is it possible the 404 message is a result of the missing key/connection string?
Okay so it turns out the documentation is lacking a key piece of information here. If you're using the .NET SignalR Client connecting to the Azure SignalR Service, you need to request a JWT token and present it when creating the hub connection.
If you need to authenticate on behalf of a user you can use this example.
Otherwise, you can set up a "/negotiate" endpoint using a web API such as an Azure Function to retrive a JWT token and client URL for you; this is what I ended up doing for my use case. Information about creating an Azure Function to get your JWT token and URL can be found here.
I created a class to hold these two values as such:
SignalRConnectionInfo.cs
public class SignalRConnectionInfo
{
[JsonProperty(PropertyName = "url")]
public string Url { get; set; }
[JsonProperty(PropertyName = "accessToken")]
public string AccessToken { get; set; }
}
I also created a method inside my SignalRService to handle the interaction with the web API's "/negotiate" endpoint in Azure, the instantiation of the hub connection, and the use of an event + delegate for receiving messages as follows:
SignalRClient.cs
public async Task InitializeAsync()
{
SignalRConnectionInfo signalRConnectionInfo;
signalRConnectionInfo = await functionsClient.GetDataAsync<SignalRConnectionInfo>(FunctionsClientConstants.SignalR);
hubConnection = new HubConnectionBuilder()
.WithUrl(signalRConnectionInfo.Url, options =>
{
options.AccessTokenProvider = () => Task.FromResult(signalRConnectionInfo.AccessToken);
})
.Build();
}
The functionsClient is simply a strongly typed HttpClient pre-configured with a base URL and the FunctionsClientConstants.SignalR is a static class with the "/negotiate" path which is appended to the base URL.
Once I had this all set up I called the await hubConnection.StartAsync(); and it "connected"!
After all this I set up a static ReceiveMessage event and a delegate as follows (in the same SignalRClient.cs):
public delegate void ReceiveMessage(string message);
public static event ReceiveMessage ReceiveMessageEvent;
Lastly, I implemented the ReceiveMessage delegate:
await signalRClient.InitializeAsync(); //<---called from another method
private async Task StartReceiving()
{
SignalRStatus = await signalRClient.ReceiveReservationResponse(Response.ReservationId);
logger.LogInformation($"SignalR Status is: {SignalRStatus}");
// Register event handler for static delegate
SignalRClient.ReceiveMessageEvent += signalRClient_receiveMessageEvent;
}
private async void signalRClient_receiveMessageEvent(string response)
{
logger.LogInformation($"Received SignalR mesage: {response}");
signalRReservationResponse = JsonConvert.DeserializeObject<SignalRReservationResponse>(response);
await InvokeAsync(StateHasChanged); //<---used by Blazor (server-side)
}
I've provided documentation updates back to the Azure SignalR Service team and sure hope this helps someone else!
Update: the sample with the serverless sample is deprecated for the management SDK (sample). The management SDK uses a negotiation server.

Client not receiving SignalR message from controller

I am trying to send a message from a controller to client(s) group using SignalR.
When an event occurs in one of my controllers, I need to push a message using a signalr hub to be displayed like an alert on my clients screens.
i know a lot of questions have been asked here, and I've read and tried a lot of them. Since I am new to SignalR some of them even already helped me to put things in place.
At the moment everything seems in place. Clients can connect to the hub and join groups and controller can call methods from the hub. But the client never receive the message and I can't figure out why. I suspect that hub's method called by controller don't "see" the clients but I can't understand what's wrong.
Hub's code :
public static class UserHandler
{
public static HashSet<string> ConnectedIds = new HashSet<string>();
}
[HubName("myHub")]
public class MyHub : Hub
{
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
public void Notify(string groupName, string message)
{
Clients.Group(groupName).displayNotification(message);
}
public static void Static_Notify(string groupName, string message)
{
var toto = UserHandler.ConnectedIds.Count();
hubContext.Clients.Group(groupName).displayNotification(message);
hubContext.Clients.All.displayNotification(message);//for testing purpose
}
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task LeaveGroup(string groupName)
{
return Groups.Remove(Context.ConnectionId, groupName);
}
public override Task OnConnected()
{
UserHandler.ConnectedIds.Add(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool StopCalled)
{
UserHandler.ConnectedIds.Remove(Context.ConnectionId);
return base.OnDisconnected(StopCalled);
}
}
Here is the call from my controller :
//(For simplification and readability I define here variables actually obtained by treating some data )
//I already checked that problem did not come from missing data here
string groupName = "theGroupName";
string message = "My beautifull message.";
//prepare signalR call
var context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
//Following commented lines are different attempts made based on exemples and answers I found here and on others sites.
//The uncommented one is the one in use at the moment and is also based on an answer (from SO i think)
//context.Clients.Group(_userEmail).displayNotification(message);
//context.Clients.Group(_userEmail).Notify(_userEmail,message);
MyHub.Static_Notify(_userEmail, message);
And here is the client-side code :
$(document).ready(function () {
var userGroup = 'theGroupName';
$.connection.hub.url = 'http://localhost/SignalRHost/signalr';
var theHub = $.connection.myHub;
console.log($.connection)
console.log($.connection.myHub)
theHub.client.displayNotification = function (message) {
console.log('display message');
alert(message);
};
$.connection.hub.start()
.done(function () {
theHub.server.joinGroup(userGroup);
console.log("myHub hub started : " + $.connection.hub.id)
console.log(theHub)
})
.fail(function () {
console.log('myHub hub failed to connect')
});
});
Please help me understand what logic I failed to understand or what error I am missing.
EDIT :
To answer Alisson's comment:
The Startup.cs I forgot to show
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration { };
map.RunSignalR();
});
}
Important stuff I forgot to mention too :
The Controller, the hub, and the client are 3 different projects (because of the global application architecture I had to separate hub logic)
All are on my localhost IIS but on different ports
I've set breakpoints on "OnConnected" and "onDiconnected" events and client connects and disconnects normally
You just need one app to act as server, in your case it should be the SIgnalRHost project. Your controller project should be a client of the server, therefore it just needs this package:
Install-Package Microsoft.AspNet.SignalR.Client
Your controller project doesn't actually need to reference the project containing the hub class. In your controller, you'll use C# SignalR client to connect to the server (as you would do in javascript client), join the group and invoke the hub method:
var hubConnection = new HubConnection("http://localhost/SignalRHost/signalr");
IHubProxy myHub = hubConnection.CreateHubProxy("MyHub");
await hubConnection.Start();
myHub.Invoke("JoinGroup", "theGroupName");
myHub.Invoke("Notify", "theGroupName", "My beautifull message.");
...finally, you don't need that Static_Notify at all.
Since you are passing the group name as a parameter on the Notify
method, you don't really need to join the group from your controller.
You'd need only if you were trying to send the message to the same
group the controller was connected (then you wouldn't need to pass the
group name as parameter anymore).
SignalR C# Client Reference.

Invoke a SignalR hub from .net code as well as JavaScript

I have a SignalR hub, which i successfully call from JQuery.
public class UpdateNotification : Hub
{
public void SendUpdate(DateTime timeStamp, string user, string entity, string message)
{
Clients.All.UpdateClients(timeStamp.ToString("yyyy-MM-dd HH:mm:ss"), user, entity, message);
}
}
update message sent successfully from JS like so
var updateNotification = $.connection.updateNotification;
$.connection.hub.start({ transport: ['webSockets', 'serverSentEvents', 'longPolling'] }).done(function () { });
updateNotification.server.sendUpdate(timeStamp, user, entity, message);
and received successfully like so
updateNotification.client.UpdateClients = function (timeStamp, user, entity, message) {
I can't work out how to call sendUpdate from within my controller.
From your controller, in the same application as the hub (rather than from elsewhere, as a .NET client), you make hub calls like this:
var hubContext = GlobalHost.ConnectionManager.GetHubContext<UpdateNotification>();
hubContext.Clients.All.yourclientfunction(yourargs);
See Broadcasting over a Hub from outside of a Hub near the foot of https://github.com/SignalR/SignalR/wiki/Hubs
To call your custom method is a little different. Probably best to create a static method, which you can then use to call the hubContext, as the OP has here: Server to client messages not going through with SignalR in ASP.NET MVC 4
Here's an example from SignalR quickstart
You need to create a hub proxy.
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.CreateHubProxy("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();
}
}
}

SignalR can't find AddToGroup method

I'm building a hub which should send message to specific user and this Question and this Question would work perfectly, only I can't find AddToGroup method, I know a lot things have changed in version 0.5, is this one of them?
Yes, there's a Groups property now on the Hub class that you could use to add users to. The documentation also illustrates this:
public class MyHub : Hub, IDisconnect
{
public Task Join()
{
return Groups.Add(Context.ConnectionId, "foo");
}
public Task Send(string message)
{
return Clients["foo"].addMessage(message);
}
public Task Disconnect()
{
return Clients["foo"].leave(Context.ConnectionId);
}
}

Categories