Send message to specific user in signalr - c#

I have a signalR Server(Console Application) and a client application(Asp.net MVC5)
How I can send message to specific user in OAuth Membership.
Actually I can't resolve sender user from hub request context with.
Context.User.Identity.Name
My Hub
public class UserHub : Hub
{
#region Hub Methods
public void LoggedIn(string userName, string uniqueId, string ip)
{
Clients.All.userLoggedIn(userName, uniqueId, ip);
}
public void LoggedOut(string userName, string uniqueId, string ip)
{
var t = ClaimsPrincipal.Current.Identity.Name;
Clients.All.userLoggedOut(userName, uniqueId, ip);
}
public void SendMessage(string sendFromId, string userId, string sendFromName, string userName, string message)
{
Clients.User(userName).sendMessage(sendFromId, userId, sendFromName, userName, message);
}
#endregion
}
Start hub class(Program.cs)
class Program
{
static void Main(string[] args)
{
string url = string.Format("http://localhost:{0}", ConfigurationManager.AppSettings["SignalRServerPort"]);
using (WebApp.Start(url))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
}
}
}

Keep connectionId with userName by creating a class as we know that Signalr only have the information of connectionId of each connected peers.
Create a class UserConnection
Class UserConnection{
public string UserName {set;get;}
public string ConnectionID {set;get;}
}
Declare a list
List<UserConnection> uList=new List<UserConnection>();
pass user name as querystring during connecting from client side
$.connection.hub.qs = { 'username' : 'anik' };
Push user with connection to this list on connected mthod
public override Task OnConnected()
{
var us=new UserConnection();
us.UserName = Context.QueryString['username'];
us.ConnectionID =Context.ConnectionId;
uList.Add(us);
return base.OnConnected();
}
From sending message search user name from list then retrive the user connectionid then send
var user = uList.Where(o=>o.UserName ==userName);
if(user.Any()){
Clients.Client(user.First().ConnectionID ).sendMessage(sendFromId, userId, sendFromName, userName, message);
}
DEMO

All of these answers are unnecessarily complex. I simply override "OnConnected()", grab the unique Context.ConnectionId, and then immediately broadcast it back to the client javascript for the client to store and send with subsequent calls to the hub server.
public class MyHub : Hub
{
public override Task OnConnected()
{
signalConnectionId(this.Context.ConnectionId);
return base.OnConnected();
}
private void signalConnectionId(string signalConnectionId)
{
Clients.Client(signalConnectionId).signalConnectionId(signalConnectionId);
}
}
In the javascript:
$(document).ready(function () {
// Reference the auto-generated proxy for the SignalR hub.
var myHub = $.connection.myHub;
// The callback function returning the connection id from the hub
myHub.client.signalConnectionId = function (data) {
signalConnectionId = data;
}
// Start the connection.
$.connection.hub.start().done(function () {
// load event definitions here for sending to the hub
});
});

In order to be able to get "Context.User.identity.Name", you supposed to integrate your authentication into OWIN pipeline.
More info can be found in this SO answer: https://stackoverflow.com/a/52811043/861018

In ChatHub Class Use This for Spacific User
public Task SendMessageToGroup(string groupName, string message)
{
return Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId}: {message}");
}
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}.");
}

Related

ASP.NET Core 6 SignalR hub store data

I'm working on a simple chat using SignalR. At the moment I'm trying associate a users connection id with their identity user. I want to do this to prevent a user from impersonating another user by manually calling the hub functions.
My hub looks something like this:
public static class MessageContext
{
public static string RECEIVE = "ReceiveMessage";
public static string REGISTER = "Register";
public static string SEND = "SendMessage";
}
public class ChatHub : Hub
{
public const string HUBURL = "/api/ChatSignal";
Dictionary<string, string> _userContext;
public ChatHub()
{
_userContext = new Dictionary<string, string>();
}
public override Task OnConnectedAsync()
{
var ConnectionId = Context.ConnectionId;
var Username = Context.User.Identity.Name;
_userContext.Add(ConnectionId, Username);
Groups.AddToGroupAsync(ConnectionId, Username);
return base.OnConnectedAsync();
}
public async Task SendAll(string user, string message)
{
var ConnectionId = Context.ConnectionId;
message = HttpUtility.HtmlEncode(message);
await Clients.All.SendAsync(MessageContext.RECEIVE, _userContext[ConnectionId], message);
}
public Task SendMessage(string sender, string receiver, string message)
{
var ConnectionId = Context.ConnectionId;
message = HttpUtility.HtmlEncode(message);
return Clients.Group(receiver).SendAsync(MessageContext.RECEIVE, _userContext[ConnectionId], message);
}
public override async Task OnDisconnectedAsync(Exception e)
{
var ConnectionId = Context.ConnectionId;
var Username = Context.User.Identity.Name;
_userContext.Remove(ConnectionId);
await base.OnDisconnectedAsync(e);
}
}
My problem is that after when I call these functions, the dictionary gets set to null. After looking around for a while I found on MSDN that, hubs are "Transient", so each hub method call is executed on a new hub instance. This is a problem if I want to save ConnectionId:Identity.Name.
How can I use a dictionary to store this data for each hub instance?
To fix the null issue remove the initialization of _userContext in the constructor.
And change the line Dictionary<string, string> _userContext; -> private static ConcurrentDictionary<string, string> _userContext = new ConcurrentDictionary<string, string>();
This is how your dictionary state will be retained across different hub instances. And ConcurrentDictionary will make it thread-safe.
But it is not a very scaleable solution. If you are really making a production-grade chat application, try to use something like Redis Cache for such state management.

