Hendel SignalR Hub Methods in client side (js file) - c#

Iam using SignalR to create my first chat App
every thing goes well but the problem is when I want to send message from one client to another not to all clients
the question is how can I target just one client by its connection id or user name ?
this is My server side code (Hub class)
i just want to know how to handel these methods in js to specify any client i want to send message to
public async Task SendMessageUser(string user, string message)
{
// here you need to set a specifc the user
await Clients.User("username").SendAsync("ReceiveMessage", user, message);
}
public async Task SendMessageToClient(string user, string receiverConnectionId, string message)
{
await Clients.Client(receiverConnectionId).SendAsync("ReceiveMessage", user, message);
}
public async Task SendMessageUsers(string user, string message)
{
// here you need to set the list of Users
await Clients.Users(new string[] { "myUser", "myUser2" }).SendAsync("ReceiveMessage", user, message);
}

Related

Bot Framework send message to different user

I have a bot that does a hand-off to a human operator.
When the operator joins I receive an event in the 'OnEventActivityAsync' event.
At that moment I want to send a message to the client that they're connected to the operator.
How do I change the turnContext/Activity to send the message to the client?
Not entirely sure which part of code to post. (can't post entire project due to company policies)
protected override async Task OnEventActivityAsync(ITurnContext<IEventActivity> turnContext, CancellationToken cancellationToken)
{
if (turnContext.Activity.Name == "webchat/agentJoin")
{
_logger.LogInformation("webchat/agentJoin");
//Database stuff here
await turnContext.SendActivityAsync(MessageFactory.Text("Connected to customer"), cancellationToken);
//How do I send a message to the client here? turnContext sends the message to the agent, not client
}
}
Please See protocol definition in the below GitHub link experimentations:
https://github.com/microsoft/BotBuilder-Samples/tree/master/experimental/handoff-library
Factory Methods
SDK will include the following factory method to aid in creation of the events specified:
C#:
namespace Microsoft.Bot.Builder
{
public static class EventFactory
{
public static IEventActivity CreateHandoffInitiation(
ITurnContext turnContext,
object handoffContext,
Transcript transcript);
public static IEventActivity CreateHandoffResponse(
ConversationAccount Conversation,
string code);
public static IEventActivity CreateHandoffCompleted(
ConversationAccount Conversation,
string code,
Transcript transcript);
}
}
Protocol codes are defined as follows:
namespace Microsoft.Bot.Schema
{
public static class HandoffCodes
{
public const string Accepted = "accepted";
public const string Failed = "failed";
public const string TimedOut = "timedOut";
public const string EndOfConversation = "endOfConversation";
public const string TransferBack = "transferBack";
}
}
Thanks to the suggestion by Mick I took another look at proactive messages.
I had looked at it before, but never got it to work because of 'Unauthorized' errors.
Now I noticed there's a fix for that specific problem.
The code is based on the MS documentation for sending proactive notifications:
Send proactive Notifications
So what I do is:
turnContext.Adapter.ContinueConversationAsync(_appId, conversationReference, BotCallback, default(CancellationToken));
private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken)
{
// If you encounter permission-related errors when sending this message, see
// https://aka.ms/BotTrustServiceUrl
await turnContext.SendActivityAsync("You're now connected to: Ted");
}

Obtaining SignalR Hub without losing its clients from "other place in code"

I have app for one person that has to display data on webpage from external device connected via usb port
Before, I gave user button "Start listening" that sent http request to backend which started listening on port (and blocked app, but it was fine because it is supposed to be used by exactly 1 person at time) until it received not-error response (SerialDataReceivedEvent/SerialErrorReceivedEvent)
and SerialData was returned from that request and displayed on page
I have to rewrite this using SignalR, so I quickly came with naive solution like this:
public class DeviceReaderHub : Hub
{
private readonly IConfiguration _config;
// this is static because listening that port (SerialPort) has to stay open
private static DeviceReader_Helper _service;
public DeviceReaderHub(IConfiguration config)
{
_config = config;
if (_service == null)
{
_service = new DeviceReader_Helper();
_service.Open(_config["DeviceInfo:Port"]);
}
_service.DataReceived_Delegate = SendMessage;
_service.ErrorReceived_Delegate = SendErrorMessage;
}
public async Task SendMessage(string message)
{
await Clients.All.SendAsync("onRead", message);
}
public async Task SendErrorMessage(string message)
{
await Clients.All.SendAsync("onRead", $"error = {message}");
}
public async override Task OnConnectedAsync()
{
await Clients.All.SendAsync("onConnected");
await base.OnConnectedAsync();
}
}
but I received reality-check very quickly - I cannot do it like that because DeviceReaderHub is disposed.
Exception thrown: 'System.ObjectDisposedException' in Microsoft.AspNetCore.SignalR.Core.dll
I thought about obtaining new DeviceReaderHub hub whenever SerialDataReceivedEvent or SerialErrorReceivedEvent is being Invoked
but I have not idea how can I get instance of the hub without losing connected clients
SignalR hubs are transient objects, which means that a new hub instance is used for each method call on the hub from the client. According to this Microsoft Docs:
Don't store state in a property on the hub class. Every hub method call is executed on a new hub instance.
So you should not use the hub to do anything other than receiving and handling requests from the client. To send messages to the client outside of the hub, SignalR provides the HubContext<T> class which is available from Dependency Injection. For example:
public class DeviceReader_Helper {
private readonly IHubContext<DeviceReaderHub> _hubContext;
// you can obtain hubContext either from constructor DI, or service locator pattern with an IServiceProvider
public DeviceReader_Helper(IHubContext<DeviceReaderHub> hubContext) {
_hubContext = hubContext;
}
public async Task SendMessage(string message) {
await _hubContext.Clients.All.SendAsync("onRead", message);
}
}
For more information see this. As for your concern that
but I have not idea how can I get instance of the hub without losing connected clients
Clients can be connected without a hub instance. Hubs are only used to receive messages from the client, and are not necessary to keep a client connected to the server.

