ASP.NET MVC handling of users - c#

Hi,
The login method in my ASP.NET MVC page looks something like this :
Check ModelState
Check Username and password
user = accountModel.GetUser(model.UserName);
this.HttpContext.Session[Biss.Extensions.SessionKey.userContext.ToString()] = new UserContext() { SiteRole = (SiteRoles)user.RoleId, Id = user.Id };
FormsAuthentication.SetAuthCookie(model.UserName, createPersistentCookie);
During development Im rebuilding, restarting the solution alot of times and I have notice the following :
Start website
Login(with method above)
Rebuild soultion
Restart website
Now the User.Identity.Name will still be set but the
HttpContext.Session[Biss.Extensions.SessionKey.userContext.ToString()]
is null? I supose that the website is restarting when doing a rebuild/restart but how can the User.Identity.Name still be set? How could I handle this?
BestRegards

Since you are restarting the AppDomain the session is deleted as it is stored in memory. Think of it that the exactly same thing might happen in your production server. Under certain circumstances IIS could simply restart the application pool: for example after some inactivity or memory/CPU threshold is reached. To avoid loosing your session data you could use an out-of-process session storage so that it doesn't stay in memory. Look at the following article for the different possibilities: http://msdn.microsoft.com/en-us/library/ms972429.aspx

As you restarting the web site, you flush out you Session. The state of session is simply gone.
I would recommend to get rid of using Session for such simple scenario as user login. My general answer almost everywhere - do use session if you have very serious reason for that.
You can keep the information about user in database and read it than you actually need.

Related

ASP.NET Core HttpContext.Session.Clear() not clearing Session properly when deployed to IIS

