Using javascript timers with signalR events to check for new data - javascript

I have this table, with a set of rows, each using a unique connection to signalR. This allows me to update several rows at the same time with unique content.
The way it works is that a service bus provides the messagehub with new values and a uniqe id to go with that value, every time a remote unit transmits a new message.
At this point i'd like to run a check every 10 seconds to see if the webserver still gets a message from the unit, which transmits this as long as it is alive. In other words, if there's more than 10 seconds since the last time SignalR gave me a value, this would indicate that the connection to the remote unit is lost. (Not to be mistaken with SignalR losing its connection)
As I have a lot of units (rows) in my table, I was wondering if a javascript timer for each row would be sufficient for this check, or is there a better way of doing this? If so, do I do this in my connector script or in my html?

A single timer firing every 10 seconds and scanning all your signalr connections should work fine.

Ok, so I figured this out in another way, letting my messagehandler take care of the task of distributing messages at the correct time:
public class AsxActivityAliveEventMessageHandler : IHandleMessages<AsxActivityAliveEvent>
{
private const double INTERVAL = 10000;
public static bool AsxConnected { get; set; }
private static Dictionary<String, TagTimer> _connectionTimers = new Dictionary<string, TagTimer>();
public void Handle(AsxActivityAliveEvent message)
{
AsxConnected = true;
NotifyClients(message);
TagTimer timer;
if (_connectionTimers.ContainsKey(message.ConveyanceId))
{
timer = _connectionTimers[message.ConveyanceId];
if (timer != null)
{
timer.Stop();
timer.Elapsed -= timer_Elapsed;
_connectionTimers.Remove(message.ConveyanceId);
}
}
timer = new TagTimer
{
Interval = INTERVAL,
Tag = message
};
timer.Elapsed += timer_Elapsed;
_connectionTimers.Add(message.ConveyanceId, timer);
timer.Start();
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var timer = sender as TagTimer;
if (timer != null)
{
timer.Stop();
timer.Elapsed -= timer_Elapsed;
}
AsxConnected = false;
if (timer != null)
{
NotifyClients(timer.Tag as AsxActivityAliveEvent);
}
}
static void NotifyClients(AsxActivityAliveEvent message)
{
var messageHub = GlobalHost.ConnectionManager.GetHubContext<MessageHub>();
var conveyanceId = message.ConveyanceId;
// Removed some vars and notify's as they're not relevant to this example
messageHub.Clients.Group(message.ConveyanceId).notifyAlive(AsxConnected, conveyanceId);
}
}
internal class TagTimer : Timer
{
public object Tag { get; set; }
}
}

Related

SignalR client not firing server code

i am testing this signal with a very basic string. But the client side is not firing the server code and there is no error. i added the [HubName("MyHub1")] and the [HubMethodName("GetValueString")] in because if not the javascript will complaint client undefine and methodname not found.
after i added this 2 meta in. there is no error but server code was not fire. Anyone help please.
Client Script
(function () {
// Defining a connection to the server hub.
debugger
var myHub = $.connection.MyHub1;
// Setting logging to true so that we can see whats happening in the browser console log. [OPTIONAL]
$.connection.hub.logging = true;
// Start the hub
$.connection.hub.start();
// This is the client method which is being called inside the MyHub constructor method every 3 seconds
myHub.client.GetValueString = function (serverTime) {
// Set the received serverTime in the span to show in browser
$("#newTime").html(serverTime);
};
}());
Server script
[HubName("MyHub1")]
public class MyHub1 : Hub
{
public void Hello()
{
Clients.All.hello();
}
[HubMethodName("GetValueString")]
public void Getstring() {
var taskTimer = Task.Factory.StartNew(async () =>
{
while (true)
{
string timeNow = DateTime.Now.ToString();
//Sending the server time to all the connected clients on the client method ()
Clients.All.GetValueString("test");
//Delaying by 3 seconds.
await Task.Delay(3000);
}
}, TaskCreationOptions.LongRunning
);
}
}
Update 1
so i change my javascript to this and the output was "undefine" but no error also
var haha = myHub.client.GetValueString;
// Set the received serverTime in the span to show in browser
Try to give this ago. Also, use this for reference
I've added a console.log try and see if you see it when you run this code.
(function () {
var myHub = $.connection.MyHub1;
myHub.client.GetValueString = function (serverTime) {
$("#newTime").html(serverTime);
};
$.connection.hub.logging = true;
$.connection.hub.start().done(function() {
console.log("hub is ready"); // tell me if you see the message in your console log
myHub.server.getstring() // note i wrote getstring with a small g, has to be.
});
}());
[HubName("MyHub1")]
public class MyHub1 : Hub
{
public void Hello()
{
Clients.All.hello();
}
public void Getstring() {
var taskTimer = Task.Factory.StartNew(async () =>
{
while (true)
{
string timeNow = DateTime.Now.ToString();
//Sending the server time to all the connected clients on the client method ()
Clients.All.GetValueString("test");
//Delaying by 3 seconds.
await Task.Delay(3000);
}
}, TaskCreationOptions.LongRunning
);
}
}