How can I check how many connections are in a Group in my Azure SignalR service?

I am trying to implement Azure SignalR Service to facilitate two way messaging between a desktop, asp.net-core and a xamarin.ios apps.
I have created a Hub as per Microsoft's documentation here:
https://learn.microsoft.com/en-us/aspnet/core/signalr/hubs?view=aspnetcore-2.2
Hub:
public class ChatHub : Hub
{
public Task SendMessage(string user, string message)
{
return Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
I add users's connections to a group when they connect to the hub as shown here: https://learn.microsoft.com/en-us/aspnet/core/signalr/groups?view=aspnetcore-2.2
Add to Group:
public async Task AddToGroup(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has joined the group {groupName}.");
}
public async Task RemoveFromGroup(string groupName)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName);
await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has left the group {groupName}.");
}
When a message is sent it has a group name as a parameter, I would like to check if the provided group has any connections before sending the message to any registered clients, if there are no connections, I would like to send a push notification (already have working code for the push notification)
Send Message with Notification Fallback:
public class ChatHub : Hub
{
public Task SendMessage(string groupName, string user, string message)
{
var group = Clients.Group(groupName);
// todo: how to check if we have any open connections in this group?
if(group.Conections.Count > 0)
{
return group.SendAsync("ReceiveMessage", user, message);
}
else
{
// todo: run code to send push notification or anything else you might want to do
}
}
}
Problem: I don't see any way to check for the number of connections currently in a group via the available api (correct me if I'm wrong)
I see the group coming back as Microsoft.AspNetCore.SignalR.Internal.GroupProxy<ChatHub> at runtime with no public methods. Internal private variables do include _groupName and _lifeTimeManager and within lifetime manager there is a _clientConnectionManager which i can see has client connections when they are connected but I have no access to any of these, I'm using the Microsoft.Azure.SignalR (1.0.4) Nuget package. Does anyone know if what I'm trying to do possible with this SDK and if so how I can do this?

how to send a binary file to SignalR client from server in dotnet core

We had a solution for sending a file to SignalR client using .Net
We have now moved to .Net Core
In previous .net solution, we used to Hub context via GlobalHost.ConnectionManager
var myHub = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
myHub.Clients.Client(connectionId).doStuffWithFile(fileByteArray, fileName);
where in the client side, function doStuffWithFile would be triggered with the two arguments.
In new .Net Core solution I created a Hub class by deriving from Hub. I added a method of Send to send a file to specific client and not broadcasting it to every one
public class MyHub : Hub
{
private static string _connectionId;
public override Task OnConnectedAsync()
{
_connectionId = Context.ConnectionId;
return Task.CompletedTask;
}
public override Task OnDisconnectedAsync(Exception exception)
{
_connectionId = Context.ConnectionId;
//// remove Connection Id
return base.OnDisconnectedAsync(exception);
}
public async Task Send(byte[] fileByteArray, string fileName)
{
await Clients.Client(_connectionId).InvokeAsync("doStuff", fileByteArray, fileName);
}
}
However, I do not have any mechanism in .Net core such as GlobalHost or ConnectionManager to get HubContext to send the file.
On the client side:
static void Main(string[] args)
{
var connection = new HubConnectionBuilder()
.WithUrl("http://localhost:25786/file")
.WithConsoleLogger()
.Build();
connection.On<byte[], string>("doStuff", DoStuff);
connection.StartAsync().ContinueWith(
task =>
{
if (task.IsFaulted)
{
Console.WriteLine("Connection faulty");
}
});
Console.ReadLine();
}
private static void DoStuff(byte[] data, string name)
{
File.WriteAllBytes(#"c:\Projects\" + name, data);
}
I tried to create a new instance of MyHub to invoke the Send method, but simply it does not work. Could you please advise me how to do this?
This is not a direct answer to your question but hopefully it will help you find a solution.
Storing connection Id in a static class variable is not correct. It will change each time a new client connects and you won't have any control over which client you are sending to. From the code you provided it is not clear how you know which client to send the file to. Note that you also set the _connectionId when the client is disconnected so it is likely that you will try to send data to the connection you know has been closed. I actually think you will want to pass the target connection id or the user to the hub Send method. I think you may not have enough context in the hub method itself to resolve the connection id but since connection id is a SignalR concept it might be hard to access it outside SignalR components. This is why it might be easier to use the user instead of connection Id (i.e. Clients.User(...) instead of Clients.Client(...)).
GlobalHost no longer exists in the new SignalR. Instead you can inject IHubContext<THub> to the class you want to invoke the method from and use InvokeAsync. Here is an example of invoking a hub method from an Mvc Controller.

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();
}
}
}

Categories