Languages: C# and HTML Razor inside ASP.NET Core MVC
Context/Issue; I am storing user-specific information using HttpContext.Session, when debugging in Visual Studio calling HttpContext.Session.Clear() in my logout action functions as expected and clearing all Session variables. But when publishing and deploying the same app to IIS, clicking logout, the Session variables are cleared, but after approximately 10 - 12 seconds the Session variables re-appear like they never were cleared... There are also sometimes instances though where the Session does stay cleared, but a majority of the time it has this issue.
This results in the logout button logging the user out for about 10 seconds, then the user information re-appears thus making the website see the user as logged in again.
Snips from my code:
Home Controller:
[Route("logout")]
[HttpGet]
public IActionResult Logout()
{
HttpContext.Session.Clear();
return RedirectToAction("Index");
}
cshtml/Razor view/page:
<a class="nav-link text-dark" asp-controller="Home" asp-action="Logout"><b>Logout</b></a>
FYI: I am using a this SessionExtension I found online that allows me to store objects directly in Session without me having to encode/decode them manually. I store the user info as a Dictionary so I can easily access it via HttpContextAccessor in pages to pull permissons when needed. I chose this rather then accessing the database everytime a permission is needed for a page. Instead accessing the database once when logging in and storing the gathered info in Session. I'm not sure if this is somehow causing it but I figured I'd include this bit for more context.
SessionExtensions.cs :
using Newtonsoft.Json;
#nullable enable
namespace Microsoft.AspNetCore.Http
{
public static class SessionExtensions
{
public static void SetObject(this ISession session, string key, object value)
{
session.SetString(key, JsonConvert.SerializeObject(value));
}
public static T GetObject<T>(this ISession session, string key)
{
var value = session.GetString(key);
return value == null ? default(T) : JsonConvert.DeserializeObject<T>(value);
}
}
}
This is the only issue I'm getting with Session, everything else works fine between users at the same time, just logging out is the issue. When the logout button does work everything else works fine too.
Things I've Tried:
Debugging the deployed instance on IIS with VS, the Session stays cleared (returns Null) for about 10 - 12 seconds after clicking the logout button, then everything re-appears like it was never cleared.
Debugging the project on my dev. computer inside VS and not seeing the same behavior, after clicking logout Session stays cleared indefinitely. (Returns Null)
Clearing cookies of client browser.
Restarting Server running IIS and site.
Deleting site from IIS and retransferring published files, then adding it again. Did the same with configuration for the publish set to Debug and Release. No change in functionality.
Updating all NuGet packages and also recently updating to .NET 7.0 framework.
I might be missing something as I'm pretty new to C#, ASP.NET Core MVC, and Razor pages, and even Stack Overflow. Comment if more information needs added to help answer the question.
EDIT:
The session timeout (Idle Timeout) set in Program.cs always correctly clears the session.
EDIT:
I could probably fix this by setting every session variable to null when clicking logout. But this seems like a run-around way of doing it and could be prone to errors if I add another variable in the future. Should this be the solution?
EDIT:
My previously mentioned fix of setting each variable to null doesn't solve every issue. I'm actually noticing other issues arising with session. I can't pinpoint because its random, sometimes setting a session variable doesn't take, or sometimes it does, or sometimes it does temporarily...
It's seeming almost like session is just ignoring some calls to it's methods, or when it follows them it only takes effect for 10 seconds...
EDIT:
I've tried everything I could try to fix this issue, until I stumbled across this.
#{var IPAddress = HttpContextAccessor.HttpContext.Request.HttpContext.Connection.RemoteIpAddress.ToString();
Ping pingSender = new Ping();
PingReply reply1 = pingSender.Send(IPAddress);
PingReply reply2 = pingSender.Send(IPAddress);
PingReply reply3 = pingSender.Send(IPAddress);
long[] ReplyTimes = { reply1.RoundtripTime, reply2.RoundtripTime, reply3.RoundtripTime};}
#ReplyTimes
This is a small piece of code I setup in a debug page that would give me my current ping over an average of 3 samples. For some reason removing this code solved all my issues that I can tell so far. After further research it seemed like every time a session update took place (changing a variable) it would save the change then revert back after 10 seconds.
The weird part comes in where after another 10 seconds it would sometimes revert back again, to the latest variable, then repeat... Basically causing the variable to change by itself. This debug page only shows when logged in so I'm still a bit confused as to how this code on this page effected the ability to logout (The session clearing then re-appearing after 10 seconds even with the debug page not loaded)
I double checked and there was still only 1 session cookie the entire time.
I have no idea why removing this code fixed the issue. Maybe somehow accessing the request remote ip address causes unforeseen issues?
I'm adding this as an edit rather then a answer since this doesn't really answer why this is causing session to act the way it is.

Session variable gets cleared without any apparent reason

I have this piece of code:
var thisUser = Session["user"];
if (thisUser == null)
{
LogFile.Log("Logging out");
Response.Write("xp");
}
I am trying to track down why sometimes when I play with the system for a few minutes and suddenly the user session variable gets null.
It happens randomly in different scenarios.
I do not set the Session["user"] to null at any point.
Session timeout is set to 20 minutes.
I do not call Session.Clear() at any point.
Any ideas\thoughts\things I should look at as to why is it may happening?
I am using Firefox if that to any help.
The system is built with asp.net.
For more info please ask.
are you calling the same host? if the base URL is different the server will treat this as different users. for example:
http://localhost/path/to/resource and http://localhost:80/path/to/resource
both point to the same resource, but the requests are different and the session cookie will be different, or not present.
An easy way to test this is to launch your browser's developer toolbar and monitor the network traffic. compare the URLs to make sure they are the same base path and the same session cookie is passed in the request.
First of all this looks like C# and ASP.NET, not classic ASP. Now if you never clear the session yourself and the server (or the app pool) is never restarted, then the only way to lose the session is to clear the browser's cookies.
Editing the web.config will recycle the app pool, which clears the session info.

Prevent expiration of individual sessions based on custom conditions?

