Session state unexpected behavior with async - c#

I am investigating async and await in the context of ASP.NET MVC controller methods, and am getting some unexpected behavior.
I have the following controller class:
[SessionState(System.Web.SessionState.SessionStateBehavior.Required)]
public class HomeController : Controller
{
// GET: Home
public ActionResult Index()
{
return View();
}
public async Task<ActionResult> Check1()
{
Session["Test"] = "Check";
System.Diagnostics.Debug.WriteLine("Session: " + Session["Test"]);
await Task.Delay(20000);
return View();
}
public ActionResult Check2()
{
var test = Session["Test"];
ViewBag.Test = test;
return View();
}
}
And simple views for each method Check1 and Check2:
Check1
#{
ViewBag.Title = "Check1";
}
<h2>View with Delay</h2>
Check2
#{
ViewBag.Title = "Check2";
var check = ViewBag.Test;
}
<h2>Immediate View #check</h2>
Now immediately after starting the application, when I open http://localhost:23131/Home/Check1 in one tab and http://localhost:23131/Home/Check2 in 2nd tab, the call to Check1 returns after 20 seconds as expected, and call to Check2 returns immediately, but it doesn't have the value set in the session state, which I fail to understand why. It should be set immediately since it is before the delay. However, correct value is printed on output window.
Now after Check1 returns, hitting refresh on Check2 tab brings value from session in viewbag and displays it.
After this, if I again refresh both tabs, Check2 doesn't return after Check1 is completed (20 seconds delay) despite Check2 being async.
My questions are:
First time when both tabs are opened, Check2 returns immediately due to Check1 being async, and it is not blocked despite SessionStateBehavior.Required since await statement returns control until awaited task completes. Why doesn't Check2 gets the value before Check1 returns?
After 1st time, Check2 gets stuck until Check1 returns despite Check1 being async, why is this? Upon re-running the application, Check1 returns immediately as stated in the previous question but only once.
Is there any concept I have missed or stated wrong in my explanation.
Using ASP.NET with .NET Framework 4.5, Visual Studio 2013 running on Windows 7 Ultimate.

For your question 2, if I understand correctly what you are doing is you refresh both tabs (presumably the one with check1 just before check2), and observe that check2 is not finishing loading until after check1 is finished.
The reason for this is that asp.net processes requests that are (potentially) writing to the same session in serial, not in parallel. That means, as long as any one request is pending, a second request will not even start processing until the first request is finished. Using tasks, async or manual thread handling will not work around this behaviour. The reason is that accessing the session is not a thread-safe operation. Potential workarounds are to not use session state in pages that do not require them, or use ReadOnly session state in pages that do not need to write to the session.
Both are accomplished by using the EnableSessionState property on the page, either setting to False to disable session state, or to ReadOnly to indicate that you do not write to the session in any requests to this page.
Also see for example answers here.
For question 1, it may be the reason is that session state is persisted only after the ReleaseRequestState life-cycle event (which is executed at the very end of the request), even though I would have expected this to to only matter when using a non-inproc session store. The more likely reason is however, that the request to check2 executes first (and blocks even the start of processing for check1). Perhaps you can elaborate how exactly you test this and how you make sure which request is executed first.
Edit:
what may be happening is this: in your first try, you do not actually use the same session (the session is not yet created when you start and a new one is created for each request. That also explains why the second request is not blocked until the first is finished). Then, on the second try, both will use the same session (because the last written session-cookie is used by both tabs). You can confirm that theory by printing the asp session (Session.SessionID) id and/or using cookieless session state

Related

ASP.NET Webservice- SessionState not timingout

