Cannot send data if the connection is not in the 'Connected' State. - Multiple Hubs in SignalR - asp.net-core

I'm attempting to get to grips with SignalR, to do so I'm trying to extend the functionality of the simple chat room tutorial that Microsoft provide in their documentation.
I'm now trying to add a second hub, which will allow the user to do send in integers and receive the value multiplied by 10. The hub itself is almost identical to the normal ChatHub, except with an extra step that checks the input is a number and does the multiplication.
ChatHub
public class ChatHub : Hub
{
public async Task SendMessage(string group,string user, string message)
{
await Clients.Group(group).SendAsync("ReceiveMessage", user, 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}.");
}
}
CalcHub
public class CalcHub : Hub
{
public async Task SendMessage(string group, string user, string message)
{
var value = MultiplyByTen(message);
await Clients.Group(group).SendAsync("ReceiveMessage", user, value);
}
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 string MultiplyByTen(string input)
{
bool isANumber = Int32.TryParse(input, out int value);
if (isANumber)
{
return (value * 10).ToString();
}
return "Not a number";
}
}
I have Javascript set up for my front-end, which works perfectly fine when I try to connect to the ChatHub and send messages, however when I attempt to use the connection to CalcHub, I get the Cannot send data if the connection is not in the 'Connected' State error message.
Here is how the two connections are established.
var calcConnection = new signalR.HubConnectionBuilder().withUrl("https://localhost:44309/calcHub").build();
var chatConnection = new signalR.HubConnectionBuilder().withUrl("https://localhost:44308/chatHub").build();
var activeConnection;
setConnection();
$("#hubSelector").on("change",
function(data) {
setConnection();
});
I have a simple select element that will swap the connection based on its value. SetConnection is the method that controls this, which is used at DOM ready to set the initial connection.
Both of the hubs are registered in my startup class too.
app.UseSignalR(routes =>
{
routes.MapHub<ChatHub>("/chatHub");
routes.MapHub<CalcHub>("/calcHub");
});
If I navigate to the two addresses of the hubs https://localhost:44309/calcHub and https://localhost:44309/chatHub, I can also see that they are valid addresses as I get the Connection ID required message.
Why is my calcHub not working?
Site.js
// Please see documentation at https://learn.microsoft.com/aspnet/core/client-side/bundling-and-minification
// for details on configuring this project to bundle and minify static web assets.
// Write your JavaScript code.
$(function() {
var calcConnection = new signalR.HubConnectionBuilder().withUrl("https://localhost:44309/calcHub").build();
var chatConnection = new signalR.HubConnectionBuilder().withUrl("https://localhost:44308/chatHub").build();
var activeConnection;
//setConnection();
//$("#hubSelector").on("change",
// function(data) {
// setConnection();
// });
activeConnection.on("ReceiveMessage", function (user, message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var encodedMsg = user + " says " + msg;
var li = document.createElement("li");
li.textContent = encodedMsg;
document.getElementById("messagesList").appendChild(li);
});
activeConnection.start().catch(function (err) {
return console.error(err.toString());
});
$("#addgroup").on("click", function () {
var group = document.getElementById("group").value;
activeConnection.invoke("AddToGroup", group).catch(function (err) {
return console.error(err.toString());
});
$("#group-list").append("<p>" + group + "</p>");
event.preventDefault();
});
$("#sendButton").on("click", function () {
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
var group = document.getElementById("group").value;
activeConnection.invoke("SendMessage", group, user, message).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
});
function setConnection() {
var selectValue = $("#hubSelector").val();
if (selectValue === "chat") {
$("#activeHub").html("<span>Active Hub: Chat</span>");
activeConnection = chatConnection;
}
if (selectValue === "calc") {
$("#activeHub").html("<span>Active Hub: Calc</span>");
activeConnection = calcConnection;
}
console.log(activeConnection);
}
});

Related

Using MQTT ManagedClient with ASP NET API, how to?

