I just created a sample project with signalR. I am just trying to test managing multiple connection. Everything works as expected when I open the first browser and load the page. It is going to fire the OnConnected event on the hub. But when I open another browser or different tab and load the page, it doesn't fire OnConnected event anymore. It shows $.connection.hub.id though.
Here is the hub
[HubName("genie")]
public class Genie : Microsoft.AspNet.SignalR.Hub
{
private static ConnectionManager _manager = new ConnectionManager();
[HubMethodName("AdminCommand")]
public void AdminCommand(string command, string message = "")
{
var connetions = _manager.GetConnections();
connetions.Remove(Context.ConnectionId);
Clients.Clients(connetions).onAdminCommand(command, message);
}
public override Task OnConnected()
{
_manager.AddConnection(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
_manager.RemoveConnection(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
}
And here is the javascript code:
var proxy = $.connection.genie;
$.connection.hub.start()
.done(function (state) {
console.log($.connection.hub.id);
});
proxy.on('onAdminCommand', function (command, message) {
if (command == "HappyGenie") {
$scope.goTo("happy/");
} else if (command == "SadGenie") {
$scope.goTo("sad/");
} else if (command == "CustomAnnouncement") {
dataService.setDataByKey("Announcement", message);
$scope.goTo("customannouncement/");
}
});
I establish a connection with the generated proxy.
Is there something I am doing wrong?
Thanks
Related
So i'm testing with Blazor and gRPC and my dificulty at the moment is on how to pass the content of a variable that is on a class, specifically the gRPC GreeterService Class to the Blazor page when new information arrives. Notice that my aplication is a client and a server, and i make an initial comunication for the server and then the server starts to send to the client data(numbers) in unary mode, every time it has new data to send. I have all this working, but now i'm left it that final implementation.
This is my Blazor page
#page "/greeter"
#inject GrpcService1.GreeterService GreeterService1
#using BlazorApp1.Data
<h1>Grpc Connection</h1>
<input type="text" #bind="#myID" />
<button #onclick="#SayHello">SayHello</button>
<p>#Greetmsg</p>
<p></p>
#code {
string Name;
string Greetmsg;
async Task SayHello()
{
this.Greetmsg = await this.GreeterService1.SayHello(this.myID);
}
}
The method that later receives the communication from the server if the hello is accepted there is something like this:
public override async Task<RequestResponse> GiveNumbers(BalconyFullUpdate request, ServerCallContext context)
{
RequestResponse resp = new RequestResponse { RequestAccepted = false };
if (request.Token == publicAuthToken)
{
number = request.Number;
resp = true;
}
return await Task.FromResult(resp);
}
Every time that a new number arrives i want to show it in the UI.
Another way i could do this was, within a while condition, i could do a call to the server requesting a new number just like the SayHello request, that simply awaits for a server response, that only will come when he has a new number to send. When it comes the UI is updated. I'm just reluctant to do it this way because i'm afraid that for some reason the client request is forgotten and the client just sit's there waiting for a response that will never come. I know that i could implement a timeout on the client side to handle that, and on the server maybe i could pause the response, with a thread pause or something like that, and when the method that generates the new number has a new number, it could unpause the response to the client(no clue on how to do that). This last solution looks to me much more difficult to do than the first one.
What are your thoughts about it? And solutions..
##################### UPDATE ##########################
Now i'm trying to use a singleton, grab its instance in the Blazor page, and subcribe to a inner event of his.
This is the singleton:
public class ThreadSafeSingletonString
{
private static ThreadSafeSingletonString _instance;
private static readonly object _padlock = new object();
private ThreadSafeSingletonString()
{
}
public static ThreadSafeSingletonString Instance
{
get
{
if (_instance == null)
{
lock(_padlock)
{
if (_instance == null)
{
_instance = new ThreadSafeSingletonString();
_instance.number="";
}
}
}
return _instance;
}
set
{
_instance.number= value.number;
_instance.NotifyDataChanged();
}
}
public int number{ get; set; }
public event Action OnChange;
private void NotifyDataChanged() => OnChange?.Invoke();
And in Blazor page in code section i have:
protected override void OnInitialized()
{
threadSafeSingleton.OnChange += updateNumber();
}
public System.Action updateNumber()
{
this.fromrefresh = threadSafeSingleton.number + " que vem.";
Console.WriteLine("Passou pelo UpdateNumber");
this.StateHasChanged();
return StateHasChanged;
}
Unfortunatly the updatenumber function never gets executed...
To force a refresh of the ui you can call the StateHasChanged() method on your component:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.components.componentbase.statehaschanged?view=aspnetcore-3.1
Notifies the component that its state has changed. When applicable, this will cause the component to be re-rendered.
Hope this helps
Simple Request
After fully understanding that your problem is just to Update the Page not to get unsyncronous messages from the server with a bi directional connection. So jou just have to change your page like (please not there is no need to change the files generated by gRPC, I called it Number.proto so my service is named NumberService):
async Task SayHello()
{
//Request via gRPC
var channel = new Channel(Host + ":" + Port, ChannelCredentials.Insecure);
var client = new this.NumberService.NumberServiceClient(channel);
var request = new Number{
identification = "ABC"
};
var result = await client.SendNumber(request).RequestAccepted;
await channel.ShutdownAsync();
//Update page
this.Greetmsg = result;
InvokeAsync(StateHasChanged);//Required to refresh page
}
Bi Directional
For making a continious bi directional connection you need to change the proto file to use streams like:
service ChatService {
rpc chat(stream ChatMessage) returns (stream ChatMessageFromServer);
}
This Chant sample is from the https://github.com/meteatamel/grpc-samples-dotnet
The main challenge on this is do divide the task waiting for the gRPC server from the client. I found out that BackgroundService is good for this. So create a Service inherited from BackgroundService where place the while loop waiting for the server in the ExecuteAsyncmethod. Also define a Action callback to update the page (alternative you can use an event)
public class MyChatService : BackgroundService
{
Random _random = new Random();
public Action<int> Callback { get; set; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Replace next lines with the code request and wait for server...
using (_call = _chatService.chat())
{
// Read messages from the response stream
while (await _call.ResponseStream.MoveNext(CancellationToken.None))
{
var serverMessage = _call.ResponseStream.Current;
var otherClientMessage = serverMessage.Message;
var displayMessage = string.Format("{0}:{1}{2}", otherClientMessage.From, otherClientMessage.Message, Environment.NewLine);
if (Callback != null) Callback(displayMessage);
}
// Format and display the message
}
}
}
}
On the page init and the BackgroundService and set the callback:
#page "/greeter"
#using System.Threading
<p>Current Number: #currentNumber</p>
#code {
int currentNumber = 0;
MyChatService myChatService;
protected override async Task OnInitializedAsync()
{
myChatService = new MyChatService();
myChatService.Callback = i =>
{
currentNumber = i;
InvokeAsync(StateHasChanged);
};
await myChatService.StartAsync(new CancellationToken());
}
}
More information on BackgroundService in .net core can be found here: https://gunnarpeipman.com/dotnet-core-worker-service/
On server side I have
public class Global : System.Web.HttpApplication
{
private static List<UserInfo> _Users;
public static List<UserInfo> Users
{
get { return Global._Users; }
set
{
lock (_Users)
{
Global._Users = value;
}
}
}
...
public class RawHub : PersistentConnection
{
protected override Task OnConnected(IRequest request, string connectionId)
{
var UserID = request.QueryString.Where(t => t.Key == "U");
if (!string.IsNullOrEmpty(UserID.First().Value))
{
var ui = new Guid(UserID.First().Value);
var user = Global.Users.FirstOrDefault(x => x.USERID == ui);
if (user == null)
{
var us = new UserInfo();
us.USERID = ui;
us.ConnectionId = connectionId;
Global.Users.Add(us);
}
else
{
user.ConnectionId=connectionId;
}
return Connection.Send(user.ConnectionId,"Good To Go");
}
}
I want to tryout send users their specific infos user by user with Send ...
bu I have to clean up Global.Users when they are disconnected.
Q: Is that possible to catch pings ? from js client or must i have to ping to client to detect live or not ?
What is the best approach
thnks
May br you can send custom i am alive messages from js client. Becouse some times ping doesnot come from client as expected. Usually when network is busy this happens...
good luck
Not sure what exactly is the problem here but you can simply listen for messages/pings in OnReceived
asp.net/signalr
I have a simple application, like a chat, integrated with SignalR. I added a new method on my Hub and a new function on client side, like you can see below.
The problem is, my method called SendMessageChat isn't firing, because occurs the following error
TypeError: chat2.server.SendMessageChat is not a function
but the method chat2.server.send works fine, and I don't know why my second method doesn't work. Can someone help me ?
JavaScript
$(function () {
var chat2 = $.connection.redirectTask;
chat2.client.broadcastMessage = function (name, message) {
// Do something here
};
chat2.client.sendMessage = function (name, message) {
// Do something here
};
//$.connection.hub.logging = true;
$.connection.hub.start().done(function () {
/* BUTTON CLICK IN ANOTHER PAGE */
$('#btnFinish').click(function () {
chat2.server.send($.cookie("User"), $("#lista :selected").text());
});
/* CASE HIT ENTER INSIDE THE TEXT FIELD IN CHAT */
$(document).on("keypress", "#txtChat", function (e) {
var code = (e.keyCode ? e.keyCode : e.which);
if (code == 13) {
var message = $(this).val();
$(this).val("");
chat2.server.SendMessageChat($.cookie("User"), message);
}
});
});
});
SERVER SIDE
public class RedirectTask : Hub
{
public void Send(string nome, string message)
{
Clients.All.broadcastMessage(name, message);
}
public void SendMessageChat(string nome, string message)
{
Clients.All.sendMessage(name, message);
}
}
Reference
Need to change to
chat2.server.sendMessageChat($.cookie("User"), message);
Camel-casing of method names in JavaScript clients
By default, JavaScript clients refer to Hub methods by using a camel-cased version of the method name. SignalR automatically makes this change so that JavaScript code can conform to JavaScript conventions.
Server
public void NewContosoChatMessage(string userName, string message)
JavaScript client using generated proxy
contosoChatHubProxy.server.newContosoChatMessage(userName, message);
If you want to specify a different name for clients to use, add the HubMethodName attribute.
Server
[HubMethodName("PascalCaseNewContosoChatMessage")]
public void NewContosoChatMessage(string userName, string message)
JavaScript client using generated proxy
contosoChatHubProxy.server.PascalCaseNewContosoChatMessage(userName, message);
I want to send data to my console application wich have a connection to my "someHub". I tried to do as described in example from a link but got no result.
Server side code:
[HubName("somehub")]
public class SomeHub : Hub
{
public override Task OnConnected()
{
//Here I want to send "hello" on my sonsole application
Clients.Caller.sendSomeData("hello");
return base.OnConnected();
}
}
Clien side code:
public class Provider
{
protected HubConnection Connection;
private IHubProxy _someHub;
public Provider()
{
Connection = new HubConnection("http://localhost:4702/");
_someHub = Connection.CreateHubProxy("somehub");
Init();
}
private void Init()
{
_someHub.On<string>("sendSomeData", s =>
{
//This code is not reachable
Console.WriteLine("Some data from server({0})", s);
});
Connection.Start().Wait();
}
}
What is the best solution for implementing this and what is the reason why i am not able to invoke the client method?
Are you trying to talk to clients outside of Hub? If yes then you will have to get a HubContext outside of Hub. And then you can talk all the clients.
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
SignalR Server using Owin Self Host
class Program
{
static void Main(string[] args)
{
string url = "http://localhost:8081/";
using (WebApplication.Start<Startup>(url))
{
Console.WriteLine("Server running on {0}", url);
Console.ReadLine();
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
for (int i = 0; i < 100; i++)
{
System.Threading.Thread.Sleep(3000);
context.Clients.All.addMessage("Current integer value : " + i.ToString());
}
Console.ReadLine();
}
}
}
class Startup
{
public void Configuration(IAppBuilder app)
{
// Turn cross domain on
var config = new HubConfiguration { EnableCrossDomain = true };
config.EnableJavaScriptProxies = true;
// This will map out to http://localhost:8081/signalr by default
app.MapHubs(config);
}
}
[HubName("MyHub")]
public class MyHub : Hub
{
public void Chatter(string message)
{
Clients.All.addMessage(message);
}
}
Signalr Client Console Application consuming Signalr Hubs.
class Program
{
static void Main(string[] args)
{
var connection = new HubConnection("http://localhost:8081/");
var myHub = connection.CreateHubProxy("MyHub");
connection.Start().Wait();
// Static type
myHub.On<string>("addMessage", myString =>
{
Console.WriteLine("This is client getting messages from server :{0}", myString);
});
myHub.Invoke("Chatter",System.DateTime.Now.ToString()).Wait();
Console.Read();
}
}
To run this code, create two separate applications, then first run server application and then client console application, then just hit key on server console and it will start sending messages to the client.
So I was using SignalR version .5 and everything was working fine. But trying to upgrade to version 1 to use the connectionSlow method. Unfortunately it seems to have broken when I have upgraded. I have an mvc application, and I am trying to use signalr to push data to the client. I want the connection to be open forever.
The server will not send messages to the client. After some investigations using a LoggingPiplineModule i found that the context.Connection.Identifier is not the connextionID of the connected browser, its asif it is trying to send it to someone else.
My Hub only has a few methods:
public void JoinGroup(string groupID)
{
if (!String.IsNullOrEmpty(Context.User.Identity.Name) && (!String.IsNullOrEmpty(groupID)))
{
Groups.Add(Context.ConnectionId, groupID.Trim());
}
else
{
LoggerSingleton.Instance.Logger.Error("Error: Could not join group as user is not logged in or group is null");
}
}
public void LeaveGroup(string groupID)
{
if (!String.IsNullOrEmpty(Context.User.Identity.Name) && (!String.IsNullOrEmpty(groupID)))
{
Groups.Remove(Context.ConnectionId, groupID.Trim());
}
else
{
LoggerSingleton.Instance.Logger.Error("Error: Could not leave group as user is not logged in or group is null");
}
}
public static void SendCallLog(CallLog newCall, int groupID)
{
var context = GlobalHost.ConnectionManager.GetHubContext<CommandCentreHub>();
context.Clients.Group(groupID.ToString()).addMessage(CallLog.ToJson(newCall), groupID.ToString());
}
And my javascript:
conChat = $.connection.commandcentrehub;
// Push method for signalR, process the pushed message passed from the server
conChat.addMessage = function (message, groupID) {
var call = JSON.parse(message);
updateTableImages($('#groupContent' + groupID), call, groupID);
updateTableImages($('#groupContent' + 'All'), call, 'All');
applyFilter();
};
$.connection.hub.start().done(function () {
$('.groupID').each(function () {
conChat.server.joinGroup(this.id.replace("group", ""));
});
});
And my global.asax
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteTable.Routes.MapHubs();
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
GlobalHost.HubPipeline.AddModule(new LoggingPipelineModule());
log4net.Config.XmlConfigurator.Configure();
}
I get no errors in chrome dev, and joingroup is working properly but when the server calls addMessage I get nothing.
Ok I fixed the issue.
It was with my javascript.
The below:
conChat.addMessage = function (message, groupID) { ...
should be:
conChat.client.addMessage = function (message, groupID) {
Hope this helps someone...