I have a web application that utilizes JQuery as my front end code and ASP.NET as my backend web service.
I set the web.config setting <sessionState timeout="1">. When the user logs in, the web service creates a session variable with the user name.
System.Web.HttpContext.Current.Session["UserID"] = user_id;
In my web service, I have a function that checks if the variable still exists.
[WebMethod(EnableSession = true)]
[ScriptMethod(UseHttpGet = true)]
public string GetSessionUserID()
{
string user_id = "";
if (System.Web.HttpContext.Current.Session["UserID"] != null)
{
user_id = System.Web.HttpContext.Current.Session["UserID"].ToString();
}
return user_id;
}
I have a JS function that calls the web service that calls GetSessionUserID().
$(document).ready(function () {
getSessionID();
setInterval(getSessionID, 3000);
function getSessionID() {
console.log("getSessionID");
$.ajax({
url: "photoapp.asmx/GetSessionUserID",
//data: ,
success: OnGetSessionIDSuccess,
error: OnGetSessionIDError
});
}
function OnGetSessionIDSuccess(data, status) {
console.log("Success OnGetSessionIDSuccess");
console.log(data);
var strUser = $(data).find("string").text();
console.log(strUser);
if (strUser == "") {
window.location = "login.html";
}
}
}
In the document ready function, I also call setInterval() which will check the session every 3 seconds.
When testing, getSessionID gets called every 3 seconds, but after a minute, the getSessionID user variable can be found. I want the redirect the user back to login.html after the minute is done. Why is the session variable still alive after a minute? What am I not understanding about ASP.Net session state? How can this be fixed?
Be aware that if you adopt SQL server to save the session state, then the session timeout events never get called, and you thus can't know or tell if the user actually has logged out.
The only possible solution then is to ensure that all web pages have some kind of heartbeat or routine that calls the server every minute or so, and when that stops, then you know the user is gone or closed the web page.
In your case? If you touch the server every 3 seconds, then the session timeout will be re-set and start over with 1 minute. You also don't mention if you using in-memory, or using sql server for session state.
If you want to jump back to the logon page? Then your 3 second js code has to get/grab the time of the last heartbeat you call every 3 seconds. So, that routine has to set a start time, and then every 3 seconds check the elapsed time. Keep in mind that if you use sql sessions, then not even the logon event will fire, nor will even the authenticated user event fire.
So, the first time you start running that routine, you need to set a session value with the start time.
However, to my knowledge, every web service call will re-set the session time out to start over to 0. session timeout gets re-set when no activity occurs. So, if the user is doing something (or your ajax calls are), then session timeout will never occur.
You have to set a start time. And then get the elapsed time from that. You session will never timeout as long as you have the web page hitting and talking to the server.

c# mvc: RedirectToAction() and browser navigation buttons

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.

How to make controller action truly async

I have the following controller:
public class PingController : ApiController
{
[Route("api/ping")]
[HttpGet]
public IHttpActionResult Ping()
{
var log = HostLogger.Get(typeof(PingController));
log.Info("Ping called.");
return Ok("Ping succeeded # " + DateTime.UtcNow);
}
[Route("api/long-ping")]
[HttpGet]
public async Task<IHttpActionResult> LongPing(CancellationToken cancelToken)
{
await Task.Delay(30 * 1000);
return Ok("Ping succeeded # " + DateTime.UtcNow);
}
}
If I execute LongPing, followed by Ping in different browser tabs, the Ping will execute and return before LongPing does -- which is exactly what I'm looking for. The problem is when I execute two LongPing calls the second one takes around 60s to complete (not 30 seconds). Chrome reports the second call has a latency of 58s (60s minus the time it took me to start the second request). It seems to me that both LongPing calls should execute in around 30s if I had this working correctly.
I also should mention that I'm hosting this in an OWIN hosting environment, not IIS. But I didn't think that made any difference but maybe someone will prove me wrong.
How do I make LongPing behave truly like an async request?
It's quite likely that your session state is causing your problems. There's a long-winded explanation for this behaviour, but the short version is that a particular user session can only do one request at a time because the session state locks to ensure consistent state. If you want to speed this up, disable your cookies to test the session state hypothesis (you'll get 1 session state per request that way), or disable session state in the application. Your code is otherwise a-ok async wise.
It turns out this is Chrome's behavior when calling the same URL. I always forget this when testing with Chrome. Normally I test with Fiddler, but this VM doesn't have Fiddler.
See this SO Q&A:
Chrome treating smart url and causing concurrent requests pend for each other

Want to prevent default signalR OnDisconnect only from a certain view

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.

How to persist keeping the info in the Session["Stuff"]?

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.

Categories