I'm currently working on a project that has to rely heavily on MQTT - one of the parts that needs to utilize MQTT is a ASP Net API, but I'm having difficulties receiving messages.
Here is my MQTTHandler:
public MQTTHandler()
{
_mqttUrl = Properties.Resources.mqttURL ?? "";
_mqttPort = Properties.Resources.mqttPort ?? "";
_mqttUsername = Properties.Resources.mqttUsername ?? "";
_mqttPassword = Properties.Resources.mqttUsername ?? "";
_mqttFactory = new MqttFactory();
_tls = false;
}
public async Task<IManagedMqttClient> ConnectClientAsync()
{
var clientID = Guid.NewGuid().ToString();
var messageBuilder = new MqttClientOptionsBuilder()
.WithClientId(clientID)
.WithCredentials(_mqttUsername, _mqttPassword)
.WithTcpServer(_mqttUrl, Convert.ToInt32(_mqttPort));
var options = _tls ? messageBuilder.WithTls().Build() : messageBuilder.Build();
var managedOptions = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
.WithClientOptions(options)
.Build();
_mqttClient = new MqttFactory().CreateManagedMqttClient();
await _mqttClient.StartAsync(managedOptions);
Console.WriteLine("Klient startet");
return _mqttClient;
}
public async Task PublishAsync(string topic, string payload, bool retainFlag = true, int qos = 1)
{
await _mqttClient.EnqueueAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)qos)
.WithRetainFlag(retainFlag)
.Build());
Console.WriteLine("Besked published");
}
public async Task SubscribeAsync(string topic, int qos = 1)
{
var topicFilters = new List<MQTTnet.Packets.MqttTopicFilter>
{
new MqttTopicFilterBuilder()
.WithTopic(topic)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)(qos))
.Build()
};
await _mqttClient.SubscribeAsync(topicFilters);
}
public Status GetSystemStatus(MqttApplicationMessageReceivedEventArgs e)
{
try
{
var json = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
var status = JsonSerializer.Deserialize<Status>(json);
if (status != null)
{
return status;
}
else
{
return null;
}
}
catch (Exception)
{
throw;
}
}
The above has been tested with a console app and works as it should.
The reason I need MQTT in the APi is that a POST method has to act on the value of a topic;
In particular I need to check a systems status before allowing the post;
[HttpPost]
public async Task<ActionResult<Order>> PostOrder(Order order)
{
if (_lastStatus != null)
{
if (_lastStatus.OpStatus)
{
return StatusCode(400, "System is busy!");
}
else
{
var response = await _orderManager.AddOrder(order);
return StatusCode(response.StatusCode, response.Message);
}
}
return StatusCode(400, "Something went wrong");
}
So I will need to set up a subscriber for this controller, and set the value of _lastStatus on received messages:
private readonly MQTTHandler _mqttHandler;
private IManagedMqttClient _mqttClient;
private Status _lastStatus;
public OrdersController(OrderManager orderManager)
{
_orderManager = orderManager;
_mqttHandler = new MQTTHandler();
_mqttClient = _mqttHandler.ConnectClientAsync().Result;
_mqttHandler.SubscribeAsync("JSON/Status");
_mqttClient.ApplicationMessageReceivedAsync += e =>
{
_lastStatus = _mqttHandler.GetSystemStatus(e);
return Task.CompletedTask;
};
}
However, it's behaving a little odd and I'm not experienced enough to know why.
The first time I make a POST request, _lastStatus is null - every following POST request seem to have the last retained message.
I'm guessing that I am struggling due to stuff being asynchronous, but not sure, and every attempt I've attempted to make it synchronous have failed.
Anyone have a clue about what I'm doing wrong?

How Use a hub for logged in users