SQLDependency firing multiple times when more than 1 connection made

I'm in the process of developing an appointment schedule for our company in ASP.NET (MVC Razor). I'm using SQLDependency so the pages can update via the service broker. We currently have 15 service centres, and each of them may have 1 to 4 computers where they can take calls and insert/modify/delete appointments. Our customers also have the option to take an online appointment with our online appointment tool, so this is why we need it to be dynamic and constantly refresh so we don't book 2 customers at the same time.
So far, everything is working fine. The page constantly refreshes when needed and we were two days away of launching our new tool. Except, when making tests in a production environment (we're switching our current tool to a new server, so we're testing it before going live with it), we realized that when 2 connections are made to the website, the SQLDependency act crazy. It will double the notifications everytime (once the first time, twice at the second notification, 4, 8, 16, 32, etc... so it escalates quickly) and so, it just becomes useless as it refreshes the partial view too many times. I was wondering if I was missing something. Here's some snippet of code :
First, the javascript call :
$(function () {
// Declare a proxy to reference the hub.
var notifications = $.connection.rendezVousHub;
// Create a function that the hub can call to broadcast messages.
notifications.client.updateAgenda = function () {
getAllRdv()
};
// Start the connection.
$.connection.hub.start().done(function () {
getAllRdv();
}).fail(function (e) {
alert(e);
});
});
The C# function that have the SQLDependency call and command
public IEnumerable<RendezVousModels> GetAllRdv(DateTime date, int whsCode)
{
_date = date;
_whsCode = whsCode;
string query = #"[dbo].[InfosAgenda]";
var rdv = new List<RendezVousModels>();
using (var connection = new SqlConnection(_connString))
{
connection.Open();
using (var command = new SqlCommand(query, connection))
{
command.Notification = null;
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Date", date.ToShortDateString());
command.Parameters.AddWithValue("#WhsCode", whsCode);
dependancy = new SqlDependency(command);
dependancy.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
var reader = command.ExecuteReader();
while (reader.Read())
{
rdv.Add(item: new RendezVousModels
{
// set infos into Model
});
}
}
}
return rdv;
}
The onChange event function :
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
SqlDependency dependency = sender as SqlDependency;
dependancy.OnChange -= dependency_OnChange;
if (e.Type == SqlNotificationType.Change)
RendezVousHub.sendAgenda();
GetAllRdv(_date, _whsCode);
}
I'm running out of ideas here. Maybe they all need different connections? If so, how should I proceed?
Thanks

SignalR join/leave group doesn't work correctly

