I have written an Application where I am using SignalR. I am sending connectionId from Client to Server(controller).
Everything is working fine with single browser (request will sent to server with connectionId="conn_1") and signalR is sending response to only conn_1, but when i open new browser and send a request from that client the previous connection gets disposed. Which means only one connection with particular connectionId remains alive.
Is there any way SignalR can not dispose and send response to both with data they want?
I am new to SignalR and would really appropriate any help or guidance.
Angular SignalRService to start connection with server
this.hubConnection = new signalR.HubConnectionBuilder()
.withUrl(this.paymentDraftHubUrl)
.build();
return this.hubConnection
.start()
.then(() => this.hubConnectionStatus = 'Connection started')
.catch(err => (this.hubConnectionStatus = 'Error while starting connection: ' + err));
}
sending connectionId from client component to Api
this.signalRService.startConnection().then((connection) => {
this.connectionId = connection.toString();
//Calling Api
this.getAllTransactionException(
this.connectionId,
this.pageNumber,
this.pageSize
}
MyHub class in C#
public class PaymentDraftServiceHub : Hub, IPaymentDraftHub
{}
Controller for API
using timer to keep calling repository for new data,
[HttpGet]
[Route("GetCsrTranactions")]
public IActionResult GetCsrTranactions([FromQuery] TransactionExceptionDataRequest queryParams)
{
TimeManager.Dispose();
var timerManager = new TimeManager(async () =>
await _paymentDraftHub.Clients.Clients.Client(queryParams.ConnectionId).SendAsync(SignalRConstants.TransferPaymentDraftServiceData, await _paymentTransactionRepository.GetCsrTranactionsAsync(queryParams)));
var response = new ResponseMessage { Message = "Accepted", Code = "201" };
return Ok(response);
}
Client can have multiple connections with multiple connection IDs if client connect from multiple browser windows or tabs.
According to the code you provided, we can find that you just pass connection ID of SignalR client within current active browser tab/window to your controller, and in your controller action, you use this code snippet .Client(queryParams.ConnectionId).SendAsync() to send message to a specific client, so other browser windows or tabs would not receive the message.
If you'd like to send message(s) to a client with multiple connections, you need to map SignalR users to connection Ids and retain information about users-to-connectionIds mapping, then you can get all connectionIds of a client and send messages to that client with with multiple connectionIds, like below.
//code logic here
//to get all connectinIds of a client/user
//from user-to-connectionIds mapping table
await _paymentDraftHub.Clients.Clients(connectionIds_here).SendAsync("method_here",args_here);
Related
I'm building a backend with Asp.Net Core & SignalR. The data are not themself located on the Asp.Net Core, but must be requested to some other devices, that will send the data to the Asp.Net Core server, and then the server must give them back to the client through the web socket.
I would like to react to the OnConnectedAsync to request my devices to send periodically some data to Asp.Net Core.
But this means that my hub must be able to know which data I need to be querying.
So I would like to have a Hub responding to the url /api/data/{id} and in my Hub, be able to know which ID has been requested.
app.MapHub<NotificationsHub>("/api/data/{id}");
The id identify a big group of data(that is requested and forwared as bulk), and multiple clients will request the same ID.
I can't find in the doc:
If this is possible
How should I specify the ID parameter?
How do I retrieve this ID in the hub?
If anybody has some pointer, it would be helpful, thank you very much
You can pass parameters as query string parameters. Pass the id from the connection url:
_hubConnection = new HubConnectionBuilder()
.WithUrl("/api/data?id=123")
.Build();
Then read it inside hub:
public override async Task OnConnectedAsync()
{
string id = Context.GetHttpContext().Request.Query["id"];
var connectionId = Context.ConnectionId;
await Groups.AddToGroupAsync(connectionId, id);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
string id = Context.GetHttpContext().Request.Query["id"];
var connectionId = Context.ConnectionId
await Groups.RemoveFromGroupAsync(connectionId, id);
await base.OnDisconnectedAsync(exception);
}
Send data:
_hubContext.Group("123").SomeMethod(dataBatch);
Map hub:
app.MapHub<NotificationsHub>("/api/data")
I'm looking at incorporating Azure SignalR functionality into my .net core Blazor web application. To this end i've been following this tutorial - Azure Signalr Serverless. This is working fine - i have a project running the Azure functions app and can start up two browsers and have a chat session. What i'm trying to do is add the ability to receive these message notifications from the Azure signalR hub that's been configured into my Blazor app. I've added the following code in Index.razor.cs that mimics the javascript code in the example client:
public class IndexComponent : ComponentBase
{
private HubConnection _connection;
public string Message;
protected override Task OnInitializedAsync()
{
_connection = new HubConnectionBuilder()
.WithUrl("http://localhost:7071/api")
.Build();
_connection.On<string, string>("ReceiveMessage", (user, message) =>
{
Message = $"Got message {message} from user {user}";
this.StateHasChanged();
});
_connection.StartAsync();
return base.OnInitializedAsync();
}
}
The example javascript code btw is:
const connection = new signalR.HubConnectionBuilder()
.withUrl(`${apiBaseUrl}/api`)
.configureLogging(signalR.LogLevel.Information)
.build();
connection.on('newMessage', newMessage);
connection.onclose(() => console.log('disconnected'));
console.log('connecting...');
connection.start()
.then(() => data.ready = true)
.catch(console.error);
So the problem is that my Blazor app never receives any message notifications sent from the javascript chat clients (so the _connection.On handler is never hit). What am i missing in my Blazor code ?
Ok so this is what i needed to do to get it to work in my Blazor app:
_connection.On<object>("newMessage", update =>
{
Console.WriteLine(update);
//Message = update;
});
I needed to subscribe to the 'newMessage' target (since that's the JS is sending on) and also the type that's being posted isn't a string but a JObject type which i would need to deserialize to the correct type.
I built a Websocket Server in Java with Spring Boot
My server configuration looks like:
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic", "/queue");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry
.addEndpoint("/my-websocket")
.setAllowedOrigins("*")
.withSockJS();
}
I've created a client in Javascript which works, and the connection looks like:
function connect() {
var myUsername = document.getElementById('from').value;
var host = 'http://localhost:8080/my-websocket';
var socket = new SockJS(host);
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
stompClient.send('/app/status', {}, JSON.stringify(createUserStatus('AVAILABLE')));
stompClient.subscribe('/topic/status', function (message) {
receiveStatus(JSON.parse(message.body));
});
stompClient.subscribe('/queue/private.message.' + myUsername, function (message) {
displayMessage(JSON.parse(message.body));
});
});
}
Now I want to create a client in .NET, but every Stomp client library I've found, doesn't allow http to be the URI, and my server only accepts http connections. My server fails to parse ws and tcp connections, and I can't find a client out there that uses http.
In my .NET client, when I try to connect to string brokerUri = "tcp://localhost:8080/my-websocket"
My server throws this exception:
client-id:ID:DESKTOP-AA29FJ6-52716-637205011942817986-0:0
host:localhost
accept-version:1.0,1.1
heart-beat:10000,30000
]
2020-03-22 19:13:14.414 DEBUG 25036 --- [nio-8080-exec-2] o.apache.coyote.http11.Http11Processor : Error parsing HTTP request header
java.lang.IllegalArgumentException: Invalid character found in method name. HTTP method names must be tokens
How can I create a Websocket connection in .NET that can subscribe to different topics and queues, and send messages over http?
Alternatively, how could I make my server handle ws or tcp connections?
Thank you!
I have an P2P network app which acts as a server and client.
The app connects to multiple clients in a mesh and sends "data" to each other once the data has been processed.
If a client gets an incoming request then creates their own request before completing the incoming request it seems to deadlock and neither requests complete. Everything works fine unless a create is sent which processing one.
Is this because I'm not using async on the server methods or is this because I shouldn't send a request while processing one?
proto
rpc submit_data(DataSubmission) returns (DataSubmissionAck);
RPC Server
public async void Start()
{
_server = new Server()
{
Services = {
DataService.BindService(new DataService())),
},
Ports = { new ServerPort(_IpAddress, _Port, ServerCredentials.Insecure) }
};
_server.Start();
}
Client method
public void SubmitData(Data data)
{
...
var serverResponse = _serviceClient.submit_data(request);
...
}
Server method
public override Task<DataSubmissionAck> submit_data(DataSubmission request, ServerCallContext context)
{
DataSubmissionAck clientResponse = new DataSubmissionAck();
return Task.FromResult(clientResponse);
}
What seems to have worked is making the server and client-side calls all async.
I need to send an instant message from the server to the client after the user has submitted a form in a browser.
I followed the Microsoft steps here to set up a signalR connection, created a Hub class, signalr.js etc.
The problem is that I can only invoke a message to all clients, but I need to invoke the message to the specific caller who initiated the request (otherwise everyone will get the message).
This is my POST Action in the HomeController.cs:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> Submit(string signalRconnectionId, Dictionary<string, string> inputs)
{
//Invoke signal to all clients sending a message to initSignal WORKS FINE
await _signalHubContext.Clients.All.SendAsync("initSignal", "This is a message from the server!");
//Invoke signal to specified client where signalRConnectionId = connection.id DOES NOT WORK
await _signalHubContext.Clients.Client(signalRconnectionId).SendAsync("initSignal", "This is a message from server to this client: " + signalRconnectionId);
return RedirectToAction("Success", inputs);
}
my client javascript file:
//Create connection and start it
const connection = new signalR.HubConnectionBuilder()
.withUrl("/signalHub")
.configureLogging(signalR.LogLevel.Information)
.build();
connection.start().catch(err => console.error(err.toString()));
console.log("connectionID: " + connection.id);
$("#signalRconnectionId").attr("value", connection.id);
//Signal method invoked from server
connection.on("initSignal", (message) => {
console.log("We got signal! and the message is: " + message);
});
I have tried debugging the action method and I get correctly passed in the connectionId which is "0" (incrementing by 1 per connection)
So here's the final solution I came up with inspired by the answer from this thread
I got the connectionId by calling the Hub class from client and then from client calling the controller passing in the connectionId.
Hub class:
public class SignalHub : Hub
{
public string GetConnectionId()
{
return Context.ConnectionId;
}
}
client-side javascript code executed at startup:
connection.start().catch(err => console.error(err.toString())).then(function(){
connection.invoke('getConnectionId')
.then(function (connectionId) {
// Send the connectionId to controller
console.log("connectionID: " + connectionId);
$("#signalRconnectionId").attr("value", connectionId);
});
});
HomeController.cs:
public async Task<IActionResult> Submit(string signalRconnectionId, Dictionary<string, string> inputs)
{
//Invoke signal to specified client WORKS NOW
await _signalHubContext.Clients.Client(signalRconnectionId).SendAsync("initSignal", "This is a message from server to this client: " + signalRconnectionId);
return RedirectToAction("Success", inputs);
}
It works fine, but still feels a little like a roundtrip, it would have been easier if we didn't have to go through the hub class to make this happen. Maybe just having the connectionId from the client-side to begin with, but maybe there is a good reason for the design :)
According to Microsoft, you can not access to the ConnectionId and Caller from outside a hub
https://learn.microsoft.com/en-us/aspnet/core/signalr/hubcontext?view=aspnetcore-2.1
When hub methods are called from outside of the Hub class, there's no
caller associated with the invocation. Therefore, there's no access to
the ConnectionId, Caller, and Others properties.