I want to create a chat room between site users with each other =>my code for hub
private readonly IMessageService _message;
private readonly UserManager<User> _user;
public SiteChatHub(IMessageService message, UserManager<User> user)
{
_message = message;
_user = user;
}
public async Task JoinGroup(string keyGroup)
{
await Groups.AddToGroupAsync(Context.ConnectionId, keyGroup);
}
public async Task LeftGroup(string keyGroup)
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, keyGroup);
}
public async Task LoadChat(string token)
{
var user = this.Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
var messages = await _message.GetMessages(token, user);
await Clients.Groups(token).SendAsync("Load", messages);
}
public async Task SendMessage(string text,string token)
{
var user = Context.User.FindFirstValue(ClaimTypes.NameIdentifier);
var me = 1;
var avatar = _user.FindByIdAsync(user).Result.UserAvatar;
//await Clients.Groups(token).SendAsync("ReceiveMessage", text, avatar, me);
await SendToClient(text, avatar, token, me);
SendMessageViewModel message = new SendMessageViewModel {Sender = user, Message = text, Token = token};
_message.InsertMessage(message);
}
public async Task SendToClient(string text, string avatar, string token, int me)
{
await Clients.Groups(token).SendAsync("ReceiveMessage", text, avatar, me);
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
}
public override Task OnDisconnectedAsync(Exception? exception)
{
return base.OnDisconnectedAsync(exception);
}
and js cod:
var activeRoomId = "";
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
var myList = document.getElementById("groups");
connection.start().then(function () {
console.log("StartHUB");
}).catch(function (err) {
return console.error(err.toString());
});
connection.on("ReceiveMessage", function (text, avatar, me) {
console.log("ReceiveMessage");
showMessage(text, avatar, me);
});
connection.on("Load", function (messages) {
console.log("Loaded Chat......");
console.log(messages);
if (!messages) return;
messages.forEach(function (m) {
console.log("Foreach.......");
showMessage(m['text'], m['image'],m['me']);
});
});
//me==1 other==2
function showMessage(text, image, me) {
console.log("show.......");
if (me === 1) {
console.log("if");
$("#list").append(
"<li><div class= 'float-right'><img src='https://tetronjob.com/images/"+image+"'width='40' height='40' class='img-thumbnail float-right'><div class='badge badge-warning p-2 m-3'><h6>"+text+"</h6></div></div></li>");
} else {
$("#list").append(
"<li style = 'text-align: left;'><div class= 'float-right'><div class='badge badge-warning p-2 m-3'><h6>" + text + "</h6></div><img src='https://tetronjob.com/images/" + image + "'width='40' height='40' class='img-thumbnail float-right'></div></li>");
console.log("else");
}
}
var chatMessages = document.getElementById("list");
function setActiveRoomButton(el) {
var allButtons = myList.querySelectorAll('.list-group-item');
allButtons.forEach(function (btn) {
btn.classList.remove('active');
});
el.classList.add('active');
}
function switchActiveRoomTo(id) {
if (id === activeRoomId) return;
if (activeRoomId) {
connection.invoke("LeftGroup", activeRoomId);
}
chatMessages.innerHTML = '';
activeRoomId = id;
connection.invoke("JoinGroup", activeRoomId);
connection.invoke("LoadChat", activeRoomId);
}
myList.addEventListener('click', function (e) {
myList.style.display = 'block';
console.log(e);
setActiveRoomButton(e.target);
var roomId = e.target.getAttribute('data-id');
console.log("Room:" + roomId);
switchActiveRoomTo(roomId);
});
document.getElementById("chatBtn").addEventListener("click", function (event) {
var text = document.getElementById("messageText").value;
console.log(text);
if (text && text.length) {
console.log("Text Send....");
connection.invoke('SendMessage',text, activeRoomId);
} document.getElementById("messageText").value="";
});
I have created a variable called "m" to separate the sender and receiver messages
If the number was one, it means the sender; If the number was two, it means the receiver
This code works but is not separated when sending and receiving messages
The sender and receiver message are placed below each other
Thanks for guiding me

Signalr .net core 2.2 - mesages going to non connected groups