I have a simple application using SignalR, where I wan't to display different data for different machines, depending on which machine has been chosen by the user
My Hub class looks like this:
readonly ISprayBroadcaster _sprayBroadcaster;
readonly IWorkRecordRepository _workRecordRepository;
public SprayHub(ISprayBroadcaster sprayBroadcaster, IWorkRecordRepository workRecordRepository)
{
_sprayBroadcaster = sprayBroadcaster;
_workRecordRepository = workRecordRepository;
}
public void Broadcast(string name)
{
Process.DataProcess(_workRecordRepository, Clients, name).Wait();
}
public void SwapGroup(string previousGroup, string newGroup)
{
Groups.Remove(Context.ConnectionId, previousGroup);
Groups.Add(Context.ConnectionId, newGroup);
}
public void JoinGroup(string groupName)
{
Groups.Add(Context.ConnectionId, groupName);
}
This is how I initialize the hub on the client side and call Broadcast method on it:
$(function () {
hub = $.connection.sprayHub;
function init() {
hub.server.joinGroup("machine1");
hub.server.broadcast("machine1");
};
// Client-side hub method that the server will call
hub.client.updateData = function (shifts) {
ViewModel.Measurements(recreateArray(shifts));
}
$.connection.hub.start().done(init);
});
Broadcast method goes to the DataProcess method which populates data to the clients from the assigned group:
public static async Task DataProcess(IWorkRecordRepository workRecordRepository, IHubConnectionContext hubClients, string machine)
{
var shiftRecords = await workRecordRepository.Records(machine, DateTime.Now).ToList();
var result = SLGT.Sentinel.Core.Calculations.Shifts(shiftRecords);
hubClients.Group(machine).updateData(result);
}
At the same time I setup a broadcaster which runs in the loop and feeds clients with appropriate data. This is a broadcast method from the broadcaster which calls the same DataProcess method to populate data for each machine found in the system:
void Broadcast(object state)
{
lock (_updateLock)
{
if (_updating)
return;
_updating = true;
var machines = _workRecordRepository.Machines();
machines.Subscribe(async machine =>
{
await Process.DataProcess(_workRecordRepository, Clients, machine);
});
_updating = false;
}
}
Finally when user clicks a button for different machine on the client side I swap the groups for the appropriate data to be displayed for this client:
$(".machineButton").click(function () {
var name = $(this).attr("id");
hub.server.swapGroup(previousGroup, name);
previousGroup = name;
}
Now, when I run this application in my test environment, everything works fine. When I run it on the server, swap between the groups doesn't work correctly, and the client is constantly fed with the same set of data. Why might it be happening? As I said local version works fine so I do not know how to debug it?
The group management methods (add and remove) are async. If you don't await the returned task then send to the group immediately after you have a race condition such that the client you just added might not receive the message. Also, you should never call .Wait() from in a hub method. Make the hub method async and await it instead.
readonly ISprayBroadcaster _sprayBroadcaster;
readonly IWorkRecordRepository _workRecordRepository;
public SprayHub(ISprayBroadcaster sprayBroadcaster, IWorkRecordRepository workRecordRepository)
{
_sprayBroadcaster = sprayBroadcaster;
_workRecordRepository = workRecordRepository;
}
public async Task Broadcast(string name)
{
await Process.DataProcess(_workRecordRepository, Clients, name);
}
public async Task SwapGroup(string previousGroup, string newGroup)
{
await Groups.Remove(Context.ConnectionId, previousGroup);
await Groups.Add(Context.ConnectionId, newGroup);
}
public async Task JoinGroup(string groupName)
{
await Groups.Add(Context.ConnectionId, groupName);
}
Also, is your production environment a single web server or is it a load-balanced farm? If it's a farm you'll need to configure SignalR scale-out.

Invoke JS method from C# using SignalR?

I know there are lots of examples out there to do with SignalR but I can't seem to get it working, I was hoping that one of you may be able to show (in full) how a WebPage (threaded loop so we can see it happening over and over) could call a JS method on a Page and change a text label or create a popup or, just something so that we an see the method execute?
I'll give you my code and maybe you can point out the error, but any basic example of Server->Client invocation without a Client first making a request would be amazing!
Hub:
[HubName("chat")]
public class Chat : Hub
{
public void Send(string message)
{
// Call the addMessage method on all clients?
Clients.addMessage(message);
}
}
Calling (Threaded) method:
private void DoIt()
{
int i = 0;
while (true)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<Chat>();
hubContext.Clients.addMessage("Doing it... " + i);
i++;
Thread.Sleep(500);
}
}
JS:
$(function () {
// Proxy created on the fly
var chat = $.connection.chat;
// Declare a function on the chat hub so the server can invoke it
chat.addMessage = function (message) {
confirm("Are you having fun?");
confirm(message);
};
// Start the connection
$.connection.hub.start();
});
The issue I had was a self closing JS import tag which stopped all JS on the page being run...
For others who have the same issue, here is my working example on a Server pushing data out to all clients without any prompting from a client:
Javascript:
$(function () {
// Proxy created on the fly
var chat = $.connection.chat;
// Declare a function so the hub can invoke it
chat.addMessage = function (message) {
document.getElementById('lblQuestion').innerHTML = message;
};
// Start the connection
$.connection.hub.start();
});
HTML:
<h2 id="lblQuestion" runat="server">Please wait for a question...</h2>
Hub:
[HubName("chat")]
public class Chat : Hub
{
public void Send(string message)
{
// Call the addMessage method on all clients
Clients.addMessage(message);
}
public void Broadcast(string message)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<Chat>();
context.Clients.addMessage(message);
}
}
Call to clients:
private void DoIt()
{
int i = 0;
while (true)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<Chat>();
hubContext.Clients.addMessage("Doing it... " + i);
i++;
Thread.Sleep(500);
}
}
Threaded call to DoIt():
var thread = new Thread(new ThreadStart(DoIt));
thread.SetApartmentState(ApartmentState.STA);
thread.Start();

