Hashem Aboonajmi
Hashem Aboonajmi

Reputation: 13860

NHibernate session is closed when refreshing page

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:

  1. everything works well

after hitting refresh button:

  1. a new session bind to the current context.
  2. the new session will be injected the repository (session is open)
  3. 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! enter image description here 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

Upvotes: 0

Views: 749

Answers (2)

Ilya Palkin
Ilya Palkin

Reputation: 15737

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:

  1. First invocation of Post -> new instance of 'LoggingNHibernateSession' is created
  2. First invocation of Put -> another one instance of 'LoggingNHibernateSession' is created
  3. Second invocation of Put -> instance of 'LoggingNHibernateSession' from previous step is used
  4. 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 */
}

Upvotes: 0

Radim K&#246;hler
Radim K&#246;hler

Reputation: 123861

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):

  1. Client FORM is about to send the data to server. The method is POST, the Action is Update()
  2. Sent FORM is coming to server, Action Update() is triggerred - all the transactions stuff is in place (as described above)
  3. 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
  4. 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...

Upvotes: 1

Related Questions