I am using .net core 2.2 with SignalR version 1.1.0. When I test the app, messages are being received by member who are NOT in the group. My groups are being dynamically created at run time based on relevant criteria, as in : var TheHub = CurrUser.Hubname; I cannot work out why group members who are NOT in the group are also receiving the messages. I am sending to GROUP and not ALL.
Please see code. Any help greatly appreciated, I am ready to pull my hair out.
My hub class
public class Chathub : Microsoft.AspNetCore.SignalR.Hub
{
public override async Task OnConnectedAsync()
{
var TheHub = CurrUser.Hubname;
await Groups.AddToGroupAsync(Context.ConnectionId, TheHub.ToString());
await base.OnConnectedAsync();
}
public Task SendMessageGroup(string user, string message)
{
var TheHub = CurrUser.Hubname;
return Clients.Group(TheHub.ToString()).SendAsync("ReceiveMessage", user, message);
}
}
My Javascript
"use strict";
document.getElementById("sendgroupButton").addEventListener("click", function (event) {
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
connection.invoke("SendMessageGroup", user, message).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
playAudio();
});
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
document.getElementById("sendgroupButton").disabled = true;
connection.on("ReceiveMessage", function (user, message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var encodedMsg = user + " says " + msg;
var li = document.createElement("li");
li.textContent = encodedMsg;
document.getElementById("messagesList").appendChild(li);
});
connection.start().then(function () {
document.getElementById("sendgroupButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});
This is how I get the current value for curruser.hubname, please see below.
#inject SignInManager<ApplicationUser> SignInManager
#inject UserManager<ApplicationUser> UserManager
#if (SignInManager.IsSignedIn(User))
{
CurrUser.CurrentUsertId = UserManager.GetUserId(User);
var ctx = new WebookContext();
var LoggedInGuestHouseName = (from Ghouse in ctx.Guesthouse
where Ghouse.UserId == CurrUser.CurrentUsertId
select Ghouse).SingleOrDefault();
//check to see if guesthouse details have been completed, if not skip this next line of code.
if( LoggedInGuestHouseName != null)
{
CurrUser.GuestHouseName = LoggedInGuestHouseName.GuestHouseName;
// add the hub to current user
CurrUser.HubId = (int) LoggedInGuestHouseName.HubId;
var Ghname = LoggedInGuestHouseName.GuestHouseName;
var GhUserEmailaddress = LoggedInGuestHouseName.Emailaddress;
var GhHuId = LoggedInGuestHouseName.HubId;
CurrUser.GuestHouseName = Ghname;
CurrUser.GuestHouseEmailaddress = GhUserEmailaddress;
var q = (from gh in ctx.Hub
where gh.HubId == GhHuId
select gh).SingleOrDefault();
var myhubname = q.HubName;
CurrUser.Hubname = myhubname;
};
}
Looks like SignalR core is not for the feint hearted. Until a authoritative book comes out, one is really walking blind. I have researched this topic blue, but alas have now given up on SignalR for now.

ASP.NET Core 2.2 - SignalR How to join a group and send message from the server?

I have a set up a basic solution for SignalR where I can send and receive messages to all clients through the browser.
Required solution:
I need to implement the use of groups, BUT with the ability to join the group and send a message from a background service class that is running on the server.
Currently I have only been successful in sending a message to ALL clients from the server.
My background service class (basically a comms server) will be running multiple instances, so each instance will have a unique name such as CommsServer 1, CommsServer 2 etc. Each instance of the comms server will need to output messages to a specific SignalR group of recipients.
In the browser, the user will select which SignalR group they wish to join from a dropdown list that is pulled from the server. The server also has knowledge of this same list and therefore each comms server instance will represent an item from the list.
My code so far:
MessageHub Class:
public class MessageHub : Hub
{
public Task SendMessageToAll(string message)
{
return Clients.All.SendAsync("ReceiveMessage", message);
}
}
Message.js File:
"use strict";
var connection = new signalR.HubConnectionBuilder()
.withUrl("/messages")
.build();
connection.on("ReceiveMessage", function (message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var div = document.createElement("div");
div.innerHTML = msg + "<hr/>";
document.getElementById("messages").appendChild(div);
});
connection.start().catch(function (err) {
return console.error(err.toString());
});
document.getElementById("sendButton").addEventListener("click", function (event) {
var message = document.getElementById("message").value;
connection.invoke("SendMessageToAll", message).catch(function (err) {
return console.error(err.toString());
});
e.preventDefault();
});
Razor Page:
<textarea name="message" id="message"></textarea>
<input type="button" id="sendButton" value="Send Message" />
<div id="messages"></div>
<script src="~/lib/signalr/dist/browser/signalr.js"></script>
<script src="~/js/message.js"></script>
Comms server class:
public class TcpServer
{
private readonly IHubContext<MessageHub> _hubContext;
public TcpServerTcpServer(IHubContext<MessageHub> hubContext)
{
_hubContext = hubContext;
}
public string inboundIpAddress = "127.0.0.1";
public int inboundLocalPortNumber = 10001;
public void TcpServerIN(string serverName)
{
Task.Run(() =>
{
IPAddress localAddrIN = IPAddress.Parse(inboundIpAddress);
TcpListener listener = new TcpListener(localAddrIN, inboundLocalPortNumber);
listener.Start();
while (true)
{
TcpClient client = listener.AcceptTcpClient();
// Get a stream object for reading and writing
NetworkStream stream = client.GetStream(); // Networkstream is used to send/receive messages
//Buffer for reading data
Byte[] bytes = new Byte[4096];
String data = null;
int i;
// Loop to receive all the data sent by the client.
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
// Translate data bytes to a ASCII string.
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
_hubContext.Clients.All.SendAsync(data);
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
stream.Write(msg, 0, data.Length);
}
// Shutdown and end connection
client.Close();
}
});
}
}
I need to select a specific SignalR group name e.g. "CommsServer 1" this will form the name of the SignalR group that the user can select from a dropdown list in the browser so they can monitor events from just this one particular server instance. Other user may wish to monitor events from a different server instance.
One option I was considering is simply send events from all servers instances to ALL browser connected clients and then somehow filter the events on the client side, but this would seem an inefficient way of handling things and not good for network traffic.
If you want your client to decide which group to join (by selecting from a list of dropdowns), then your client should probably notify the server of its subscription to messages aimed at the group via the hub:
Hub:
public class MessageHub : Hub
{
public async Task SendMessageToAll(string message)
{
await Clients.All.SendAsync("ReceiveMessage", message);
}
public async Task Subscribe(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
}
Background service:
public class TcpServer
{
private readonly IHubContext<MessageHub> _hubContext;
private readonly string _serviceInstanceName;
public TcpServerTcpServer(IHubContext<MessageHub> hubContext, string serviceInstanceName)
{
_hubContext = hubContext;
_serviceInstanceName = serviceInstanceName;
}
public async Task SendMessageToGroup(string message)
{
await _hubContext.Clients.Group(_serviceInstanceName).SendAsync("ReceiveMessage", message);
}
}
And somewhere in your client, perhaps after user chooses an option from your dropdown:
connection.invoke("subscribe", "CommsServer 1, or whatever").catch(function (err) {
return console.error(err.toString());
});

signalr with sqldepdency firing multiple times for each browser instance

I am having a asp.net mvc app with vs2013 and .net framwork 4.5.1 which should notify users when a certain field gets updated and for a certain recordID.
Everything works fine when I have a single instance of the browser open, but when i open another tab or browser either on the same machine or on the different machine it fires the sqldepdencychange event multiple times.
Below is my hub code
public class MessagesHub : Hub
{
private static string conString = ConfigurationManager.ConnectionStrings["FleetLink_DB"].ToString();
private static string hostName = "";
public static void SendMessages(string hName)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<MessagesHub>();
hostName = hName;
context.Clients.Group(hostName).updateMessages(hName);
}
public Task leaveGroup(string hName)
{
return Groups.Remove(Context.ConnectionId, hName);
}
public Task joinGroup(string hName)
{
return Groups.Add(Context.ConnectionId, hName);
}
}
Below is my signalr script file
$(function () {
var dialog, form
// Declare a proxy to reference the hub.
var notifications = $.connection.messagesHub;
//debugger;
//Create a function that the hub can call to broadcast messages.
notifications.client.updateMessages = function (hName) {
alert("testing");
getoneMessages(hName)
};
$.connection.hub.logging = true;
$.connection.hub.start().done(function () {
var hostName = getUrlVars()["System_Name"];
notifications.server.joinGroup(hostName);
}).fail(function (e) {
alert(e);
});
});
function getUrlVars() {
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for (var i = 0; i < hashes.length; i++) {
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
return vars;
}
function getoneMessages(hName) {
var tbl = $('#selectable');
//alert('mesgID=' + mesgID)
//var tbl = $('#selectable');
$.ajax({
url: '/controller/view',
cache: false,
contentType: 'application/html ; charset:utf-8',
type: 'GET',
dataType: 'html'
}).success(function (result) {
//alert(result);
tbl.empty().append(result);
}).error(function (exception) {
//alert('failed= ' + exception);
});
}
window.onbeforeunload = function (e) {
var hostName = getUrlVars()["System_Name"];
notifications.server.joinGroup(hostName);
$.connection.hub.stop();
};
Below is my partialview code along with the definition for RegisterForNotification and depdendency_onchange event
public PartialViewResult SignalRTesterPartialView()
{
/...COde not included for brevity..../
RegisterForNotifications(ID);
}
public void RegisterForNotifications(int mID)
{
var efConnectionString = ConfigurationManager.ConnectionStrings["DB"].ConnectionString;
var builder = new EntityConnectionStringBuilder(efConnectionString);
var regularConnectionString = builder.ProviderConnectionString;
string commandText = null;
commandText = "select ID,Status,Name from tblABC where ID=" + strID;
using (SqlConnection connection = new SqlConnection(regularConnectionString))
{
using (SqlCommand command = new SqlCommand(commandText, connection))
{
connection.Open();
var dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
// NOTE: You have to execute the command, or the notification will never fire.
var reader = command.ExecuteReader();
}
}
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change && e.Info== SqlNotificationInfo.Update)
{
MessagesHub.SendMessages(hName);
}
RegisterForNotifications(1012);
}
Not sure why it is firing the sendmessages multiple times with each additional browser instance that I open. Any pointers would be helpful!
remove EventHandler when you done with it
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
if (e.Type == SqlNotificationType.Change && e.Info== SqlNotificationInfo.Update)
{
MessagesHub.SendMessages(hName);
}
//remove event handler
SqlDependency dependency = sender as SqlDependency;
dependency.OnChange -= new OnChangeEventHandler(dependency_OnChange);
RegisterForNotifications(1012);
}