How To Refresh Connected ConnectionId List For All The Connected Connection In Blazor Server SignalR?

I am trying to refresh the connected ConnectionId whenever a new connection is made for all the connected connections in blazor server using signalr.
But the problem is that whenever the second or later connection is made the the same ConnectionId is added twice in the list.
Any help with code will be grate. Thank you
Below is my hub
public class ChatHub : Hub
{
public Task SendMessage(string ConnectionId, string message)
{
return Clients.Client(ConnectionId).SendAsync("ReceiveMessage", message);
}
public override Task OnConnectedAsync()
{
Clients.All.SendAsync("ReceiveUser", Context.ConnectionId);
return base.OnConnectedAsync();
}
}
public static class UserHandler
{
public static List<string> ConnectedUsers = new List<string>();
}
Below is my code to refresh connected ConnectionId
protected override async Task OnInitializedAsync()
{
hubConnection = new HubConnectionBuilder()
.WithUrl(NavigationManager.ToAbsoluteUri("/chathub"))
.Build();
hubConnection.On<string>("ReceiveMessage", broad);
hubConnection.On<string>("ReceiveUser", RefreshUserList);
await hubConnection.StartAsync();
StateHasChanged();
}
private void RefreshUserList(string connectedUserId) // Double hit in this method as a result same connectionId is added twice in the list
{
UserHandler.ConnectedUsers.Add(connectedUserId);
connectedUserList = UserHandler.ConnectedUsers;
StateHasChanged();
}

Display specific page to all connected users, after userA clicks button - Xamarin.Forms

I have Xamarin.Forms frontend app and Web API .NET Core backend and I use SignalR. I am able to make chat - userA sends a message and all connected users can see it.
And now I need to display specific page to all connected users after userA clicks the button. I have created DisplayPageToAllConnectedUsers SignalR client method but I don’t know how to tell there to display specific page to all connected users. Thanks for any advice
Xamarin.Forms - SignalRClient.cs
public class SignalRClient : INotifyPropertyChanged
{
private HubConnection Connection;
public delegate void MessageReceived(string username, string message);
public event MessageReceived OnMessageReceived;
public SignalRClient(string url)
{
Connection = new HubConnectionBuilder()
.WithUrl(url)
.Build();
Connection.On<string, string>("ReceiveMessage", (username, text) =>
{
OnMessageReceived?.Invoke(username, text);
});
Connection.On("DisplayPageToAllConnectedUsers", () =>
{
//??display specific page to all connected users??
});
}
public void SendMessage(string username, string text)
{
Connection.InvokeAsync("SendMessage", username, text);
}
public void DisplayPageToAllConnectedUsers()
{
Connection.InvokeAsync("DisplayPageToAllConnectedUsers");
}
public Task Start()
{
return Connection.StartAsync();
}
Backend - ChatHub.cs
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
public async Task DisplayPageToAllConnectedUsers()
{
await Clients.All.SendAsync("DisplayPageToAllConnectedUsers");
}
}
* Edit *
Instead of MainPage I have to put SignalRClient and it is working if I use MessagingCenter without using SignalR. See bellow
In SignalRClient.cs
public void DisplayPageToAllConnectedUsers()
{
//Connection.InvokeAsync("DisplayPageToAllConnectedUsers");
MessagingCenter.Send<SignalRClient>(this, "MyPage"); //working
}
But it is not working as soon as I use SignalR
Connection.On("DisplayPageToAllConnectedUsers", () =>
{
MessagingCenter.Send<SignalRClient>(this, "MyPage");
});
You can use the MessagingCenter, to send a message to your MainPage, to redirect the application to whatever page you want. For example:
In SignalRClient.cs
MessagingCenter.Send<MainPage> (this, "ChangeToYourPage");
In your MainPage.xaml.cs, you subscribe to this event:
MessagingCenter.Subscribe<MainPage> (this, "ChangeToYourPage", (sender) => {
// do something whenever the "ChangeToYourPage" message is sent
Navigation.PushAsync(YourNewPage);
});

Using SignalR Core to send messages from a Controller Method to Angular

