Right now, I have overridden SignalR's OnDisconnect Method as follows:
public override Task OnDisconnected()
{
if (this.Context.User != null)
{
string userName = this.Context.User.Identity.Name;
var repo = new LobbyRepository();
Clients.Group("Lobby").remove(userName);
repo.RemoveFromLobby(userName);
}
return base.OnDisconnected();
}
However, this code is reached every time the user navigates to any view, temporarily breaking the signalR connection. How can I prevent this from happening only when the user is requesting a certain view?
The connection can be maintained as long you are in the same page, if you navigate away, the connection ends.
You can use Ajax to replace the content of your page, using a technique named "click hijacking" https://web.archive.org/web/20160305021055/http://mislav.net/2011/03/click-hijack/
But remember, the connection is associated to your page.
Cheers.
Related
I'm using the latest version of SignalR with jQuery and getting some odd behavior where a user disconnects, but still hangs around in my "UserQueue" despite having disconnected.
I think this may be related to the fact that a page refresh appears to trigger the OnDisconnected and OnConnected events almost simultaneously. When I set a break point in one of these methods and step through, my pointer bounces back and forth between the two methods with each step (F10).
I'd like to run a check with each OnConnected event to find out who is actually connected to my app. I want to fire a JS event from my C# code in the OnConnected event, and then allow the client/front end to fire back a confirmation of the user being present:
public override Task OnConnected()
{
// Check for other users:
Clients.All.checkForOtherUsers();
// Do stuff with my user queue
var curmodelId = GetModelId(Context);
var curUserConnection = GetCurrentUser(Context);
// First create the ledger is missing, setting the modelId as the first key
if (_ledger == null)
{
_ledger = new ConnectionLedger(curmodelId, curUserConnection);
}
else
{
// key not found, create new connection pool this model
if (!_ledger.PoolIds.Contains(curmodelId))
{
_ledger.AddConnectionPool(curmodelId, curUserConnection);
}
else
{
var curModelPool = _ledger.ConnectionPools.First(x => x.Id == curmodelId);
curModelPool.UserQueue.Enqueue(curUserConnection);
}
}
return base.OnConnected();
}
Now in the client JS, if I have this code:
modelHub.client.checkForOtherUsers = function () {
// I can see logging here if I add console.log("checking for other users...")
modelHub.server.send();
}
...I'm expecting my C# Send method to receive back the context (if the user is actually present at the client for the JS to execute) and update my UserQueue:
public void Send()
{
var curmodelId = GetModelId(Context);
var curModelPool = _ledger.ConnectionPools.First(x => x.Id == curmodelId);
var curUserConnection = GetCurrentUser(Context);
curModelPool.UserQueue.Enqueue(curUserConnection);
}
... but my Send method never gets fired, even when I fire it from the JS Console directly $.connection.modelingHub.server.send().
Why doesn't the Send method fire? Is there something about the order of events / async nature of the SignalR library that causes this?
Comments on the overall design are welcome too (since I suspect it could be stupid), but note that I cannot access Context.User or Clients.User because of my customized Authentication config.
Seems to be two causes below.
Server-side
Not good to call Clients.All.checkForOtherUsers(); within OnConnected().
I would recommend calling it after connected.
Please refer to SignalR - Send message OnConnected
Client-side
Might need to regist modelHub.client.checkForOtherUsers = function () { before calling start method.
Normally you register event handlers before calling the start method
to establish the connection. If you want to register some event
handlers after establishing the connection, you can do that, but you
must register at least one of your event handler(s) before calling the
start method. - How to establish a connection
In my application, i am storing an object into session which is passed to a web service to return data to display in a table. If the session exists, then it will not ask the user to input fresh data. However, if a user selects a link called "New List", then the session data will be cleared and the user prompted to enter new data.
In my code, i have an anchor defined like so:
New List
Which will trigger this Controller Action:
public ActionResult NewList()
{
Session["new_list"] = "y";
return RedirectToAction("List");
}
And then continue to execute this action:
public ActionResult List()
{
if ((string)Session["new_list"] == "y")
{
//clear session variables, load fresh data from API
}else{
//display blank table. Ask user to input data to retrieve a list
}
....
}
Now, the issue i have is when a user navigates away from the list page, and then navigates back with the browser's back button, it is still calling newlist. In the history of the browser, instead of storing List it is storing newlist which is causing the session variable to clear. What can i do to stop this from happening or is there a different mechanism to use in c# mvc that can help me achieve the desired effect.
Your main problem here is that the NewList action uses GET when it should really be a POST.
A GET request is never supposed to alter the state of a resource, but simply return the current state of the resource; while a POST request allows for the altering of a resource.
Because you allow the NewList action to be called with a GET request, the user's browser assumes (quite rightly on its part) that nothing bad/undesired will happen if it simply repeats the request in the future, e.g. when a user uses the back button.
If instead a POST request is issued, a user browser will never re-issue the request without the user confirming they actually intended to re-issue it.
The solution to your problem then is modify this to the standard PRG pattern: POST/Redirect/GET; that is, send a POST request to perform the state change, redirect the user browser to another page, and GET the result page. In this scheme, pressing the back-button would effectively "skip" over the state change action and go the previous page the user was on.
To accomplish this in MVC:
[HttpPost]
public ActionResult NewList()
{
//clear session variables, load fresh data from API
return RedirectToAction("List");
}
public ActionResult List()
{
// whatever needs to happen to display the state
}
This does mean that you can't provide the "New List" action directly as a hyperlink in the page, as these will always issue GET requests. You will need to use a minimal form like so: <form method="post" action="#Url.Action("NewList", "Alert")"><button type="submit">New List</button></form>. You can style the button to look like a normal hyperlink as desired.
The reason it storing NewList is because you are redirecting to "Alert/NewList", and its the string in your URL for making hit to "NewList" Action, So whenever you are try back button the browser gets this "Alert/NewList" URL, hence its making hit to action "NewList".
But now, I am not getting why the session gets clear. because you are initializing the session in "NewList" itself. Still i suggest you to use local-storage to assign values with session.
I recently learned about SignalR and implemented a Persistent Online Connection check where the repository of the states is a SQL database.
It correctly detects when a user is Online/Offline. The problem becomes when the user is Online browsing the Home/Index.cshtml and then goes to Home/Edit.cshtml, OnDisconnected gets fired thus it creates a new connection. If I will like to keep the connection open how to? I had placed the hub connection in the _Layout.cshtml but obviously this gets reloaded as a new page is browsed.
I attempted to increase:
GlobalHost.Configuration.ConnectionTimeout = TimeSpan.FromSeconds(110);
GlobalHost.Configuration.DisconnectTimeout = TimeSpan.FromSeconds(45);
GlobalHost.Configuration.KeepAlive = TimeSpan.FromSeconds(15);
In the Startup.cs right before app.MapSignalR();. Did not work, not sure why.
Thus if I will like to keep a chat api the user will appear offline/online momentarily. I will like to prevent this.
Just implement OnReconnected() method, and keep your user online.
public override Task OnReconnected()
{
string name = Context.User.Identity.Name;
if (!_connections.GetConnections(name).Contains(Context.ConnectionId))
{
_connections.Add(name, Context.ConnectionId);
}
return base.OnReconnected();
}
Complete example here.
In that case - create a master page (layout page in case of mvc) and in the master page, connect the user to the signalr server. So if the user will redirect to some other page, it will stll be connected with the signalr server.
When the user makes selection and clicks a button, I call to:
public ActionResult Storage(String data)
{
Session["Stuff"] = data;
return null;
}
Then, I redirect them to another page where the data is accessed by
#Session["Stuff"]
This far, I'm happy. What I do next is that upon a click on a button on the new page, I perform a call to:
public ActionResult Pdfy()
{
Client client = new Client();
byte[] pdf = client.GetPdf("http://localhost:1234/Controller/SecondPage");
client.Close();
return File(pdf, "application/pdf", "File.pdf");
}
Please note that the PDFization itself works perfectly well. The problem is that when I access the second page a second time (it's beeing seen by the user and looks great both in original and on reload), it turns out that Session["Stuff"] suddenly is null!
Have I started a new session by the recall?
How do I persistently retain data stored in Session["Stuff"] before?
If you're simply storing string data (as would be indicated by your method signature) in an MVC application, don't.
It's far easier to pass the data as a query parameter to each method that needs it. It's far easier to manage and doesn't rely on Session sticky-ness.
To generate the appropriate links, you can pass data to your views and use Html.ActionLink to generate your links with the appropriate parameter data.
Here's several reasons why the session variable could return null:
null is passed into Storage
Some other code sets Session["Stuff"] to null
The session times out
Something calls Session.Clear() (or Session.Abandon())
The underlying AppPool is restarted on the server
Your web server is farmed and session state is not distributed properly
The first two can be discovered by debugging.
In my asp.net application, i want to clear the session whenever my browser closed or my tab (if my browser containing multiple tabs)closed.
Please guide me to get out of this issue...
Short version, No.
There's no solid way of a server detecting if the client has closed their browser. It's just the nature of web development's asynchronous pattern.
Long version, if it's really, really important to you;
Put a bit of javascript in the page that sends a regular post to your website in the background and set up a serverside agent or service that disposes of the sessions if it doesnt receive these regular "heartbeat" signals.
You can put a javascript postback onto the page's unload() event but dont rely on it, it doesnt always fire.
This happens by default whenever you close your browser, and that's not just for ASP.NET. It's for most server-side programming languages that have a session state. Basically, any cookie that is added that doesn't specify an expiration date, will be deleted when the browser is closed.
Where this doesn't apply, is when you close a tab, which is something you will not have any control over because the tab close event will not get sent back to the Web server.
You can try to do that with javascript. Check it at:
http://www.codeproject.com/Tips/154801/How-to-end-user-session-when-browser-closed
Alternatively you can check you previous session state on every new browser opening and can Session.clear() or Session.abandon() the previous session.
this will make sure that every time you start application you will get new session.
use BasePage in your .net application.
Check the session.sessionid on basepage load.
More Inforamtion how to detect new session in basepage. BasePage.Session.Link
Hope this helps
regards
Shaz
public class BasePage : Page
{
protected string mySessionId;
private CurrentUser _currentUser;
public CurrentUser _CurrentUser
{
get { return ((CurrentUser)HttpContext.Current.Session["myCurrentUser"]); }
set { _currentUser = value; }
}
protected override void OnLoad(EventArgs e)
{
if (Session["myCurrentUser"] != null)
{
if (_CurrentUser.ProUser)
{
mySessionId = Session.SessionID; // it means New Session
}
if (!mySessionId.IsNullOrDefault() && mySessionId != Session.SessionID)
{
Session.Abandon(); //Abandon current session and start new one
}
}
}
}
I think cookies can better meet your requirement here for session management.
it means that session data should not be stored on the server and
should be with your call, so that you don't have to worry about
clearing the data on server.
Yes.First of all Browser automatically clear session when browser is closed. you can try to capture browser close or tab close event in browser using javascript function like on before unload and on unload. Mostly onbefore unload event captures browser close event in chrome, Firefox, IE 11.
You can use Session_End event of Global.aspx
//For Specific Session
Session.Remove("SessionName");
//All the Session
Session.Abandon();