I am developing WPF application where one instance of a program will be able to communicate to other via SignalR Self-Host. Everything is fine, except that I don't know how and where do I join a group so that program would know to which users send notification. Any help would be appreciated.
Here is my sample code:
//client side
private async void ConnectAsync()
{
Connection = new HubConnection(ServerURI);
Connection.Closed += Connection_Closed;
HubProxy = Connection.CreateHubProxy("MyHub");
//Handle incoming event from server: use Invoke to write to console from SignalR's thread
HubProxy.On<string, string>("AddMessage", (name, message) =>
this.Dispatcher.Invoke(() => RichTextBoxConsole.AppendText(String.Format("{0}: {1}\r", name, message))
)
);
try
{
await Connection.Start();
}
catch (HttpRequestException)
{
StatusText.Content = "Unable to connect to server: Start server before connecting clients.";
//No connection: Don't enable Send button or show chat UI
return;
}
HubProxy.Invoke<string>("JoinGroup", "foobar").Wait(); // Do I have to do it here?
}
//serverside
public class MyHub : Hub
{
public void Send(string name, string message)
{
Clients.Group("foobar").AddMessage(name, message);
}
public Task JoinGroup(string groupName)
{
return Groups.Add(Context.ConnectionId, groupName);
}
public Task AddGroups()
{
return Groups.Add(Context.ConnectionId, "foobar");
}
public override Task OnConnected()
{
return AddGroups();
}
}
I think you have it almost right, but your OnConnected method isn't quite right.
Try changing it to:
public override Task OnConnected()
{
Groups.Add(Context.ConnectionId, "foobar");
return base.OnConnected();
}
This is basically the code I've used, and the difference is the base.OnConnected() part which is my guess as to what's breaking for you.
The problem was that when adding a new Group:
public Task AddGroups()
{
return Groups.Add(Context.ConnectionId, "foobar");
}
it for some reason added prefix "hg-MyHub." to the name of the Group so the name of the Group looked like this: "hg-MyHub.foobar" and of course using this code:
HubProxy.Invoke<string>("JoinGroup", "foobar").Wait();
didn't do anything simply because there was no Group with name "foobar".
Hope this could be usefull for somebody.
Related
Sorry, if this is a stupid question but I don't find any useful information in the internet.
Has anyone ever tried to implement the observer pattern in C# using gRPC as communication?
If yes, please show me the link.
Many thanks in advance and best regards.
I have implemented a client convenience class wrapper to turn server streaming calls into regular events for a project I am working. Not sure if this is what you are after. Here is a simple gRPC server that just publishes the time as a string once every second.
syntax = "proto3";
package SimpleTime;
service SimpleTimeService
{
rpc MonitorTime(EmptyRequest) returns (stream TimeResponse);
}
message EmptyRequest{}
message TimeResponse
{
string time = 1;
}
The server implementation, which just loops once a second returning the string representation of the current time until canceled, is as follows
public override async Task MonitorTime(EmptyRequest request, IServerStreamWriter<TimeResponse> responseStream, ServerCallContext context)
{
try
{
while (!context.CancellationToken.IsCancellationRequested)
{
var response = new TimeResponse
{
Time = DateTime.Now.ToString()
};
await responseStream.WriteAsync(response);
await Task.Delay(1000);
}
}
catch (Exception)
{
Console.WriteLine("Exception on Server");
}
}
For the client, I created a class that contains the gRPC client and exposes the results of the server streaming MonitorTime call as a plain ole .net event.
public class SimpleTimeEventClient
{
private SimpleTime.SimpleTimeService.SimpleTimeServiceClient mClient = null;
private CancellationTokenSource mCancellationTokenSource = null;
private Task mMonitorTask = null;
public event EventHandler<string> OnTimeReceived;
public SimpleTimeEventClient()
{
Channel channel = new Channel("127.0.0.1:50051", ChannelCredentials.Insecure);
mClient = new SimpleTime.SimpleTimeService.SimpleTimeServiceClient(channel);
}
public void Startup()
{
mCancellationTokenSource = new CancellationTokenSource();
mMonitorTask = Task.Run(() => MonitorTimeServer(mCancellationTokenSource.Token));
}
public void Shutdown()
{
mCancellationTokenSource.Cancel();
mMonitorTask.Wait(10000);
}
private async Task MonitorTimeServer(CancellationToken token)
{
try
{
using (var call = mClient.MonitorTime(new SimpleTime.EmptyRequest()))
{
while(await call.ResponseStream.MoveNext(token))
{
var timeResult = call.ResponseStream.Current;
OnTimeReceived?.Invoke(this, timeResult.Time);
}
}
}
catch(Exception e)
{
Console.WriteLine($"Exception encountered in MonitorTimeServer:{e.Message}");
}
}
}
Now create the client and subscribe to the event.
static void Main(string[] args)
{
SimpleTimeEventClient client = new SimpleTimeEventClient();
client.OnTimeReceived += OnTimeReceivedEventHandler;
client.Startup();
Console.WriteLine("Press any key to exit");
Console.ReadKey();
client.Shutdown();
}
private static void OnTimeReceivedEventHandler(object sender, string e)
{
Console.WriteLine($"Time: {e}");
}
Which when run produces
I have left out a lot of error checking and such to make the example smaller. One thing I have done is for gRPC interfaces with many server streaming calls that may or may not be of interest to call clients, is to implement the event accessor (add,remove) to only call the server side streaming method if there is a client that has subscribed to the wrapped event. Hope this is helpful
Using SignalR, I have the following Hub classes below and here I want to collect shared methods in a Base Hub class:
HubBase
public class HubBase : Hub
{
public readonly static ConnectionMapping<string> _connections =
new ConnectionMapping<string>();
public override Task OnConnected()
string name = Context.User.Identity.Name;
_connections.Add(name, Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
string name = Context.User.Identity.Name;
_connections.Remove(name, Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
string name = Context.User.Identity.Name;
if (!_connections.GetConnections(name).Contains(Context.ConnectionId))
{
_connections.Add(name, Context.ConnectionId);
}
return base.OnReconnected();
}
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);
}
}
}
StockHub
public class StockHub : HubBase
{
private static IHubContext context = GlobalHost
.ConnectionManager.GetHubContext<StockHub>();
public static void SendStockData(object data) {
context.Clients.All.sendStockData(data);
}
}
StockHub
public class ChatHub : HubBase
{
private static IHubContext context = GlobalHost
.ConnectionManager.GetHubContext<ChatHub>();
public static void SendMessage(string message) {
context.Clients.All.sendMessage(message);
}
}
My questions are:
1- As the HubBase is inherited by Stock and Chat hub classes, I think all the connected clients to one of those methods will be added connected users in the HubBase. So, when I broadcast data to the clients who connected to StockHub, I will also broadcast data to the other clients who connected to ChatHub. Is that true?
2- How can I fix this problem without defining all the connection classes in Stock and Chat hub instead of Base hub (HubBase)?
As it is not logical to define these shared methods for all of the hub classes, I need to discriminate the user who connect to StockHub. How can I do this?
3- In addition to that, I am wondering what if I do not use none of these connection methods and any base class (define each hub classes separately with no relation), and use Clients.All method in order to broadcast data, in this case will the broadcast only goes only to the clients who connected my hub? If so, for this scenario there is no need to use connection methods (assuming that I broadcast to all of the clients connected to StockHub). Is that true?
SignalR allows messages to be sent to all connections associated with a specific user, as well as to named groups of connections.
Groups in SignalR : it is manages the Users and Groups for you which associated with Hub
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}.");
}
read more about here
Hope it helps you.
I have created a chat using SignalR2. The client and server itself works fine. Now, I'm trying to implement a 'users online' function. The server code seems about right, but I'm struggling to make the client receive the data that the server pushes back to the client.
This is the server code below:
public static List<string> Users = new List<string>();
public void Send(string name, string message)
{
// Call the broadcastMessage method to update clients.
Clients.All.broadcastMessage(name, message);
Clients.All.addMessage(name, message);
}
public void SendUserList(List<string> users)
{
var context = GlobalHost.ConnectionManager.GetHubContext<chatHub>();
context.Clients.All.updateUserList(users);
}
public override Task OnConnected()
{
string clientId = GetClientId();
//if (Users.IndexOf(clientId) == -1)
//{
Users.Add(clientId);
//}
SendCount(Users.Count);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
System.Diagnostics.Debug.WriteLine("Disconnected");
SendCount(Users.Count);
return base.OnDisconnected(stopCalled);
}
private string GetClientId()
{
string clientId = "";
if (Context.QueryString["clientId"] != null)
{
// clientId passed from application
clientId = this.Context.QueryString["clientId"];
}
if (string.IsNullOrEmpty(clientId.Trim()))
{
clientId = Context.ConnectionId;
}
return clientId;
}
public void SendCount(int count)
{
// Call the addNewMessageToPage method to update clients.
var context = GlobalHost.ConnectionManager.GetHubContext<chatHub>();
context.Clients.All.updateUsersOnlineCount(count);
}
Below is the client code for connecting / receiving messages:
public static async void ConnectAsync(RadChat ChatInternal)
{
ChatInternal.Author = new Author(null, Varribles.Agent);
var querystringData = new Dictionary<string, string>();
querystringData.Add("clientId", Varribles.Agent);
Connection = new HubConnection(ServerURI, querystringData);
HubProxy = Connection.CreateHubProxy("chatHub");
//Handle incoming event from server: use Invoke to write to console from SignalR's thread
HubProxy.On<string, string>("AddMessage", (name, message) =>
ChatInternal.Invoke((Action)(() =>
Backend.GET.Messages(ChatInternal)
)));
try
{
await Connection.Start();
Backend.GET.Messages(ChatInternal);
}
catch (System.Net.Http.HttpRequestException)
{
//No connection: Don't enable Send button or show chat UI
return;
}
}
Now, my question is, how can I retrieve the 'Users' list from the server?
Thanks in advance
SignalR core is very new, so the docs detail how to use about it are very rare.
I've done the tutorial from Microsoft and successfully sent messages to all the clients. Now I want to send for specific user, with
public Task SendPrivateMessage(string user, string message, string to)
{
return Clients.User(to).SendAsync("ReceiveMessage", user, message);
}
the "to" value is the ConnectionID I got from the
public override Task OnConnectedAsync()
{
Console.WriteLine("New ID Connected: " + Context.ConnectionId);
return base.OnConnectedAsync();
}
Here is my client:
public async void InitSignalRAsync()
{
ChatMessage mess = new ChatMessage();
hubConnection = new HubConnectionBuilder().WithUrl("http://localhost:5000/chatHub").Build();
await hubConnection.StartAsync();
hubConnection.On<string, string>("ReceiveMessage", async (user, message) =>
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
mess.user = user;
mess.message = message;
Messages.Add(mess);
});
});
}
private void Send_Click(object sender, RoutedEventArgs e)
{
hubConnection.InvokeAsync("SendPrivateMessage", User.Text, Text.Text, To.Text);
}
The console logs nothing error so I guess it did send but why can't I receive it?
For send to client by client's connectionId you should use of Clients.Client(coonectionId)
,Clients.User() is for send to uniqe user by user's Id.to do send message by user id you can try as follows:
-create a CustomUserIdProvider:
public class CustomUserIdProvider: IUserIdProvider
{
public virtual string GetUserId(HubConnectionContext connection)
{
//get current user id by httpcontext
}
}
and then in startup.cs:
services.AddSignalR();
services.AddSignalRCore();
services.AddSingleton<IUserIdProvider, CustomUserIdProvider>();
now in your hub you can send message by user id:
public void Send(string userId, string message)
{
Clients.User(userId).send(message);
}
for more info go to this link
I have modified the Xamarin/Azure TODO example. But the code is stuck in
IMobileServiceTable.ToListAsync()
This is my IO class:
class DataIO
{
BackgroundWorker DatabaseWorker = new BackgroundWorker();
IMobileServiceTable<UserPosition> PositionTable;
MobileServiceClient client;
public DataIO()
{
Init();
}
public void Init()
{
client = new MobileServiceClient(Constants.ApplicationURL);
PositionTable = client.GetTable<UserPosition>();
}
async void AddEntry(UserPosition entry)
{
await PositionTable.InsertAsync(entry);
}
public async Task<List<UserPosition>> GetEntries()
{
List<UserPosition> Entries = await PositionTable.ToListAsync();
return Entries;
}
public async void DeleteEntry(UserPosition entry)
{
await PositionTable.DeleteAsync(entry);
}
public async void AddToDatabase(UserPosition item)
{
await PositionTable.InsertAsync(item);
}
}
The debugger dosen't neither step over it nor throws an error.
How to handle that?
In an earlier call, there wasn't any problem.
EDIT:
I've rewritten the GetEntries() method to:
public async Task<List<UserPosition>> GetEntries()
{
Task<List<UserPosition>> task = PositionTable.ToListAsync();
List<UserPosition> entries = await task;
return entries;
}
according to this example. But the debugger just stays in the line
Task<List<UserPosition>> task = PositionTable.ToListAsync();
AFAIK, IMobileServiceTable.ToListAsync() would send the request as follows for retrieving the result:
Get https://<your-app-name>.azurewebsites.net/tables/UserPosition
I would recommend you using Fiddler to collect the network traces when calling IMobileServiceTable.ToListAsync(). Also, you could access the table endpoint from your mobile app via the browser to make sure your mobile app could work as expected. Additionally, here is a great tutorial about Handling Data in Mobile Clients, you could refer to it.