I'm making a little chat application with SignalR 2.0 (just like everyone).
I have a win8.1 application and when the application is closed, the hub receives the OnDisconnected event and removes the user from the list on the hub.
The hub sends to every client that the user has left the chat, so we can visualize that the user has left.
But when I'm using SignalR and Javascript in a webpage and the page gets closed, the hub doesn't get notified that the tab/browser is closed...
Anyone any idea how the connection can be closed?
What i've coded:
Startup Hub
[assembly: OwinStartup(typeof(ChatServer.Startup))]
namespace ChatServer
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
// Map all hubs to "/signalr"
app.MapSignalR();
}
}
}
Hub
[HubName("ChatHub")]
public class ChatHub : Hub
{
private static List<User> Users = new List<User>();
public void Send(string name, string message)
{
// Call the broadcastMessage method to update clients.
Clients.All.broadcastMessage(name, message, DateTime.Now);
}
public void UserConnected(string name)
{
Clients.Caller.connected(JsonConvert.SerializeObject(Users));
User user = new User() { Name = name, ConnectionID = Context.ConnectionId };
Clients.Others.userConnected(JsonConvert.SerializeObject(user));
Users.Add(user);
}
public void UserLeft()
{
if(Users.Any(x=>x.ConnectionID == Context.ConnectionId))
{
User user = Users.First(x => x.ConnectionID == Context.ConnectionId);
Users.Remove(user);
Clients.Others.userLeft(JsonConvert.SerializeObject(user), Context.ConnectionId);
}
}
public override System.Threading.Tasks.Task OnDisconnected()
{
// Only called when win8.1 app closes
// Not called when browsers closes page
UserLeft();
return base.OnDisconnected();
}
}
HTML - Javascript:
Javascript:
chat = new function () {
var ChatHub;
// Connecting to the hub
this.attachEvents = function () {
if ($.connection != null) {
ChatHub = $.connection.ChatHub;
$.connection.hub.start({ transport: 'auto' }, function () {
// Register client on hub
ChatHub.server.userConnected("web name");
});
}
this.send = function (name,message) {
if ($.connection != null) {
//Send chat message
ChatHub.server.send(name, message).fail(function (e) {
alert(e);
});
}
}
}
};
window.onbeforeunload = function (e) {
//This is called when we close the page
$.connection.hub.stop();
return "You killed me! :'(";
};
Win8.1 client
internal async void ConnectToHub(string userName)
{
try
{
HubConnection hubConnection = new HubConnection(SERVER_PROXY);
chat = hubConnection.CreateHubProxy("ChatHub");
Context = SynchronizationContext.Current;
MakeHubFunctionsAvailableOnClient();
await hubConnection.Start();
// Register client on hub
await chat.Invoke("UserConnected", userName);
}
catch (Exception)
{
throw;
}
}
private void MakeHubFunctionsAvailableOnClient()
{
//Receive chat messages
chat.On<string, string, DateTime>("broadcastMessage",
(name, message, date) => Context.Post(
delegate {
Messages.Add(new UserMessage() { Message = message, User = name, Date = date });
}, null
)
);
//Receive all online users
chat.On<string>("connected",
(users) => Context.Post(
delegate
{
List<User> userList = JsonConvert.DeserializeObject<List<User>>(users);
foreach (User user in userList)
{
Users.Add(user);
}
Messages.Add(new UserMessage() { Message = "CONNECTED", User = "System", Date = DateTime.Now });
}, null
)
);
//New user connected
chat.On<string>("userConnected",
(user) => Context.Post(
delegate
{
User newUser = JsonConvert.DeserializeObject<User>(user);
Users.Add(newUser);
Messages.Add(new UserMessage() { Message = newUser.Name + " CONNECTED", User = "System", Date = DateTime.Now });
}, null
)
);
//User left, remove user from list on client
chat.On<string>("userLeft",
(user) => Context.Post(
delegate
{
User newUser = JsonConvert.DeserializeObject<User>(user);
User y = Users.First(x=>x.ConnectionID == newUser.ConnectionID);
bool ux = Users.Remove(y);
Messages.Add(new UserMessage() { Message = newUser.Name + " left the conversation", User = "System", Date = DateTime.Now });
}, null
)
);
}
My hub doesn't trigger OnDisconnected, when i close tab / browser / navigating to other site
The site is a singe page website (for the moment)
What browser am i using? Chrome: Version 32.0.1700.68 beta-m
Internet Explorer 11
I guess your question is how to detect whether a specific user left or closed the browser. When a user comes in or leaves, send a notification to other users.
I think the problem is you didn't handle OnConnected and OnDisconnected properly.
To make it work, first you need to know each client connecting to a hub passes a unique connection id. You can retrieve this value in the Context.ConnectionId property of the hub context. When each client comes in, they go through OnConnected, when they leave, they go through OnDisconnected. Below is a working example:
Hub Class
[HubName("ChatHub")]
public class ChatHub : Hub
{
private static List<User> Users = new List<User>();
public void Send(string message)
{
var name = Context.User.Identity.Name;
Clients.All.broadcastMessage(name, message, DateTime.Now.ToShortDateString());
}
public override Task OnConnected()
{
User user = new User() { Name = Context.User.Identity.Name, ConnectionID = Context.ConnectionId };
Users.Add(user);
Clients.Others.userConnected(user.Name);
return base.OnConnected();
}
public override Task OnDisconnected()
{
if (Users.Any(x => x.ConnectionID == Context.ConnectionId))
{
User user = Users.First(x => x.ConnectionID == Context.ConnectionId);
Clients.Others.userLeft(user.Name);
Users.Remove(user);
}
return base.OnDisconnected();
}
}
View
<input type="text" id="message" />
<button class="btn">Send</button>
<ul id="contents"></ul>
#section scripts
{
<script src="~/Scripts/jquery-1.10.2.js"></script>
<script src="~/Scripts/jquery.signalR-2.0.1.js"></script>
<script src="/signalr/hubs"></script>
<script>
var chatHub = $.connection.ChatHub;
chatHub.client.broadcastMessage = function (name, message, time) {
var encodedName = $('<div />').text(name).html();
var encodedMsg = $('<div />').text(message).html();
var encodedTime = $('<div />').text(time).html();
$('#contents').append('<li><strong>' + encodedName + '</strong>: ' + encodedMsg + '</strong>: ' + encodedTime + '</li>');
};
chatHub.client.userConnected = function (data) {
$('#contents').append('<li>Wellcome<strong>' + '</strong>:' + data + '</li>');
};
chatHub.client.userLeft = function (data) {
$('#contents').append('<li>Bye<strong>' + '</strong>:' + data + '</li>');
};
$(".btn").on("click", function() {
chatHub.server.send($("#message").val());
});
$.connection.hub.start().done(function() {
console.log("connected...");
});
</script>
}
You might be running into the issue described here: https://github.com/SignalR/SignalR/issues/2719
tl;dr: There is a bug introduced in Chrome 31 that is already fixed in Chrome Canary, that prevents SignalR from immediately trigger OnDisconnected.
If you are, you can use the workaround I suggested in the issue which is to add the following to your JS code.
window.onbeforeunload = function (e) {
$.connection.hub.stop();
};
Even with this bug and without the workaround I would expect SignalR to raise OnDisconnected approximately 35s after the browser exits the page hosting SignalR.
Can you provide more details about which browsers you are experiencing this issue with and in what situations? (e.g. closing the tab, closing the window, refreshing, navigating to another page on the same site, navigating to a different site, etc.)
Related
I have application on which I get all username when someone login but I need to click the button to show all users,I want to show all user on real time as soon as someone login.However I am able to logout user on real time.
This below code working well for getting username on click button and real time logout users.
[Authorize]
public class AuthHub : Hub
{
private static ConcurrentDictionary<string, User> ActiveUsers = new ConcurrentDictionary<string, User>(StringComparer.InvariantCultureIgnoreCase);
public IEnumerable<string> GetConnectedUsers()
{
return ActiveUsers.Where(x => {
lock (x.Value.ConnectionIds)
{
return !x.Value.ConnectionIds.Contains(Context.ConnectionId, StringComparer.InvariantCultureIgnoreCase);
}
}).Select(x => x.Key);
}
public override Task OnConnected()
{
string userName = Context.User.Identity.Name;
string connectionId = Context.ConnectionId;
var user = ActiveUsers.GetOrAdd(userName, _ => new User
{
Name = userName,
ConnectionIds = new HashSet<string>()
});
lock (user.ConnectionIds)
{
user.ConnectionIds.Add(connectionId);
}
return base.OnConnected();
}
private User GetUser(string username)
{
User user;
ActiveUsers.TryGetValue(username, out user);
return user;
}
public void forceLogOut(string to)
{
User receiver;
if (ActiveUsers.TryGetValue(to, out receiver))
{
User sender = GetUser(Context.User.Identity.Name);
IEnumerable<string> allReceivers;
lock (receiver.ConnectionIds)
{
allReceivers = receiver.ConnectionIds.Concat(receiver.ConnectionIds);
}
foreach (var cid in allReceivers)
{
Clients.Client(cid).Signout();
}
}
}
}
client side code
$(function () {
var chat = $.connection.authHub;
chat.client.Signout = function () {
$('#logoutForm').submit();
$.connection.hub.stop();
};
$.connection.hub.start().done(function () {
console.log("working");
function PopulateActiveUsers() {
$("#usersTable").empty();
chat.server.getConnectedUsers().done(function (users) {
console.log("working");
$.each(users, function (i, username) {
$("#usersTable").append("<tr><th scope='row'>" + (i + 1) + "</th><td>" + username + "</td><td><a href='javascript:void(0)' data-user='" + username + "' class='btn btn-primary btn-sm btn-logout'>Logout User</a></td></tr>");
});
});
}
$("#displayActiveUsers").on("click", function () {
PopulateActiveUsers();
});
$('body').on('click', 'a.btn-logout', function () {
var username = $(this).attr('data-user');
chat.server.forceLogOut(username);
});
});
});
You would add that same method you call from the client to fire during the OnConnected() event. Example:
public override Task OnConnected()
{
//Get your list of users however you do that now
//Send back to all clients
Client.All.yourClientMethod(users);
}
In your client you just need a client side method to receive the data and process it just as you do now.
I have solved my problem by doing some modification in my code like so
public void GetConnectedUsers()
{
Clients.All.getConnectedUsers(ActiveUsers.Where(x =>
{
lock (x.Value.ConnectionIds)
{
return x.Value.ConnectionIds.Contains(Context.ConnectionId, StringComparer.InvariantCultureIgnoreCase);
}
}).Select(x => x.Key).ToList());
//return Clients.All(ActiveUsers.Select(x => x.Key).ToList());
}
//client side code
$.connection.hub.start().done(function () {
chat.server.getConnectedUsers().done(function (users) { });
});
chat.client.getConnectedUsers = function (users) {
$.each(users, function (i, username) {
$("#usersTable").append("<tr><th scope='row'>" + (i + 1) + "</th><td>" + username + "</td><td><a href='javascript:void(0)' data-user='" + username + "' class='btn btn-primary btn-sm btn-logout'>Logout User</a></td></tr>");
});
};
I'm creating an ASP.NET MVC application which uses "SqlDependecy" and "SignalR" technologies to maintain real-time communication with the server based on database changes. It simply inspect a field value changes in specific database record and then display it on the browser.
The attempt works perfectly fine. But when I monitor the network requests through the browsers "Network" performance, the request count increases by 1 in every refresh of the page.
As in the image.
Initial page load only make one request.
First refresh after the initial load and then db change will lead to make 2 requests.
Second refresh after the initial load and then db change will lead to make 3 requests.
so on...
The js code I tried is given below.
It seams as an problem to me. If this is a real problem, Any advice on this will be highly appreciated. Thank you very much.
<script type="text/javascript">
$(function () {
var jHub = $.connection.journeyHub;
$.connection.hub.start();
jHub.client.ListenChange = function () {
getData();
}
jHub.client.ListenChange();
});
function getData() {
$.ajax({
url: 'GetValue',
type: 'GET',
dataType: 'json',
success: function (data) {
if (data == "pending") {
$("#box").css({ "background-color": "orange" });
}
else if (data == "deny") {
$("#box").css({ "background-color": "red" });
}
else if (data == "success") {
$("#box").css({ "background-color": "green" });
}
}
});
}
</script>
<div id="box" style="width:100px; height:100px; background-color: gray;"></div>
[Edit v1]
Here is my Controller where the event handler is located.
public class TravelController : Controller
{
SqlConnection link = new SqlConnection(ConfigurationManager.ConnectionStrings["linkTraveller"].ConnectionString);
// GET: Travel
public ActionResult Listen()
{
return View();
}
public ActionResult GetValue()
{
using (IDbConnection conn = link)
{
string query = #"SELECT [Status] FROM [dbo].[Journey] WHERE [Id]=1";
SqlCommand command = new SqlCommand(query, link);
SqlDependency sqlDep = new SqlDependency(command);
sqlDep.OnChange += new OnChangeEventHandler((sender, e) => sqlDep_OnChange(sender, e));
conn.Open();
string status = command.ExecuteScalar().ToString();
return Json(status, JsonRequestBehavior.AllowGet);
}
}
private void sqlDep_OnChange(object sender, SqlNotificationEventArgs e)
{
JourneyHub.Start();
}
}
Here is the Hub
public class JourneyHub : Hub
{
public static void Start()
{
var context = GlobalHost.ConnectionManager.GetHubContext<JourneyHub>();
context.Clients.All.ListenChange();
}
}
Off the top of my head, I would say you are not decrementing your trigger handlers, sql dependency triggers only fire once and then they are gone, you have to remember the remove the event handler for it or they just keep adding but, but I will know for sure if you can post your sql dependency trigger code.
Here is a sample from something I did many years ago, but the idea is still the same.
try
{
using (
var connection =
new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
connection.Open();
using (SqlCommand command = new SqlCommand(#"SELECT [Id]
,[FName]
,[LName]
,[DOB]
,[Notes]
,[PendingReview]
FROM [dbo].[Users]",
connection))
{
// Make sure the command object does not already have
// a notification object associated with it.
command.Notification = null;
SqlDependency dependency = new SqlDependency(command);
dependency.OnChange += new OnChangeEventHandler(dependency_OnChange);
if (connection.State == ConnectionState.Closed)
connection.Open();
command.ExecuteReader();
}
}
}
catch (Exception e)
{
throw;
}
}
private void dependency_OnChange(object sender, SqlNotificationEventArgs e)
{
SqlDependency dependency = sender as SqlDependency;
if (dependency != null) dependency.OnChange -= dependency_OnChange;
//Recall your SQLDependency setup method here.
SetupDependency();
JobHub.Show();
}
I have to download a file a day from a website.
I have to log in, go to the download page, insert strings in a form, then click on an "Export" button which starts a js script which submits the form and starts the download. I tried pretty much everything, understood that JS couldn't be handled by the HtmlAgilityPack.
I used ScrapySharp to simulate a web browser. I can log in, go to the export page, click on the button and get the response, but can't handle the download.
Here's my code :
public class GetCSVFileTask : BaseTask
{
ScrapingBrowser Browser;
public GetCSVFileTask(IStateMachine StateMachine, ITraceEngine Trace, ILogEngine Log)
: base(StateMachine, Trace, Log)
{
this.Log = Log;
this.Trace = Trace;
this.stateMachine = StateMachine;
}
public void Process()
{
try
{
Init();
Login();
DownloadCSV();
}
catch (Exception ex)
{
Log.Write(LogLevel.Error, string.Format(CultureInfo.InvariantCulture, "Exception: {0}.", ex.ToString()));
}
}
private void Init()
{
Browser = new ScrapingBrowser();
Browser.AllowAutoRedirect = true;
Browser.AllowMetaRedirect = true;
Browser.AutoDetectCharsetEncoding = true;
Browser.AutoDownloadPagesResources = true;
}
private void Login()
{
var test = new Uri(ConfigurationManager.AppSettings.Get("LoginURL"));
WebPage LoginPage = Browser.NavigateToPage(test);
var AuthForm = GetAuthForm(LoginPage, Browser);
AuthForm["Email"] = ConfigurationManager.AppSettings.Get("Login");
AuthForm["Password"] = ConfigurationManager.AppSettings.Get("Password");
AuthForm.Submit();
}
private void DownloadCSV()
{
WebPage ExportPage = Browser.NavigateToPage(new Uri(ConfigurationManager.AppSettings.Get("ExportURL")));
PageWebForm DLForm = GetDlForm(ExportPage, Browser);
WebPage test = DLForm.Submit();
}
private PageWebForm GetAuthForm(WebPage loginPage, ScrapingBrowser browser)
{
//temp[0] = logout form
//temp[1] = login form
var temp = (from n in loginPage.Encoding.GetString(loginPage.RawResponse.Body).ToHtmlNode().Descendants("form")
select n).ToList();
return new PageWebForm(temp[1], browser);
}
private PageWebForm GetDlForm(WebPage exportPage, ScrapingBrowser browser)
{
//temp[0] = logout form
//temp[1] = download form
return GetAuthForm(exportPage, browser);
}
}
The issue I have at the moment is that I don't know how to "catch" the file download and download it in a local folder
Sorry for my bad english, i'm french and new in the developer world :)
I seem to have a problem sending an alert (toast) message from a controller action using SignalR. Unless I add a Thread.Sleep() after the send call, I never see the message. Inevitably, the send call occurs before return View(), so I imagine the alert is visible for a millisecond on the previous view, until the new one is served.
My first, rough solution is to use a timer to keep sending the message, until I get an acknowledgement.
This solution stinks. What else can I do? I can have the 'receiving' pages poll the server to see if they have any alerts, but that defeats the purpose of SignalR.
But then, maybe SignalR isn't suited to my case and I should just send the alerts as json strings on the model.
From the Login action:
....
//ModelState.AddModelError("", "Invalid login attempt.");
AlertsHub.ShowClientAlert(new Alert(AlertLevel.Error, "Invalid login attempt."));
return View(model);
The hub:
public class AlertsHub : Hub
{
private static Alert pendingAlert;
static Timer pollTimer;
internal static void ShowClientAlert(Alert alert)
{
if (pendingAlert != null)
{
return;
}
pendingAlert = alert;
pollTimer = new Timer(_ => SendAlert(pendingAlert), null, 0, 500);
}
static private void SendAlert(Alert alert)
{
IHubContext context = GlobalHost.ConnectionManager.GetHubContext<AlertsHub>();
context.Clients.All.ShowAlert(alert.Level.ToString(), alert.Message, alert.Title);
}
[HubMethodName("AlertReceived")]
public void AlertReceived(Guid alertId)
{
pollTimer.Dispose();
pendingAlert = null;
}
}
From the JS:
var toast;
$(function () {
if (typeof toast != 'undefined' && toast != null) {
showToast(toast);
toast = null;
}
var alertsProxy = $.connection.alertsHub;
alertsProxy.client.showAlert = function(alertId, level, message, title) {
toast.alertId = alertId;
toast.level = level;
toast.message = message;
toast.title = title;
};
$.connection.hub.start()
.done(function () {
console.log('Now connected, connection ID=' + $.connection.hub.id);
})
.fail(function () {
console.log('Could not Connect!');
});
});
function showToast(toast) {
switch (toast.level.toLowerCase()) {
case "success":
toastr.success(toast.message, toast.title);
break;
...
}
alertsProxy.server.AlertReceived(alertId)
.done(function() {
console.log("Alert '" + alertId + "' acknowledged.");
})
.fail(function() {
console.log("Acknowledgement of alert '" + alertId + "' failed.");
});
}
I am seeing odd behavior with the code here.
Client-side (Javascript):
<input type="text" id="userid" placeholder="UserID" /><br />
<input type="button" id="ping" value="Ping" />
<script>
var es = new EventSource('/home/message');
es.onmessage = function (e) {
console.log(e.data);
};
es.onerror = function () {
console.log(arguments);
};
$(function () {
$('#ping').on('click', function () {
$.post('/home/ping', {
UserID: parseInt($('#userid').val()) || 0
});
});
});
</script>
Server-side (C#):
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Web.Mvc;
using Newtonsoft.Json;
namespace EventSourceTest2.Controllers {
public class PingData {
public int UserID { get; set; }
public DateTime Date { get; set; } = DateTime.Now;
}
public class HomeController : Controller {
public ActionResult Index() {
return View();
}
static ConcurrentQueue<PingData> pings = new ConcurrentQueue<PingData>();
public void Ping(int userID) {
pings.Enqueue(new PingData { UserID = userID });
}
public void Message() {
Response.ContentType = "text/event-stream";
do {
PingData nextPing;
if (pings.TryDequeue(out nextPing)) {
var msg = "data:" + JsonConvert.SerializeObject(nextPing, Formatting.None) + "\n\n";
Response.Write(msg);
}
Response.Flush();
Thread.Sleep(1000);
} while (true);
}
}
}
Once I've pressed ping to add a new item to the pings queue, the loop inside the Message method picks the new item up and issues an event, via Response.Write (confirmed using Debug.Print on the server). However, the browser doesn't trigger onmessage until I press ping a second time, and the browser issues another event; at which point the data from the first event reaches onmessage.
How can I fix this?
To clarify, this is the behavior I would expect:
Client Server
-------------------------------------------------------------------
Press Ping button
XHR to /home/ping
Eneque new item to pings
Message loop issues server-sent event
EventSource calls onmessage
This is what is actually happening:
Client Server
-------------------------------------------------------------------
Press Ping button
XHR to /home/ping
Eneque new item to pings
Message loop issues server-sent event
(Nothing happens)
Press Ping button again
New XHR to /home/ping
EventSource calls onmessage with previous event data
(While running in Chrome the message request is listed in the Network tab as always pending. I'm not sure if this is the normal behavior of server-sent events, or perhaps it's related to the issue.)
Edit
The string representation of the msg variable after Response.Write looks like this:
"data:{\"UserID\":105,\"Date\":\"2016-03-11T04:20:24.1854996+02:00\"}\n\n"
very clearly including the newlines.
This isn't an answer per say but hopefully it will lead one. I was able to get it working with the following code.
public void Ping(int id)
{
pings.Enqueue(new PingData { ID = id });
Response.ContentType = "text/plain";
Response.Write("id received");
}
public void Message()
{
int count = 0;
Response.ContentType = "text/event-stream";
do {
PingData nextPing;
if (pings.TryDequeue(out nextPing)) {
Response.ClearContent();
Response.Write("data:" + nextPing.ID.ToString() + " - " + nextPing.Date.ToLongTimeString() + "\n\n");
Response.Write("event:time" + "\n" + "data:" + DateTime.Now.ToLongTimeString() + "\n\n");
count = 0;
Response.Flush();
}
if (!Response.IsClientConnected){break;}
Thread.Sleep(1000);
count++;
} while (count < 30); //end after 30 seconds of no pings
}
The line of code that makes the difference is the second Response.Write. The message doesn't appear in the browser until the next ping similar to your issue, but the ping always appears. Without that line the ping will appear only after the next ping, or once my 30 second counter runs out.
The missing message appearing after the 30 second timer leads me to conclude that this is either a .Net issue, or there's something we're missing. It doesn't seem to be an event source issue because the message appears on a server event, and I've had no trouble doing SSE with PHP.
For reference, here's the JavaScript and HTML I used to test with.
<input type="text" id="pingid" placeholder="ID" /><br />
<input type="button" id="ping" value="Ping" />
<div id="timeresponse"></div>
<div id="pingresponse"></div>
<script>
var es = new EventSource('/Home/Message');
es.onmessage = function (e) {
console.log(e.data);
document.getElementById('pingresponse').innerHTML += e.data + " - onmessage<br/>";
};
es.addEventListener("ping", function (e) {
console.log(e.data);
document.getElementById('pingresponse').innerHTML += e.data + " - onping<br/>";
}, false);
es.addEventListener("time", function (e) {
document.getElementById('timeresponse').innerHTML = e.data;
}, false);
es.onerror = function () {
console.log(arguments);
console.log("event source closed");
es.close();
};
window.onload = function(){
document.getElementById('ping').onclick = function () {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onload = function () {
console.log(this.responseText);
};
var url = '/Home/Ping?id=' + document.getElementById('pingid').value;
xmlhttp.open("GET", url);
xmlhttp.send();
};
};
</script>
Since an eventstream is just text data, missing the double line break before the first event is written to response could affect the client. The example from mdn docs suggests
header("Content-Type: text/event-stream\n\n");
Which could be applied apply to .NET response handling (note the side effects of Response.ClearContent()).
If it feels too hacky, you could start your stream with a keep-alive comment (if you want to avoid timing out you may have to send comments periodically):
: just a keep-alive comment followed by two line-breaks, Response.Write me first
I'm not sure if this will work because I can't try it now, but what about to add an End?:
Response.Flush();
Response.End();
The default behavior of .net is to serialize access to session state. It blocks parallel execution. Requests are processed sequentially and access to session state is exclusive for the session. You can override the default state per class.
[SessionState(SessionStateBehavior.Disabled)]
public class MyPulsingController
{
}
There is an illustration of this in the question here.
EDIT: Would you please try creating the object first and then passing it to Enqueue? As in:
PingData myData = new PingData { UserID = userID };
pings.Enqueue(myData);
There might be something strange going on where Dequeue thinks it's done the job but the the PingData object isn't properly constructed yet.
Also can we try console.log("I made it to the function") instead of console.log(e.data).
---- PREVIOUS INFORMATION REQUESTED BELOW ----
Please make sure that the server Debug.Print confirms this line of code:
Response.Write("data:" + JsonConvert.SerializeObject(nextPing, Formatting.None) + "\n\n");
Is actually executed? Please double check this. If you can capture the server sent response then can we see what it is?
Also could we see what browsers you've tested on? Not all browsers support server events.