I'm trying to use SignalR for Asp Net Core 2.1 in order to send a message from a controller method which call is triggered from a test button in Angular.
The behavior I'd expect is that when I click the button, my service invokes the controller method, which sends the test message. Then, I will simply log the message.
I want to manage this in a service in order to avoid code duplication in all of the components.
I've read some examples like this question about using SignalR in a service (I've used the second solution) and this article and the official docs but even with applying these concepts it don't seems to work. (So, or I'm absolutely applying them in a wrong way or there's still something missing but I can't find out what...)
The client connects to the Message Hub successfully and if I click the button, the method is getting hit but I don't get any message and instead I get this warning in the Chrome console:
Warning: No client method with the name 'SendAsync' found.
Sending messages works fine, the issue is just with receiving them...
The question is: what am I doing wrong? Is the error on the back-end side or in the Angular side?
I share with you all of my code (the button and the service to call the controller method are not relevant since the call to the service goes fine):
> Startup.cs
public void ConfigureServices(IServiceCollection services)
{
//...
services.AddSignalR();
}
//...
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//...
app.UseSignalR(routes =>
{
//...
routes.MapHub<MessageHub>("/messagehub");
//...
});
}
> MessageHub.cs
public class MessageHub : Hub<ITypedHubClient>
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
public interface ITypedHubClient
{
Task SendAsync(string title, string name, string message);
}
> MessageController.cs
IHubContext<MessageHub, ITypedHubClient> _messageHubContext;
public MessageController(IHubContext<MessageHub, ITypedHubClient> messageHubContext)
{
_messageHubContext = messageHubContext;
}
[HttpPost("Test")]
public async Task<IActionResult> Test()
{
try
{
await _messageHubContext.Clients.All.SendAsync("ReceiveMessage","test", "test");
return Ok(true);
}
catch (Exception e)
{
return BadRequest(e);
}
}
> communication.service.ts
#Injectable()
export class CommunicationService {
private _hubConnection: HubConnection | undefined;
public async: any;
message = '';
messages: string[] = [];
private newmessage = new Subject<string>();
message$ = this.newmessage.asObservable();
constructor() {
this._hubConnection = new signalR.HubConnectionBuilder()
.withUrl('/messagehub')
//.configureLogging(signalR.LogLevel.Information)
.configureLogging(signalR.LogLevel.Debug)
.build();
this._hubConnection.start().catch(err => console.error(err.toString()));
this._hubConnection.on('SendMessage', (user: any, message:any) => {
const received = `Received: ${message}`;
//this.messages.push(received);
this.newmessage.next(received);
console.log("got something new...", received);
});
}
clear() {
this.newmessage.next("");
}
public sendMessage(): void {
const data = `Sent: ${this.message}`;
if (this._hubConnection) {
this._hubConnection.invoke('SendMessage', 'AAA' ,data);
}
this.messages.push(data);
}
}
In signalr core 2.1 you can use strongly typed hubs to declare in an interface what actions can be called on the clients :
public class MessageHub : Hub<ITypedHubClient>
{
public async Task SendMessage(string title, string user, string message)
{
await Clients.All.SendMessageToClient(title, user, message);
}
}
public interface ITypedHubClient
{
Task SendMessageToClient(string title, string name, string message);
}
in the controller :
IHubContext<MessageHub, ITypedHubClient> _messageHubContext;
public async Task<IActionResult> Test()
{
await _messageHubContext.Clients.All.SendMessageToClient("test", "test", "test");
return Ok("ok");
}
in the client :
_hubConnection.on('SendMessageToClient', (title, user, message) => {
const received = `title: ${title}, name: ${user}, message: ${message}`;
console.log(received);
});
If you don't use strongly typed hub, then to call the same method in the client it becomes :
public class MessageHub : Hub
{
public async Task SendMessage(string title, string user, string message)
{
await Clients.All.SendAsync("SendMessageToClient", title, user, message);
}
}
In that case you can use the SendAsync method on the client proxy, it's first parameter is the name of the method you want to call.
Update :
When we define a strongly typed hub with an interface, all interface methods must return a Task. With custom methods, signalr generates methods that call SendCoreAsync. That allow us to call these methods asynchronously.
If the return type of the interface methods is not a Task we get the error : All client proxy methods must return 'System.Threading.Tasks.Task'

SignalR Mapping user to connectionID with custom authentication

I am trying to implement a chat application. Users should be able to send messages to specific users. In order to do that I need to map usernames to their connectionIDs.
My client is using custom authentication. Username is stored in Session["User"]. Therefore I don't have the username stored in Context.User.Identity.Name, which is where SignalR normally takes the username from.
How else can I get the username of the logged in user so that I can map it to Context.ConnectionID?
Here is some sample implementation of public class ChatHub : Hub I found on the web.
private readonly static ConnectionMapping<string> _connections =
new ConnectionMapping<string>();
public void SendChatMessage(string who, string message)
{
string name = Context.User.Identity.Name;
foreach (var connectionId in _connections.GetConnections(who))
{
Clients.Client(connectionId).addChatMessage(name + ": " + message);
}
}
public override Task OnConnected()
{
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
return base.OnConnected();
}
Pass your username using query string.
Client
First set query string
For auto generated proxy
$.connection.hub.qs = { 'username' : 'anik' };
For manual proxy
var connection = $.hubConnection();
connection.qs = { 'username' : 'anik' };
then start hub connection
Server
public override Task OnConnected()
{
var username= Context.QueryString['username'];
return base.OnConnected();
}

Categories

Resources