Consuming Multiple .NET Web Services from Javascript in Parallel

I have two ASMX web services consumed from javascript. The first service performs a long operation and updates a database table on its progress. The second service polls that same database table in order to report the progress to the end user via a progress bar.
My problem is that the long process seems to be blocking the polling service. When I log the activity of the javascript, it seems to be requesting the long service correctly, and then starts to request the polling service once a second asynchronously (note: the long process is asynch as well). Both request types either use setInterval or setTimeout which shouldn't halt the browser. Yet when I look at the activity of the javascript, none of the responses from the polling requests return until the long process completes. So it seems the long process is blocking the polling requests until it's done.
Here's the nitty gritty:
JavaScript:
var percentComplete = 0;
setTimeout(function ()
{
MyWebService.CreateBulkOrder(serverVariable, function (result, eventArgs)
{
percentComplete = 100;
completeOperation(result);
});
}, 0);
var intID = setInterval(function ()
{
if (percentComplete < 100)
{
MyWebService.GetStatus(serverVariable, callback);
}
else
{
clearInterval(intID);
}
}, 1000);
Service Code (VB.NET - Note: code is changed to make it generic)
<System.Web.Script.Services.ScriptService()>
<System.Web.Services.WebService(Namespace:="http://mydns.com/webservices")>
<System.Web.Services.WebServiceBinding(ConformsTo:=WsiProfiles.BasicProfile1_1)>
<ToolboxItem(False)>
Public Class MyWebServices
Inherits System.Web.Services.WebService
<WebMethod(EnableSession:=True)>
Public Function GetStatus(serverVariable As Integer) As Object
Dim currentPage As Integer = 0
Dim totalPages As Integer = Math.Ceiling(CType(If(Session("Number of Records"), Double) / CType(ConstantsCommon.TOTAL_PER_PAGE, Double))
Using clientDB As ClientDataContext = FunctionsOrderMgmt.ClientConnectionReadOnly
Dim repeatPageQuery = From repeatPage In clientDB.RepeatPages
Where repeatPage.KEY = serverVariable
Select repeatPage
Dim repeatPageData = repeatPageQuery.SingleOrDefault()
If repeatPageData Is Nothing Then
currentPage = 0
Else
currentPage = If(repeatPageData.REPEAT_PAGE, 0)
End If
Return New With {.TotalPages = totalPages, .CurrentPage = currentPage}
End Using
End Function
<WebMethod(EnableSession:=True)>
Public Function CreateBulkOrder(serverVariable As Integer) As Boolean
If Not TestsPass Then
Return False
End If
Try
'Do stuff that takes a long time
Catch ex As Exception
Return False
End Try
Return True
End Function
End Class
Add 'OneWay = true' to the CreateBulkOrder Webmethod otherwise it will be waiting for a response.
http://msdn.microsoft.com/en-us/library/system.web.services.protocols.soapdocumentmethodattribute.oneway(v=vs.71).aspx

Categories