A website I am working on is very data centric. Some reports take more than an hour to complete. Whenever a user submits a request for a report, a new thread is created which generates the report. The user is then redirected to a page which says that the report in progress, and to please refresh to download the report. If the user again refreshes the page and the report is still in progress, the same message is shown; otherwise a download link is provided.
All report/user relations are saved in the application variable. That works fine, except when the user is inactive for more than 20 min (while the report is being processed), and then the user is logged out; if the user logs in again, the report can still be downloaded.
I do not want to increase the session expiration time, but I need to stop the expiration if the user has something going in background, like a report being processed.
In Session_End I am able to retrieve the the userid and match it in Application["work"] to see the user has pending work or not.
However, I am clueless as to how I can defer the session end in the above case?
Edit: Every one has suggested as a workaround from 'maintaining a contact' to 'using query string'. 'Maintaining the contact' looked the most promising to me but it fails in the following scenarios: a. When browser is closed/computed goes in standby mode during lunch, etc. b. When user goes to another non-asp.net section (it's a legacy site).
Isn't It possible to cancel the Session_End event itself?
The short answer
There is currently (that I know of) no simple way to extend the life of a single ASP.NET session. There is one possible solution: use a custom Session-State Store Provider!
The long answer
First things first: Start with something that is already built! Use the sample Session-State Store Provider (and its tutorial) provided by Microsoft. This sample Session-State Store Provider uses Microsoft Access as its back end; although, because it uses ODBC connections, you can have virtually any database back end supported through your installed ODBC drivers.
This sample Session-State Store Provider is simply a custom version of what ASP.NET uses internally (with the exception that ASP.NET's runs in-memory).
Secondly: Let's prepare the Access Database requirements, and the configuration.
Create the table as specified in the tutorial and in the comments of the file:
CREATE TABLE Sessions
(
SessionId Text(80) NOT NULL,
ApplicationName Text(255) NOT NULL,
Created DateTime NOT NULL,
Expires DateTime NOT NULL,
LockDate DateTime NOT NULL,
LockId Integer NOT NULL,
Timeout Integer NOT NULL,
Locked YesNo NOT NULL,
SessionItems Memo,
Flags Integer NOT NULL,
CONSTRAINT PKSessions PRIMARY KEY (SessionId, ApplicationName)
)
NOTE: If you want to use SQL Server, simply replace Text(...) with varchar(...), YesNo with bit, and Memo with varchar(MAX).
Add/update your web.config with the following (you can use connectionstrings.com to help you generate a connection string):
<configuration>
<connectionStrings>
<add name="OdbcSessionServices" connectionString="DSN=SessionState;" />
</connectionStrings>
<system.web>
<sessionState
cookieless="true"
regenerateExpiredSessionId="true"
mode="Custom"
customProvider="OdbcSessionProvider">
<providers>
<add name="OdbcSessionProvider"
type="Samples.AspNet.Session.OdbcSessionStateStore"
connectionStringName="OdbcSessionServices"
writeExceptionsToEventLog="false" />
</providers>
</sessionState>
</system.web>
</configuration>
Third: Adding a function that will extend for more than the specified Timeout.
Make a copy of the ResetItemTimeout function, and name it ResetItemTimeout2:
var ExtendedTotalMinutes = 2 * 60; // hours * minutes
public override void ResetItemTimeout2(HttpContext context, string id)
{
OdbcConnection conn = new OdbcConnection(connectionString);
OdbcCommand cmd =
new OdbcCommand("UPDATE Sessions SET Expires = ? " +
"WHERE SessionId = ? AND ApplicationName = ?", conn);
cmd.Parameters.Add("#Expires", OdbcType.DateTime).Value
= DateTime.Now.AddMinutes(ExtendedTotalMinutes); // IMPORTANT!! Set your total expiration time.
cmd.Parameters.Add("#SessionId", OdbcType.VarChar, 80).Value = id;
cmd.Parameters.Add("#ApplicationName", OdbcType.VarChar, 255).Value = ApplicationName;
try
{
conn.Open();
cmd.ExecuteNonQuery();
}
catch (OdbcException e)
{
if (WriteExceptionsToEventLog)
{
WriteToEventLog(e, "ResetItemTimeout");
throw new ProviderException(exceptionMessage);
}
else
throw e;
}
finally
{
conn.Close();
}
}
Fourth: Supporting the extension of a single ASP.NET Session!
Whenever you need to extend a session, call the ResetItemTimeout function as follows:
using Samples.AspNet.Session;
// from inside a User Control or Page
OdbcSessionStateStore.ResetItemTimeout2(this.Context, this.Session.SessionID);
// --or--
// from anywhere else
OdbcSessionStateStore.ResetItemTimeout2(System.Web.HttpContext.Current, System.Web.HttpContext.Current.Session.SessionID);
Footnotes
Read the comments on the page with the sample Session-State Store Provider;
There is one potential good entry about a Mistake in GetSessionStoreItem when using GetItem.
Another good one is that Timestamps should be UTC.
There are obvious performance/maintainability improvements that could be done (especially with having duplicate code in ResetItemTimeout and ResetItemTimeout2).
I have not tested this code!
Edits
I realized I missed the part where you want to extend more than the Timeout - answer has been fully updated.
Added footnotes section.
Maintain a contact with the server will avoid Session Timeout.
Create a empty Web service and run that in your server then call the web service by your site by JQuery by the interval of some seconds is enough to keep the session alive
May be this solution will help you..
Try this link for full details : Prevent Session Timeout in ASP.NET
While desperately looking for defering the session_end event I think it seems impossible?
The easiest work around that I was able to come with was 'Using Cookies' and modifying my authentication logic.
I implemented a method for writing a guid key to the cookie named admin when ever user requested for the report with expiration of 9 hour(Max time office will be open for work).
I save this guid with user id in a seperate table.
In the master page where i was checking for session userid I implemented another method to check for any cookie named admin. If it is found i set session to the user id saved in table else i redirect them to login page as before it was happening.
It seems to work like magic. But I need to know is this a right thing?
What you can do is set the Session timeout to a higher value when you detect that a report has been requested that will take a long time. This of course supposes that you can calculate whether a report will take a long time to run. If so, you can do this before you kick off the thread:
Session.Timeout = 120 // set timeout to two hours for this session only
Apart from pinging a page or service through Ajax, there really is no other way. (unless not relying on sessions at all is an option).
This is because of the way sessions are maintained: the ASP.NET runtime detects a session when the request contains a cookie. This cookie is set at every request / response and will contain an expiration date.
If in your initial request you set an expiration of 20 minutes and the user closes the browser or is inactive for more than 20 minutes there is no way on the server side you can detect which session the request belongs to.
So, to answer your question whether you can cancel the session_end, no you cannot do that as that code runs server side and it cannot access the client cookie. It's a simple event that is fired twenty minutes after you last set the cookie. This is completely asynchronous from the client-side.
The solution I proposed is a workaround that could work if you know how to calculate the duration (at least approximately).
Another solution, but way more complicated, would be to save the reports and make a separate section for the user where he can see all his reports. That way, even if the session times out, he can log back in and go to his history and retrieve the report.
It's best not to rely on Session_end as it doesn't always fire, such as when the worker process recycles or an uncaught exception occurs, i.e. it is basically killed / bounced.
If you want to revive a session then it seems that the best way is to store the user data somehow and totally manage the cache yourself.
It seems from your reply to previous posts that the additional network activity and subsequent page time load increase when using sql state management are unacceptable, and the difference between using sql server state provider to using a session server such as Microsoft AppFabric would be negligible, however it seems a distinct possibility that if you were to use the session server of AppFabric coupled with it's caching, things could be sped up a lot.
P.S. In general doing away with sessions would seem like the most efficient solution, see John Han's answer in this post just about sums it up sessions are bad mmkay.
In order for a session to stay alive, something (not necessarily the user's browser) has to make a request to your app with that user's ASP.NET_SessionId cookie every so often.
What if you had some code that saves the ASP.NET_SessionIds of the users you are interested in, and then have a windows service that requests a page on your app with the required ASP.NET_SessionId(s) every 20 minutes or so.
see http://erlend.oftedal.no/blog/?blogid=41 for some info about this cookie
Are you using FormsAuthentication? If so, you could increase the timeout for the authentication ticket, which will prevent the login screen even after the session has expired.
At the beginning of the request you could check the user through the ticket
After get the user if the session is null it means the user has been offline for while, you the can check the work in progress for that user.
If the user has a work in progress, load session values that you might need it and redirect them to the work in progress or report to download.
If the user has nothing, expire the ticket and redirect them to login page or just keep them logged in and reload session values.
The timeout for the authentication ticket is pretty big
http://msdn.microsoft.com/en-us/library/system.web.configuration.formsauthenticationconfiguration.timeout.aspx
Cheers
I'd suggest increasing the session timeout instead of trying to find a way around it. There's an interesting thread about session and form timeouts here
I suggest that you don't depend on sessions at all .. you can depend on the query string by adding a new GUID variable and use that variable value with the application object to map the user requested file(s) with the GUID value .. this way .. the user will always be able to download the file since he have the link to the file that is mapped to the application object and no need to handle any session timeout.
Why do not you try to show the loading file progress bar and inside that one you can use the logic of checking status of file downloaded so far. It will have two advantage , as you are hitting your website , Session will not expire at the same time you are giving the useful information back to end user.

How reliant should I be on Session

I am using session to do two things:
Keep track of which css file to load for a particular company.
Keep track of the Company Id Guid that tracks what company we are in.
I am seeing that this is coming in as null sometimes but I'm not getting a consistent problem to track down. I have no doubt this is probably something in my code that I need to track down but this bring me up to my questions about session...
Am I relying on Session to much to pass information between screens?
Is it consistent?
What could be clearing it between page loads?
Is session easily cleared by the user?
Should I be using something else more reliable?
I am worried that I will move this to live usage and have all kinds of problems.
I'm simply doing this:
Session["Css"] = css;
and reading it the same way:
css = Session["Css"]
UPDATE
The session I am using:
HttpSessionStateBase Controller Session
There are a few types of Session State. InProc, StateServer, and SqlServer. I believe the default is InProc. You can read more about this on MSDN here and here.
Each of these will obey the timeout value for sessionState defined in your web.config file. For a single server setup (which is what I typically do) I usually have my sessionState setup as follows:
<sessionState
mode="StateServer"
stateConnectionString="tcpip=127.0.0.1:42424"
timeout="2880" />
This is the same default timeout as Forms Auth, so my sessions will stick around as long as my users auth cookie/session. This will require you to set the start up on the ASP.NET State Server to Automatic. Below is my high-level pass at explaining the types and their potential drawbacks.
InProc
This will be reset everytime the application pool recycles the worker process for the web app. I believe this is what is happening to you, and this is why your session variables are null at what appear to be random times.
StateServer
This will persist state across apppool recycles, but requires that you enable the ASP.NET State Server Service, and change it's start up type to Automatic.
You can run into issues if you have a web farm or multiple web servers handling the website, unless you run a dedicated state server.
This requires that variables stored in session variables are [Serializable].
SQLServer
This will persist session variables to a SQL database, and is is generally the best approach for a web farm situation.
This requires that variables stored in session variables are [Serializable].
Could you not be writing your method to GetCss() something along these lines?
GetCss() {
var css = (CastToYourTypeHere)Session["css"];
if(css == null) {
//do whatever you'd normally do here to set the css,
//e.g. get the users guid and find their company css, and set css equal to it
css = myHypotheticalGetCssBasedOnTheUserFunction();
}
return css;
}
That way you're kind of covering all bases in that, you can happily use Session, if it it's lost for whatever reason you're just re-running your initial 'getCssForThisUser' type code.
IMO, session isn't a bad way to hold this kind of data for each user between pages in the type of example you've described.

ASP.NET handle external server sessions - clean-up

Are we "doing it wrong"?
A colleague and I are messing around with an ASP.NET page to act as a "portal" to view the results from a diagnostic program on a UniData server. Although we do the odd-job of ASP/ASP.NET at work, it is not our primary language.
To access this server, we have to use UniObjects, which is an API for authenticating and using the UniData server.
We needed each user visiting the website to have to authenticate with UniData and get their own session via the UniObjects library, then be able to use it without signing in again (unless the session isn't used with in 'x' minutes).
The method we have come up with is as follows:
We have a singleton with a Hashtable. It maps Windows username with a session object.
If the user goes to our page and 'username' doesn't exist in the Hashtable, it redirects to a login page where the session object is created and added to the Hashtable if authentication succeeds. Otherwise, it grabs the users existing session object from the Hashtable and uses that for the request (unless it has expired, in which case we remove it and redirect to the login page).
Each session object (which is a wrapper object for stuff from UniObjects) has a "lastUsed" method. We need to clean-up user's sessions since we have license restrictions on users logged into the UniData server, so every time a user gets redirected to the sign-in page, it checks if any sessions have not been used in 'x' mins, in which case it closes that session and removes it from the Hashtable. It is done here so users won't experience any delay related to checking all sessions on every request, only at login.
Something is telling me that this solution smells, but I don't have enough ASP.NET experience to work out what we should be doing? Is there a better method for doing this or is it actually okay?
Since all of your users seem to be authenticated, I would suggest you think about using a different way of managing session state and timeout.
Part of the issue you have is that if a user just closes the browser without logging out, or stops using the application, you have to wait until the session times out to kill it off and free up UniObjects for your licensing issues.
My suggestion is as follows:
Add an invisible IFRAME to your
MasterPage template, or to each page
in the site if you aren't using
MasterPages.
That MasterPage will
load a KeepAlive.aspx page, that
contains a META Refresh, reloading
the page every 5 minutes.
You can
reduce the session timeout to 10
minutes (maybe even 6)
Now, if a user closes their browser windows, their session times out much quicker than usual, but if their browser window is left open, their session is persistent.
A code example and walkthrough can be seen here.
You now need to solution to prevent the user from leaving their browser window open all night and hogging your UniData licences. In this case I would implement a similar methodology, where a stagnant page (i.e. user has done nothing for 20 minutes) is refreshed to a logout ASPX page, clearing the session.
If you are using UniObjects COM, make sure you get your COM marshalling working correctly. Take a look at:
SafeCOMWrapper - Managed Disposable Strongly Typed safe wrapper to late bound COM
http://www.codeproject.com/KB/COM/safecomwrapper.aspx
Another thing to watch out for is that the dynamic array class in UniObjects COM has a threading issue that doesn't play nice with .NET. If you can, use your own dynamic array class or array splits in .NET instead of the Dynamic array class in UniObjects COM.
Sometimes when you try to access the data from the class, it shows an empty string, but when you debug it, the data is there. Don't know the root cause of this.
If you need a generic dynamic array class that works with .NET, I can supply you one.
UniObjects.NET does not have these problems to my knowledge.
Nathan Rector
When you say you are using UniObjects... are you using the COM or .NET object set? The easiest would be to use UniObject Conneciton pooling.
When you create your Singleton, are you storing it in the Application object, Session Object, or Cache Object?
I would suggest Application object, as the Session object is can do strange things. One way to handle and check timeouts would be to use the Cache Key with a CacheRemoveCallback. This way you can use a File/Path Monitor dependency to watch for a windows file change to cause a remove manually, or a timeout from the Cache dependency.
Draw back to this is that timeouts on Cache Dependencies are only driven by page activity, and if the asp.net session recycles, it may/will destory the cache dependencies.
Nathan Rector

Categories

Resources