MVC noob here.
I currently have this code that fires off my HomeController when a page loads via AJAX:
namespace ETTData.Controllers
{
public class HomeController : Controller
{
[HttpPost]
public ContentResult clearSessions()
{
var currentSession = System.Web.HttpContext.Current.Session;
System.Diagnostics.Debug.WriteLine("BEFORE: " + currentSession.Timeout);
currentSession.Abandon();
//currentSession.RemoveAll();
//currentSession.Clear();
System.Diagnostics.Debug.WriteLine("AFTER : " + currentSession.Timeout);
return new ContentResult { Content = "OK", ContentType = "text/plain" };
}
}
}
The output of the debug.WriteLine is:
BEFORE: 35
AFTER : 35
So as you can see it has 35 on the BEFORE but also has 35 for the AFTER when it shouldnt equal anything since I used currentSession.Abandon(); prior to calling that output.
I am setting the session timeout via the Global.asax.cs file:
namespace ETTData
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Session_Start(Object sender, EventArgs e)
{
HttpContext.Current.Session.Timeout = 35;
}
}
}
So saying all that - I'm at a loss as to why its not clearing the session...
Yea that's a good one that got me too in WebForms a long time ago.
The issue is, that your session is bound to a session cookie.
The cookie is transmitted in the request and response headers, which means via the HTTP protocol. The HTTP protocol is stateless, and therefore, it can't remove a cookie until after a response has been sent.
When you call session.Abandon, the session data will be abandoned at the same time as the cookie is abandoned at the client. Which means the frameworks marks the session data as "to be cleared after the response has been sent", which is after response.end. At response.end (which will be called after ContentResult.ExecuteResult), the framework will then clear the session. Subsequently it will call the Session_End event.
Session.Clear removes items immediately, but it will not remove the session cookie - therefore it also doesn't end the session, and it will not call the Session_End event - which is because it doesn't expire the session cookie.
Think of it as async-function.
You called abandon, but it's not yet executed.
As the others told you, if you need the session cleared immediately, call session.clear after session.abandon.
But if you start another session after you called session.abandon, you'll run into a very sharp knife.
Basically, you should never use sessions.
If you want to access "session" data without a detour into the database, you should store the information in an encrypted and asymmetrically-signed cookie, which you can bind to a session-lifetime, if you want to, but you don't have to. Google JWT for more information. I would bind such data into your auth-cookie. That way, there's no need for >1 cookies. The default timeout of 20 minutes in ASP.NET is a pretty bad thing. Your session data shouldn't expire until your authentication has.
Also, be careful what you write into your session.
If you just store user information there, that's fine.
But if you store state information there, you'll have a problem, because I can open multiple tabs of your site at once, and then the state from tab2 will overwrite the state of tab1. You have ONE session per domain, not one per tab.
Have a look at this question to find your answer.
In Short: Session.Abandon destroys the session but doesn't clear it's values. This happens when the request ends. Session.Clear clears everything from the session but doesn't destroy it.
Related
I am currently developing an application in ASP.NET CORE 2.0
The following is the action inside my controller that get's executed when the user clicks submit button.
The following is the function that get's called the action
As a measure to prevent duplicate inside a database I have the function
IsSignedInJob(). The function works
My Problem:
Sometimes when the internet connection is slow or the server is not responding right away it is possible to click submit button more than once. When the connection is reestablished the browser (in my case Chrome) sends multiple HttpPost request to the server. In that case the functions(same function from different instances) are executed so close in time that before the change in database is made, other instances are making the same change without being aware of each other.
Is there a way to solve this problem on a server side without being to "hacky"?
Thank you
As suggested on the comments - and this is my preferred approach-, you can simply disable the button once is clicked the first time.
Another solution would be to add something to a dictionary indicating that the job has already been registered but this will probably have to use a lock as you need to make sure that only one thread can read-write at a time. A Concurrent collection won't do the trick as the problem is not whether this operation is thread-safe or not. The IsSignedInJob method you have can do this behind the scenes but I wouldn't check the database for this as the latency could be too high. Adding/removing a Key from a dictionary should be a lot faster.
Icarus's answer is great for the user experience and should be implemented. If you also need to make sure the request is only handled once on the server side you have a few options. Here is one using the ReaderWRiterLockSlim class.
private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
[HttpPost]
public async SomeMethod()
{
if (cacheLock.TryEnterWriteLock(timeout));
{
try
{
// DoWork that should be very fast
}
finally
{
cacheLock.ExitWriteLock();
}
}
}
This will prevent overlapping DoWork code. It does not prevent DoWork from finishing completely, then another post executing that causes DoWork again.
If you want to prevent the post from happening twice, implement the AntiForgeryToken, then store the token in session. Something like this (haven't used session in forever) may not compile, but you should get the idea.
private const SomeMethodTokenName = "SomeMethodToken";
[HttpPost]
public async SomeMethod()
{
if (cacheLock.TryEnterWriteLock(timeout));
{
try
{
var token = Request.Form.Get["__RequestVerificationToken"].ToString();
var session = Session[SomeMethodTokenName ];
if (token == session) return;
session[SomeMethodTokenName] = token
// DoWork that should be very fast
}
finally
{
cacheLock.ExitWriteLock();
}
}
}
Not exactly perfect, two different requests could happen over and over, you could store in session the list of all used tokens for this session. There is no perfect way, because even then, someone could technically cause a OutOfMemoryException if they wanted to (to many tokens stored in session), but you get the idea.
Try not to use asynchronous processing. Remove task,await and async.
I've just discovered you can't access the current session within the SignalR Hub.
Simplified my scenario: I've tried to write a chat.
The name of the current user was kept within the Session.
I've used SignalR to update (a group of connections) about every new message.
Now I see that I can't access the name of the current user through the hub.
I guess there might be some workarounds, but is that implement my design was wrong?
Should I've not used SignalR for that purpose? Or should I not use Session in this way?
You shouldn't use Session with SignalR (see SignalR doesn't use Session on server). You identify logical connections by their connection id which you can map to user names.
The underlying problem is that access to SessionState is serialized in ASP.NET to ensure state consistency, so each request to the hub would block other requests. In the past, limited read-only access (I assume (but can't confirm since the gist is gone) by setting EnableSessionstate to read-only, which prevents the locking problem I described) was possible, but support for this was dropped. Also see various other places where the SignalR team made similar statements. Lastly: there's a statement in the official documentation about HTTPContext.Current.Session.
You could send values from client to server hub via Query String.
Before the $.connection.hub.start() method you could add something like this:
Client JS Code:
// Replace "some value" with the session value or anything you want
$.connection.hub.qs = { "Name": "some value" };
$.connection.hub.start()...bla bla bla
On the server side on the Hub you could use this in any method:
string ClientValue= Context.QueryString["Name"].ToString();
I did not test with sessions, but of course on client side you could be as awesome as you can.
I was in a situation where I couldn't use the User/Identity because the session hadn't been authenticated yet so I just used the actual session cookie value.
[HubName("headerHub")]
public class HeaderHub : Hub
{
static HeaderHub()
{
EventManager.CartUpdated += Update;
EventManager.LoggedOut += Update;
EventManager.LoggedIn += Update;
}
public override Task OnConnected()
{
var group = Context.Request.Cookies["ASP.NET_SessionId"].Value;
Groups.Add(Context.ConnectionId, group);
return base.OnConnected();
}
private static void Update(object sender, SessionEventArgs e)
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<HeaderHub>();
hubContext.Clients.Group(e.SessionId).onUpdateHeader();
}
}
I am sure someone will find something wrong with this solution and if so feel free to comment because I would like a better way to accomplish tying a session to a set of clients to notify.
One the best approach is to use Cookie instead of session. because as mentioned above , You can not use session . so When user login to your system, put its unique identifier(such as username) in cookie . then work with cookie where ever you want access session . such below...
public class CustomUserIdProvider : IUserIdProvider
{
public string GetUserId(IRequest request)
{
return request.Cookies["ODPUserID"].Value;
}
}
REWRITTEN QUESTION
I have an ASP.NET MVC 4 site that uses forms auth.
It also needs to retrieve custom user object from a service call and then set it to the HttpCurrent.User.Context.
this works fine but I realised that when it hits the post authenticate request that it will hit it several times per request - not good.
Global.asax.cs:
protected void Application_PostAuthenticateRequest(object sender, EventArgs e)
{
if (User.Identity.IsAuthenticated)
{
IIdentity ui = HttpContext.Current.User.Identity;
MyMembershipUser myUser = new MyMembershipUser (ui.Name);
MyCustomPrincipal myPrincipal = new MyCustomPrincipal (ui, myUser);
HttpContext.Current.User = myPrincipal;
}
}
I cant entirely cache the user for a number of reasons so lets not go there.
so this gets executed a few times per request. This means for every hit, it calls the DB.
Some views on the site use the custom principal to display some user specific details only if they are authenticated. if they aren't, then it wont display it. But if they are authenticated, it gets the principal and casts it to "MyCustomPrincipal" so I can grab the properties I need to display.
How can I prevent these multiple hits?
I tried creating a custom Authorize attribute and doing the above code in there, it works but fails when it renders the view which can see the user is authenticated but fails to do the cast because at that point, the User Identity/principal is still set to the Generic principal.
typical code in the view:
#if (Helpers.UserContext.IsAuthenticated)
{
#: tmpStatus = '#Helpers.UserContext.User.Status';
}
UserContext.IsAuthenticated just returns HttpContext.Current.User.Identity.IsAuthenticated
User in UserContext does the casting:
return HttpContext.Current.User as MyCustomPrincipal
I hope this clarifies the question more!
I want to avoid multiple hits happening on the PostAuthenticateRequest but not sure why those hits are happening. I am not even sure if it is the right place to place it. I want to make sure that the Context User is all setup for subsequent accesses/requests to it without having to call the service layer to get the details again.
thanks
you minimise some action by check if authenticated
//assuming something like....
public override void Init() {
base.Init();
// handlers managed by ASP.Net during Forms authentication
PostAuthorizeRequest += new EventHandler(PostAuthHandler);
}
// try screen out some calls that arent authenticated yet.
public void PostAuthHandler(object sender, EventArgs e) {
if (Request.IsAuthenticated) {
//.... try a break to see how often
}
}
EDIT: But careful of multiple hits due to script and content bundling / loading.
Check the Request.Url value. Is it changing.
Also Note the thread Id. See Debug.Windows.Threads
The thread may also be changing.
Consider thread safety before you attempt any caching / global singletons etc.
You may wish consider moving some code to a controller Or Base Controller
I'm currently using the "Session_Start" function within the Global.ascx file to save when an authenticated user visits my site.
This works OK if a users session expires, however as I'm using persistent cookies the user may return to the site within 28 days and this function will not be called and therefore will not be recording in the database that the user has visited.
I've taken a good look at all the functions available within Global.ascx, however I cannot find one that will perform what I need.
Application_Start - triggered only run within life cycle
Application_BeginRequest - each and every request made
Application_AuthenticateRequest - each and every request
Session_Start - when a new session is started
The two events that I believe could be used are Application_BeginRequest or Application_AuthenticateRequest.
Is there any way of limiting the above events to only run specific code on the first visit to a site and not on each request?
Alternatively is there any way of using my master file?
Any suggestions would be very useful.
Cheers
Why don't you implemented by your own? As you mentioned there is an event Application_BeginRequest. I think following might do the trick:
protected void Application_BeginRequest(object sender, EventArgs e)
{
string session_param_name = "SOME_SESSION_ID";
if (HttpContext.Current.Request.Form[session_param_name] == null)
{
//Count
}
else if (HttpContext.Current.Request.QueryString[session_param_name] == null)
{
//Also count
}
}
I have the following problem: republishing ASP.NET web app causes (as expected) session resetting where I keep additional user info (what on access attempt will cause NullReferenceException).
To avoid that, my page checks this info existence and in case of null redirects user to the login page (forms auth), so I'm calling:
void LogOut()
{
Session.Clear();
Session.Abandon();
User = null;
FormsAuthentication.SignOut();
FormsAuthentication.RedirectToLoginPage()
}
But sometimes it doesn't help, so I found a workaround:
Response.Redirect(FormsAuthentication.LoginUrl);
but it doesn't add returnUrl, what I wish it were (I don't want to emulate this behavior manually).
So want to figure out why does the first way doesn't work as expected.
Have you tried calling Response.End() after FormsAuthentication.RedirectToLoginPage() ?
I have the following problem: republishing ASP.NET web app causes (as expected) session resetting where I keep additional user info (what on access attempt will cause NullReferenceException).
But sometimes it doesn't help
I'm not sure what you mean by "sometimes it doesn't help" - you don't say what exactly happens.
But you should remember that expiration of a Forms Authentication ticket and expiration of a Session timeout are completely independent. A user's session can timeout while his Forms Authentication ticket is still valid and vice versa.
In general, when accessing data from Session, you should always test for existence first, and refresh it if necessary:
object o = Session["Whatever"];
if (o == null)
{
o = ... refresh it e.g. from the database
Session["Whatever"] = o;
}
...
Often it's useful to use a helper class to encapsulate this.
In your case you refer to "additional user info" - so you'll probably be able to retrieve this using HttpContext.Current.User.Identity.Name as a key.
Forcing a user to log in again because a Session has expired, e.g. because of an Application Pool recycle on the server, is very unfriendly.
UPDATE
The MSDN documentation for RedirectToLoginPage states that:
Unlike the HttpResponse.Redirect method, this method does not end the request by calling HttpResponse.End. This means that code that follows the RedirectToLoginPage method call will run.
This probably explains what you're seeing: code in the Page life cycle after your call to RedirectToLoginPage is running, and throwing a NullReferenceException.
You could call Response.End after RedirectToLoginPage to avoid this.
My Session timeout set = Forms auth timeout (via web.config).
I would re-iterate that Session expiry and FormsAuthentication expiry are unrelated, even if the timeouts happen to be the same. A FormsAuthentication cookie will survive an application pool recycle on the server; a Session will not.