Has anyone ever eperienced session's being shared application wide?
My MVC application has the vanilla setup but for some reason, my sessions are being shared.
I didn't really think about it when I could switch between FF and IE and maintain a logged in state but now, I've noticed that I can switch machines too.
My web.config doesn't have anything in to setup the session state, so I assumed it was cookie based but it seem it's not.
Has anyone ever experienced this before and if so, how did you resolve it?
FYI: I'm running it on Server 2003 IIS6.
Thanks all!!
Gav
Are you specifically using storing things in Session or are you seeing this in TempData calls (which temporarily uses session as well)?
Well would you believe it... Stupid static variables...
I thought by using a static private variable that it would help me by not doing as much work when getting the data, but as it seems, it was the root of evil. (doctor evil pinky)
Thanks everyone!
** NOTE THIS IS HOW NOT!! TO DO IT **
public class UserHelper
{
private static UserSession _session;
public static UserSession Session
{
get
{
// If we already have the session, don't get it
// from the session state
if (_session == null)
{
// Attempt to get the session from the
// session state
_session = GetUserSessionFromSession(HttpContext.Current.Session);
if (_session == null)
{
// Create a new session object
_session = new UserSession();
}
}
return _session;
}
set
{
// Set the local value
_session = value;
// Add the object to the session state
HttpContext.Current.Session["SMEUser"] = _session;
}
}
public static void Logout()
{
Logout(HttpContext.Current.Session);
}
public static void Logout(HttpSessionState session)
{
_session = null;
session.Clear();
}
public static UserSession GetUserSessionFromSession(HttpSessionState session)
{
// Get the session from the session state
UserSession us = session["SMEUser"] as UserSession;
return us;
}
}
Related
I inherited ASP.NET MVC 5 application which has its own Context object. The context object has few static properties that returns data from session or cache object.
For brevity purpose i have reduced the number properties to 2, one from Session and one from Cache.
public class MyContext
{
public static UserVM CurrentUser
{
get
{
return HttpContext.Current.Session.GetCurrentUserInfo();
}
}
public static IEnumerable<StateProvinceVM> StateProvincesLookup
{
get
{
return HttpContext.Current.Cache.GetStateProvinces();
}
}
}
Extension methods
/// Gets the current user information. MVC version
public static UserVM GetCurrentUserInfo(this HttpSessionStateBase session)
{
// get logged-in user from the database and put it in session ( user specific)
// and any further request for user info will be retuned from session
}
// Gets the current user information. Use when we are not in MVC context
public static CompanyUserDTO GetCurrentUserInfo(this HttpSessionState session)
{
return (new HttpSessionStateWrapper(session)).GetCurrentUserInfo();
}
// Gets the state provinces from cahe
public static IEnumerable<StateProvinceVM> GetStateProvinces(this Cache cache)
{
// load states from database and load in Cache. (application specific)
// any request from any user to States will be returned from Cache.
}
Well this implementation is working but makes unit testing hard and also i cannot inject Session and Cache. So i refactored it as below
public class MyContext
{
private static HttpSessionStateBase _session;
private static Cache _cache;
public TPTContext(HttpSessionStateBase session, Cache cache)
{
_session = session;
_cache = cache;
}
public static UserVM CurrentUser
{
get
{
return _session.GetCurrentUserInfo();
}
}
public static IEnumerable<StateProvinceVM> StateProvincesLookup
{
get
{
return _cache.LookupStateProvinces();
}
}
}
I am using Unity for DI. So im registering Session and Cache as
container.RegisterType<HttpSessionStateBase>(new InjectionFactory(x => new HttpSessionStateWrapper(System.Web.HttpContext.Current.Session)));
container.RegisterType<Cache>(new InjectionFactory(x => System.Web.HttpContext.Current.Cache));
I am not sure if my refactoring is the correct way to solve this issue. Because i am making _session as static variable. Will that cause any issue down the line since session is user specific? Will it cause Captive Dependency?
I think Cache would be okay.
or this is completely anti pattern?
I have the following global action filter:
public class AddWidgetsAttribute : ActionFilterAttribute {
private ISession _session;
public ISession Session {
get {
if (_session == null)
_session = DependencyResolver.Current.GetService<ISession>();
return _session;
}
}
public override void OnActionExecuting(ActionExecutingContext filterContext) {
filterContext.Controller.ViewBag.Widgets = Session.Query<Widget>().ToList();
}
}
Here's the code which adds the filter in the Application_Start event:
GlobalFilters.Filters.Add(new AddWidgetsAttribute());
This works fine on the first request but then it throws an error telling me the session is closed. If I change the Session property to the following:
public ISession Session {
get { return DependencyResolver.Current.GetService<ISession>(); }
}
It works fine but it seems like abit of a hack. Is there a nice way to inject the dependency within global action filters?
I'd appreciate the help. Thanks
There is no problem doing this, but, keep in your mind, if you instance a global filter, it will add a single instance into your application. So, your code check if the _session object is not null and instance it, but, when you close the session you still have the reference on the global filter, because it is a single instance, and worst, the session is closed. So, the next access will get a non null object and closed session.
I would keep the DependencyResolver to give me the instance of the session everytime, because you are controlling it in another level (I want to understand you are doing a session per request).
public ISession Session
{
get
{
return DependencyResolver.Current.GetService<ISession>();
}
}
And the asp.net mvc will get the right instance every request.
This is another strange problem I've encountered this days!!! I've created and MVC 4 app using nhibernate. and added a filter attribute named [LoggingNHibernateSessionAttribute] on my HomeController which manages session for each action. I've followed 'ASP.NET MVC4 and the Web API published by Apress'.
public class LoggingNHibernateSessionAttribute : ActionFilterAttribute
{
private readonly IActionLogHelper _actionLogHelper;
private readonly IActionExceptionHandler _actionExceptionHandler;
private readonly IActionTransactionHelper _actionTransactionHelper;
public LoggingNHibernateSessionAttribute()
: this(WebContainerManager.Get<IActionLogHelper>(),
WebContainerManager.Get<IActionExceptionHandler>(),
WebContainerManager.Get<IActionTransactionHelper>())
{
}
public LoggingNHibernateSessionAttribute(
IActionLogHelper actionLogHelper,
IActionExceptionHandler actionExceptionHandler,
IActionTransactionHelper actionTransactionHelper)
{
_actionLogHelper = actionLogHelper;
_actionExceptionHandler = actionExceptionHandler;
_actionTransactionHelper = actionTransactionHelper;
}
public override void OnActionExecuting(ActionExecutingContext actionExectingContext)
{
_actionLogHelper.LogEntry(actionExectingContext.ActionDescriptor);
_actionTransactionHelper.BeginTransaction();
}
public override void OnActionExecuted(ActionExecutedContext actionExecutedContext)
{
_actionTransactionHelper.EndTransaction(actionExecutedContext);
_actionTransactionHelper.CloseSession();
_actionExceptionHandler.HandleException(actionExecutedContext);
_actionLogHelper.LogExit(actionExecutedContext.ActionDescriptor);
}
}
ActionTransactionHelper
public class ActionTransactionHelper : IActionTransactionHelper
{
private readonly ISessionFactory _sessionFactory;
private readonly ICurrentSessionContextAdapter _currentSessionContextAdapter;
public ActionTransactionHelper(
ISessionFactory sessionFactory,
ICurrentSessionContextAdapter currentSessionContextAdapter)
{
_sessionFactory = sessionFactory;
_currentSessionContextAdapter = currentSessionContextAdapter;
}
public void BeginTransaction()
{
var session = _sessionFactory.GetCurrentSession();
if (session != null)
{
session.BeginTransaction();
}
}
public bool TransactionHandled { get; private set; }
public void EndTransaction(ActionExecutedContext filterContext)
{
var session = _sessionFactory.GetCurrentSession();
if (session == null) return;
if (!session.Transaction.IsActive) return;
if (filterContext.Exception == null)
{
session.Flush();
session.Transaction.Commit();
}
else
{
session.Transaction.Rollback();
}
TransactionHandled = true;
}
public bool SessionClosed { get; private set; }
public void CloseSession()
{
if (_currentSessionContextAdapter.HasBind(_sessionFactory))
{
var session = _sessionFactory.GetCurrentSession();
session.Close();
session.Dispose();
_currentSessionContextAdapter.Unbind(_sessionFactory);
SessionClosed = true;
}
}
}
when run the app, I can save an entity in the dataBase. but when I hit refresh button and exception thrown indication session is closed.
I don't know why this happens. (I searched and find this NHibernate throwing Session is closed but couldn't solve my problem).
in my NinjectConfigurator I added inRequestScope() to all of injections but no answer. I checked when I refresh the page session will be opened. but I donnow why it say session is closed?!
UPDATE:
when I first run the app. I can create a new member. but when I hit the refresh button, the session will be closed unexpectedly!!
first run:
everything works well
after hitting refresh button:
a new session bind to the current context.
the new session will be injected the repository (session is open)
the ActionTransactionHelper calls beginTransaction()
4- customMembership createUser (....) called
5- but when the _userRepositoy.save(user)called in the repository session is closed!!!!
note:but when still endTransaction and closeSession isn't called. but how session is closed?
if I comment closeSession() in onActionExecute(). session alway is open and everything woks well if refresh the page.
I checked a lot and tried different way I knew. it only happens when for the second time I want to do CRUD operations with my customMembership.
for other entities it works like a charm!
I have upoaded my sample code. for testing just create and empty database and change connection string. then go to localHost:*****/api/categories (user and pass doesn't required)
Download sample project:
Size: 47 MB
https://www.dropbox.com/s/o63wjng5f799fii/Hashem-MVC4ServicesBook.rar
size: 54 MB
Zip Format: https://www.dropbox.com/s/smrsbz4cbtznx1y/Hashem-MVC4ServicesBook2.zip
A very important thing here, could be the nature of the NHibernate. The NHibernate and its Session are in the ASP.NET MVC living longer, then could be expected. I mean not only inside of the
ActionExecuting (Controller Action starts)
ActionExecuted (the View or Redirect is called)
Session in fact must live also through the phase of rendering. Because, we could load some proxy in the "Action()" but its collection, could be lazily loaded only during the View rendering. So even in these phases Session must be opened (the same Session from the request begining)
ResultExecuting (the proxy could start to be loaded only here)
ResultExecuted (almost all is done, let's close the session)
Other words... keep the session opened throught the complete Request. From authorization untill the content is rendered.
NOTE: Anohter hint, just to be sure that all is ok, I am using this scenario (maybe you do as well):
Client FORM is about to send the data to server. The method is POST, the Action is Update()
Sent FORM is coming to server, Action Update() is triggerred - all the transactions stuff is in place (as described above)
Once NHibernate persists the data into DB, the Update() action ends, and is redirected to action
Detail() if all is ok or
Edit() if something goes wrong
The users Browser was redirected to action Detail or Edit. So if user does REFRESH, the Detail or Edit is refreshed. The Update() is not called at all (it is a POST method)
In fact, the step 1. was one of the Actions Detail or Edit. In this case, we would face this issue already...
You have this error since Asp.Net MVC does not create a new instance of LoggingNHibernateSessionAttribute every request. It creates a new instance when you request an action first time and then uses this instance in the future.
The behaviour is the following:
First invocation of Post -> new instance of 'LoggingNHibernateSession' is created
First invocation of Put -> another one instance of 'LoggingNHibernateSession' is created
Second invocation of Put -> instance of 'LoggingNHibernateSession' from previous step is used
First invocation of Delete -> another one instance of 'LoggingNHibernateSession' is created
[LoggingNHibernateSession]
public JsonResult Post(Dto data)
{
/* ... */
}
[LoggingNHibernateSession]
public JsonResult Put(int id, Dto data)
{
/* ... */
}
[LoggingNHibernateSession]
public JsonResult Delete(int id)
{
/* ... */
}
It can be solved using Func<IActionLogHelper> instead of IActionLogHelper in the constructor. An instance of IActionLogHelper can be initialised within OnActionExecuting method.
public class LoggingNHibernateSessionAttribute : ActionFilterAttribute
{
/* your code */
private readonly Func<IActionTransactionHelper> _getActionTransactionHelper;
private IActionTransactionHelper _actionTransactionHelper;
public LoggingNHibernateSessionAttribute()
: this(WebContainerManager.Get<IActionLogHelper>(),
WebContainerManager.Get<IActionExceptionHandler>(),
() => WebContainerManager.Get<IActionTransactionHelper>())
{
}
public LoggingNHibernateSessionAttribute(
IActionLogHelper actionLogHelper,
IActionExceptionHandler actionExceptionHandler,
Func<IActionTransactionHelper> getActionTransactionHelper)
{
_actionLogHelper = actionLogHelper;
_actionExceptionHandler = actionExceptionHandler;
_getActionTransactionHelper = getActionTransactionHelper;
_actionTransactionHelper = null;
}
public override void OnActionExecuting(ActionExecutingContext actionExectingContext)
{
_actionTransactionHelper = _getActionTransactionHelper();
_actionLogHelper.LogEntry(actionExectingContext.ActionDescriptor);
_actionTransactionHelper.BeginTransaction();
}
/* your code */
}
In my ASP .NET MVC 2 - application, there are several controllers, that need the session state. However, one of my controllers in some cases runs very long and the client should be able to stop it.
Here is the long running controller:
[SessionExpireFilter]
[NoAsyncTimeout]
public void ComputeAsync(...) //needs the session
{
}
public ActionResult ComputeCompleted(...)
{
}
This is the controller to stop the request:
public ActionResult Stop()
{
...
}
Unfortunately, in ASP .NET MVC 2 concurrent requests are not possible for one and the same user, so my Stop-Request has to wait until the long running operation has completed. Therefore I have tried the trick described in this article and added the following handler to Global.asax.cs:
protected void Application_BeginRequest()
{
if (Request.Url.AbsoluteUri.Contains("Stop") && Request.Cookies["ASP.NET_SessionId"] != null)
{
var session_id = Request.Cookies["ASP.NET_SessionId"].Value;
Request.Cookies.Remove("ASP.NET_SessionId");
...
}
}
This simply removes the session-id from the Stop-Request. At the first glance this works well - the Stop-Request comes through and the operation is stopped. However, after that, it seems that the session of the user with the long running request has been killed.
I use my own SessionExpireFilter in order to recognize session timeouts:
public class SessionExpireFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
// check if session is supported
if (ctx.Session != null)
{
// check if a new session id was generated
if (ctx.Session.IsNewSession)
{
// If it says it is a new session, but an existing cookie exists, then it must
// have timed out
string sessionCookie = ctx.Request.Headers["Cookie"];
if ((null != sessionCookie) && (sessionCookie.IndexOf("ASP.NET_SessionId") >= 0))
{
filterContext.Result = new JsonResult() { Data = new { success = false, timeout = true }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
}
}
base.OnActionExecuting(filterContext);
}
}
ctx.Session.IsNewSession is always true after the Stop-Request has been called, but I don't know why. Does anyone know why the session is lost? Is there any mistake in the implementation of the Stop-Controller?
The session is lost because you removed the session cookie. I'm not sure why that seems illogical. Each new page request supplies the cookie to asp.net, and if there is no cookie it generates a new one.
One option you could use to use cookieless sessions, which will add a token to the querystring. All you need to do is generate a new session for each login, or similar.
But this is one of the reasons why session variables are discouraged. Can you change the code to use an in-page variable, or store the variable in a database?
I have a web site running Burrow, and I'd like to use it for Quartz jobs as well.
The thing is that I want them to not share any state. The Quartz jobs is running in each own threads while the Mvc framework closes the Workspace at the end of every request.
Ideally, mvc should have it's own session, and each job should have it's own session.
What are my possibilities here?
PS: I'm very new to Quartz, Burrow and MVC btw, so I'm probably missing some very essential knowledge :|
I tried a simple naive way that at least seem to work for now. Is there something fundamentally wrong I'm doing here? Will these variables be garbage collected when a thread exits?
public static class SessionManager
{
[ThreadStatic]
private static IDictionary<ISessionFactory, ISession> _sessions;
public static ISession GetSession(Type type)
{
var burrow = new BurrowFramework();
if (burrow.WorkSpaceIsReady)
{
return burrow.GetSession(type);
}
else
{
if (_sessions == null)
{
_sessions = new Dictionary<ISessionFactory, ISession>();
}
var factory = burrow.GetSessionFactory(type);
if (!_sessions.ContainsKey(factory))
{
_sessions[factory] = null;
}
var session = _sessions[factory];
if (session == null || !session.IsOpen)
{
session = _sessions[factory] = factory.OpenSession();
}
return session;
}
}
}