I'm sorry for potentially creating a duplicate thread here, but I simply cannot get my web application to do what I need by following the other examples I've found.
My goal is to do one of the following:
OPTION 1 - IDEAL SOLUTION
Fetch data from a database and update the UI on a web page ONLY when changes are made to the data being displayed on the web page. For example, if a user is viewing a service ticket, I don't want to update the UI on that page unless that ticket is changed.
OPTION 2 - ACCEPTABLE SOLUTION
Fetch data from a database every x seconds and use that data to update the UI on a web page.
My current implementation of Option 2 is below. It involves sending an asynchronous HTTP request every 60 seconds to fetch the data:
// start checking for new messages every 60 seconds
setInterval(function () {
$.ajax({
async: true,
type: "POST",
contentType: "application/json; charset=utf-8;",
url: "/AJAX_Handlers/CheckForNewMessages.ashx",
dataType: "json",
success: function (Result) {
var new_message_received = Result[0]["NewMessageReceived"];
if (new_message_received) {
$("#DIVMessageReminder").html("<strong>You have " + num_new_messages + " new message(s).</strong>");
$("#DIVMessageReminder").show();
}
else {
$("#DIVMessageReminder").hide();
}
}
});
}, 60000);
Rather than sending an HTTP request every 60 seconds, I would like to use SignalR to push that data to the client every 60 seconds.
As a simple example, I have created the following Hub with a method to get the current time on the server:
Imports Microsoft.AspNet.SignalR
Public Class ServerTimeHub
Inherits Hub
Public Sub GetServerTime()
Dim current_time As String = Now.ToString()
Clients.All.updateTime(current_time)
End Sub
End Class
And a basic textbox:
<input id="TXTLongPollingTest" type="text" class="form-control" />
And my client-side code:
var hub = $.connection.serverTimeHub;
hub.client.updateTime = function (new_time) {
$("#TXTLongPollingTest").val(new_time);
}
$.connection.hub.start().done(function () {
alert("connected to the SignalR hub");
hub.getServerTime();
}).fail(function (err) {
alert("failed to connect to SignalR hub: " + err);
});
At first I tried getting it to fetch the server time just once. My code will successfully connect to the hub, but then it throws an error saying "Uncaught TypeError: hub.getServerTime is not a function". That's the first problem I haven't been able to overcome.
The second problem is: How can I get the hub to send the current time to the client on a regular interval such as every 1 second?
Here is what I have done to achieve something similar. Essentially fetching data from the database and broadcasting to clients every 30 seconds.
In my global.asax.cs I have this to ensure whenever my website is has started/restarted it will kick off my repeater:
protected void Application_Start(object sender, EventArgs e)
{
GetTeamData.TeamDataRepeater();
}
In my GetTeamData.cs I have a timer that is set to run every 30 seconds
public class GetTeamData
{
static Timer TeamDataTimer = new Timer();
public static void TeamDataRepeater()
{
TeamDataTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnTimedEvent_TeamDataBroadcaster);
TeamDataTimer.Interval = 30000; //30 Seconds
TeamDataTimer.Start();
}
public static void OnTimedEvent_TeamDataBroadcaster(Object sender, ElapsedEventArgs e)
{
updateFirstRow();
}
public static void updateFirstRow()
{
IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<MsgHub>();
hubContext.Clients.All.pushMyData(mydata1, mydata2, mydata3);
}
}
My java script for the client has:
//I have already started my connection
$(function () {
var chat = $.connection.msgHub;
chat.client.pushMyData = function (mydata1, mydata2, mydata3)
{
//Do something with the returned data now
}
});
Note that I have removed some things such as use of try/catch just to give you an example.
Hope that helps.
Related
I am using SignalR to redirect my app after session timeout:
void Session_End(object sender, EventArgs e)
{
var HubContext = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
HubContext.Clients.All.clientListener(false);}
And my Hub class:
public class MyHub : Hub
{
public void ServerListener(bool result)
{
Clients.Caller.ClientListener(result);
}
}
JavaScript:
(function () {
var myHub = $.connection.myHub;
$.connection.hub.start()
.done(function () {
console.log("Connected");
})
.fail(function () {
alert("Failed!");
})
myHub.client.clientListener = function (data) {
if (data == false)
window.location.href = "/Home/Index";//#Url.Action("Index","Home");
}
})();
The Problems that I face is:
When multiple users are connected all users are logged out at the same time, even if they logged in at different times.
Logout redirection occurs even if regular requests are made.
It would really help me, if someone could tell me how to do a "server push" without invoking the Session_End() as I want to use some other session state other than "In Proc".
Regarding Question #1:
Since you're using HubContext.Clients.All.clientListener all users connected to SignalR are indeed going to receive the message from the server - it doesn't matter when they logged in to your app.
You should use this guide to send a message from the server to a specific user: https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/mapping-users-to-connections .
Personally I like single-user groups idea https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/mapping-users-to-connections#single-user-groups.
As for the additional question, as long as you can get the hub reference with GlobalHost.ConnectionManager.GetHubContext<MyHub>() all you have to do is call a registered client side method to perform a server push (e.g. Clients.Group(userid).clientListener(false)). It's not something that has to be done exclusively on Session_End().
What I did is:
On Page_Load I update a hidden label with the Session.SessionId
Pass it via query string before starting hub on client:
$.connection.hub.qs = { "sessionId": $("#lblSessionId").text() };
On the server side in the hub OnConnected/OnReconnected I call this method to store the correlation of sessionId->connectionId in dictionary:
private void AddConnectedClient()
{
// read the [ASP.Net] sessionId we send from client in queryString
string sessionId = Context.Request.QueryString["sessionId"];
if (!string.IsNullOrEmpty(sessionId))
sessionIdToConnectionId.AddOrUpdate(sessionId, Context.ConnectionId, (k, v) => Context.ConnectionId);
}
Now in the Session_End I have this code (GetConnectionIdFromSessionId is a static method I added in hub to read from sessionIdToConnectionId dictionary shown in #3):
protected void Session_End(object sender, EventArgs e)
{
// look for signalR connecitonId
string connectionID = YourHub.GetConnectionIdFromSessionId(Session.SessionID);
if (connectionID != null)
GlobalHost.ConnectionManager.GetHubContext<YourHub>().Clients.Client(connectionID).onSessionExpired();
}
i want when the client send to my web page request,my web page get the request and active timer and wait about 15 minute and check client so connect and response the json string.
my server code is:
string name, count;
protected void Page_Load(object sender, EventArgs e)
{
name = Request.QueryString["name"];
count = Request.QueryString["count"];
Timer1.Interval = 15000;
Timer1.Enabled = true;
}
protected void Timer1_Tick(object sender, EventArgs e)
{
if (Response.IsClientConnected)
{
JavaScriptSerializer jss = new JavaScriptSerializer();
string json=jss.Serialize("در خواست شما تایید شد");
Response.Clear();
Response.Write(json);
Response.End();
}
else
{
Response.End();
}
}
how can i do this?
From your comment : " thanks,i want for example customer send my server a order and when the system manager accept the order,response to client:your order accepted"
You will need to include jQuery to do this bit.
As you have the name and count in the query string, which I take it will help you identify the customer (or change it to an order ID), then you can use the following on your .aspx page in a javascript block:
$(document).ready(function(){
setTimeout(function()
{
location.reload();
}, (15 * 60 * 1000)); // (that's 15 mins that I couldn't be bothered to work out...)
});
Your server side code could then be:
protected void Page_Load(object sender, EventArgs e)
{
var name = Request.QueryString["name"];
var count = Request.QueryString["count"];
if(OrderAccepted(name, count))
{
// display message to user
}
}
private bool OrderAccepted(string name, string count)
{
// do what you need to do to find out if the order is accepted.
}
You may also want to try and have the javascript only run if the order is not accepted so that it doesn't keep running. But a 15 min interval is a decent long period, and most people will close their browsers once they know the order is accepted.
Hope this helps.
You cannot achieve what you are trying to do through "only" server-side (compiled C#) code.
The problem is that you want the client to send a request every fifteen minutes - to get the latest JSON - but you are trying to tell the server to do that.
There are many better ways to accomplish what you are trying to do (ie, better than a fifteen-minute timer) but here is one way you can achieve that through JavaScript:
First, expose your server's API call (the call that gets the JSON).
Then, in a script node on the client (ie in a script tag in the HTML you send down), define the following function:
function refreshDataAndUpdatePage() {
$.ajax({
url: 'your/url/to/the/action',
success: function (data) {
// use data JSON to update page.
// then, call this function again recursively after a timeout of 15 minutes = 900 s = 900,000 ms
setTimeout(refreshDataAndUpdatePage, 900000);
}
}
And of course, call it somewhere.
I seem to have an issue with SignalR's JS Client Hub.
The problem is that the 'on' handler does not seem to work - it generates no error but doesn't receive any signals sent by the server.
The code below shows an extract where I call the server (using the invoke) which works fine - then on the server I call back to acceptHubData which should be picked up on the client but isn't.
My objective is when navigating to pages that each page will open a connection to a specific hub and releases this connection when the user moves to another page!!
EDIT: using the following code snippet works but I wonder why the code further below using the 'on' event doesn't work!
var superHub = $.connection.mySuperHub;
superHub.client.acceptHubData = function (data) {
$('<li>hello there' + data + '</li>').prependTo($('#ul1'))
}
$.connection.hub.start().done(function () {
$('<li>done phase 1</li>').prependTo($('#ul1'))
});
Any help would be much appreciated!
This is the client code (in js)
$(document).ready(function () {
var myHub;
try {
var connection = $.hubConnection();
connection.start().done(function () {
myHub = connection.createHubProxy("mySuperHub");
myHub.on('acceptHubData', function (data) {
alert(data); // THIS IS NOT CALLED!
});
myHub.invoke('AcceptSignal', "hello from the client2");
});
}
catch (e) {
alert(e.message);
}
});
This is the Server code:
[HubName("mySuperHub")]
public class MyHub : Hub
{
private readonly HubEngine _hubEngine;
public MyHub() : this(HubEngine.Instance) { }
public MyHub(HubEngine hubEngine)
{
_hubEngine = hubEngine;
}
public void AcceptSignal(string msg)
{
Clients.Caller.acceptHubData("hi");
Clients.All.acceptHubData("hi");
}
}
You can still use the on method to add events for JS client hub method calls in the latest version of SignalR, but if you do not add any event listeners to a hubProxy before calling hubConnection.start(), you will not be subscribed to the hub. SignalR subscribes to the hubs you have event handlers for when the hubConnection starts. If you are not subscribed to your hub, adding any events to that hub after start() won't work.
If you add at least one event listener to the hub before start(), even if it doesn't do anything, you can then add any additional event handlers you want to the hub using on after start() and your handlers will be called.
It doesn't matter if you add an event using hubProxy.on('eventName', function (... or autogeneratedHubProxy.client.eventName = function (... before you call start(), but only on will successfully add event listeners after start() is called.
Not sure which version of SignalR you are using, but I have had more success using the following syntax on my server:
var context = GlobalHost.ConnectionManager.GetHubContext<MyHub>();
context.Clients.All.acceptHubData("hello");
and on my clients:
myHub.client.acceptHubData = function (data) {
console.log(data);
}
I want to send some data from server to all connected clients using hubs after a specific interval. How can I accomplish this using signalr hubs.
Spin up the System.Threading.Timer, and from it's callback broadcast the message using specific hub.
Global.asax:
private Timer timer;
public class Global : System.Web.HttpApplication
{
protected void Application_Start(object sender, EventArgs e)
{
RouteTable.Routes.MapHubs("~/signalr2");
timer = new Timer(TimerCallback(timerCallback), null, Timeout.Infinite, 1000);
}
}
Check the “Broadcasting over a Hub from outside of a Hub” section in SignalR wiki page.
Use ReactiveExtensions and then setup an Observable.Interval call. Then reactive will automatically call the lambda which can broadcast to your clients.
I have stumbled upon this post by Jason Roberts => http://dontcodetired.com/blog/post/Using-Server-Side-Timers-and-SignalR-in-ASPNET-MVC-Applications.aspx
He uses IRegisteredObject and HostingEnvironment.RegisterObject then a System.Threading.Timer in the class that does the work, I haven't tried it myself, but it looks exactly the sort of thing.
Just add
Thread.Sleep(5000);
in your send Method.
Ex:
public void Send(string name, string message)
{
Thread.Sleep(5000);
//call the broadcast message to upadate the clients.
Clients.All.broadcastMessage(name, message);
}
Hope it helps.
Edit
The following code renders the current time for every 5 seconds.
Here is script for it:
<script type="text/javascript">
$(function () {
$.connection.hub.logging = true;
$.connection.hub.start();
// Declare a proxy to reference the hub.
var chat = $.connection.chatHub;
//Appending the responce from the server to the discussion id
chat.client.currentTime = function (time) {
$('#discussion').append("<br/>" + time + "<br/>");
};
// Start the connection.
$.connection.hub.start().done(function () {
//Call the server side method for every 5 seconds
setInterval(function () {
var date = new Date();
chat.client.currentTime(date.toString());
}, 5000);
});
});
</script>
<div id="discussion"></div>
And on the HubClass write the following:
public class ChatHub: Hub
{
public void currentTime(string date)
{
Clients.All.broadCastTime(date);
}
}
I am using MassTransit request and response with SignalR. The web site makes a request to a windows service that creates a file. When the file has been created the windows service will send a response message back to the web site. The web site will open the file and make it available for the users to see. I want to handle the scenario where the user closes the web page before the file is created. In that case I want the created file to be emailed to them.
Regardless of whether the user has closed the web page or not, the message handler for the response message will be run. What I want to be able to do is have some way of knowing within the response message handler that the web page has been closed. This is what I have done already. It doesnt work but it does illustrate my thinking. On the web page I have
$(window).unload(function () {
if (event.clientY < 0) {
// $.connection.hub.stop();
$.connection.exportcreate.setIsDisconnected();
}
});
exportcreate is my Hub name. In setIsDisconnected would I set a property on Caller? Lets say I successfully set a property to indicate that the web page has been closed. How do I find out that value in the response message handler. This is what it does now
protected void BasicResponseHandler(BasicResponse message)
{
string groupName = CorrelationIdGroupName(message.CorrelationId);
GetClients()[groupName].display(message.ExportGuid);
}
private static dynamic GetClients()
{
return AspNetHost.DependencyResolver.Resolve<IConnectionManager>().GetClients<ExportCreateHub>();
}
I am using the message correlation id as a group. Now for me the ExportGuid on the message is very important. That is used to identify the file. So if I am going to email the created file I have to do it within the response handler because I need the ExportGuid value. If I did store a value on Caller in my hub for the web page close, how would I access it in the response handler.
Just in case you need to know. display is defined on the web page as
exportCreate.display = function (guid) {
setTimeout(function () {
top.location.href = 'GetExport.ashx?guid=' + guid;
}, 500);
};
GetExport.ashx opens the file and returns it as a response.
Thank you,
Regards Ben
I think a better bet would be to implement proper connection handling. Specifically, have your hub implementing IDisconnect and IConnected. You would then have a mapping of connectionId to document Guid.
public Task Connect()
{
connectionManager.MapConnectionToUser(Context.ConnectionId, Context.User.Name);
}
public Task Disconnect()
{
var connectionId = Context.ConnectionId;
var docId = connectionManager.LookupDocumentId(connectionId);
if (docId != Guid.Empty)
{
var userName = connectionManager.GetUserFromConnectionId(connectionId);
var user = userRepository.GetUserByUserName(userName);
bus.Publish( new EmailDocumentToUserCommand(docId, user.Email));
}
}
// Call from client
public void GenerateDocument(ClientParameters docParameters)
{
var docId = Guid.NewGuid();
connectionManager.MapDocumentIdToConnection(Context.ConnectionId, docId);
var command = new CreateDocumentCommand(docParameters);
command.Correlationid = docId;
bus.Publish(command);
Caller.creatingDocument(docId);
}
// Acknowledge you got the doc.
// Call this from the display method on the client.
// If this is not called, the disconnect method will handle sending
// by email.
public void Ack(Guid docId)
{
connectionManager.UnmapDocumentFromConnectionId(connectionId, docId);
Caller.sendMessage("ok");
}
Of course this is from the top of my head.