Creating a session ASP.NET MVC action filter

Often, a unit of work maps neatly on to a single controller action. I'll show you how to create an action filter to manage our NHibernate sessions in an ASP.NET MVC application.

Getting ready

Setup an ASP.NET MVC application for NHibernate. The steps are as follows:

  1. Create a new ASP.NET MVC application.
  2. Add references to NHibernate.dll, NHibernate.ByteCode.Castle.dll, log4net.dll, and our Eg.Core project from Chapter 1.
  3. In the web.config file, set up the NHibernate and log4net configuration sections. Refer to the Configuring NHibernate with App.config recipes in Chapter 2.
  4. Set the current_session_context_class property to web.
  5. In Global.asax, create a static property named SessionFactory.
    public static ISessionFactory SessionFactory { get; 
    private set; }
  6. In the Application_Start method, add this code.
    log4net.Config.XmlConfigurator.Configure();
    var nhConfig = new Configuration().Configure();
    SessionFactory = nhConfig.BuildSessionFactory();

How to do it...

  1. Add the NHibernateSessionAttribute class as shown in the following code:
    [AttributeUsage(AttributeTargets.Method,
    AllowMultiple=false)]
    public class NHibernateSessionAttribute 
      : ActionFilterAttribute 
    {
      public NHibernateSessionAttribute()
      {
        Order = 100;
      }
      protected ISessionFactory sessionFactory
      {
        get
        {
          return MvcApplication.SessionFactory;
        }
      }
      public override void OnActionExecuting(
        ActionExecutingContext filterContext)
      {
        var session = sessionFactory.OpenSession();
        CurrentSessionContext.Bind(session);
      }
      public override void OnActionExecuted(
        ActionExecutedContext filterContext)
      {
       var session = CurrentSessionContext.Unbind(sessionFactory);
       session.Close();
      }
    }
  2. Decorate your controller actions with the attribute as shown in the following code:
    [NHibernateSession]
    public ActionResult Index()
    {
      return View(DataAccessLayer.GetBooks());
    }
  3. Create a dummy data access layer with this code:
    using System.Collections.Generic;
    
    namespace ActionFilterExample
    {
      public static class DataAccessLayer
      {
    
        public static IEnumerable<Eg.Core.Book> GetBooks()
        {
          var session = MvcApplication.SessionFactory
            .GetCurrentSession();
          using (var tx = session.BeginTransaction())
          {
            var books = session.QueryOver<Eg.Core.Book>()
              .List();
            tx.Commit();
            return books;
          }
        }
    
      }
    }
  4. Inside the Views folder, create a folder named Book
  5. In the Book folder, add a view using the settings shown in the following screenshot:
    How to do it...
  6. Open SQL Server Management Studio, connect to the NHCookbook database, and run this SQL code to create some book data:
    USE NHCookbook 
    
    INSERT INTO Product 
    VALUES (
      NEWID(),
      'Eg.Core.Book',
      0,
      'NHibernate 3 Cookbook',
      'Bridging the gap between database and .NET Application',
      45.99,
      null,
      'Jason Dentler',
      '3043'
    )
    
    INSERT INTO Product 
    VALUES (
      NEWID(),
      'Eg.Core.Book',
      0,
      'NHibernate 2 Beginner's Guide',
      'Rapidly retrieve data from your database into .NET objects',
      45.99,
      null,
      'Aaron Cure',
      '978-1-847198-90-7'
    )
  7. Build and run your application. You will see the following web page:
    How to do it...

How it works...

The concept behind this recipe is very similar to our session-per-request recipe at the beginning of the chapter. We are using NHibernate's contextual sessions with a variation of session-per-request.

Before the Index() controller action is executed, ASP.NET MVC will run our filter's OnActionExecuting method. In OnActionExecuting, our action filter opens a session and binds it to this web request using NHibernate's contextual sessions feature.

Similarly, ASP.NET MVC will run our filter's OnActionExecuted when Index() returns. In OnActionExecuted, the filter unbinds the session and closes it. Then, ASP.NET MVC processes the action result. In this case, it renders a view to display a list of books.

The Order property of an action filter determines in what order that action filter executes. For Executing events, all action filters with an unspecified Order are executed first. Then, those with a specific Order are executed, starting with zero, in ascending order. For Executed events, the process works in reverse. Essentially, it allows us to stack action filters - last in, first out. This provides a determinate order, so we can combine it with session-dependent filters with higher Order values.

There's more...

NHibernate requires an NHibernate transaction around every database interaction, whether it be a direct method call on the session or an action that triggers lazy loading. With this implementation, it is very difficult to capture lazy loading calls in a transaction. As we will see in the next recipe, we can combine the proper use of sessions and transactions in a single action filter to allow for lazy loading elsewhere in the controller action.

Be sure an action loads all of the data required by the view. The session is not open anymore when the action result (a view, in this case) is rendered.

Note

Because the session has already been closed, if a view attempts to access a lazy-loaded collection that wasn't loaded by the controller action, you will get a LazyInitializationException.

Even with more lenient implementations, it's not recommended to access the database from the view. Views are usually dynamic and difficult to test.

View models

To avoid this issue and many others, many ASP.NET MVC applications use view models. A view model class is defined for each view, and contains exactly the data required by that view, and nothing more. Think of it as a data transfer object between the controller and the view.

Rather than write pages of plumbing code to copy data from entities to view models, you can use an open source project, AutoMapper. When combined with an action filter attribute, this process becomes dead simple. A good example of this can be found in Jimmy Bogard's blog post at http://www.lostechies.com/blogs/jimmy_bogard/archive/2009/06/29/how-we-do-mvc-view-models.aspx.

Pay attention to the Order property on the AutoMapper attribute. To allow for lazy loading when translating from entities to view models, the Order should be even higher than our session attribute. This ensures that the session is open when AutoMapper is translating.

See also

  • Setting up session per web request
  • Creating a transaction ASP.NET MVC action filter
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset