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 or Web API 2 application.

Getting ready

Follow the instructions in the Getting ready section of the Setting up session-per-web request recipe in the beginning of this chapter. You can name the project ActionFilterExample.

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. Create a new model class (in the Models folder) named BookModel:
    public class BookModel
    {
      public int Id { get; set; }
      public string Name { get; set; }
      public string Author { get; set; }
    }
  3. Create a new MVC controller called BooksController:
    public class BooksController : Controller
    {
      [NHibernateSession]
      public ActionResult Index()
      {
        var books = DataAccessLayer.GetBooks()
            .Select(x=>new BookModel {
    Id=x.Id, 
    Name=x.Name,
    Author= x.Author});
        return View(books);
      }
    }
  4. Create a dummy data access layer with this code:
    public static class DataAccessLayer
    {
      public static IEnumerable<Book> GetBooks()
      {
        var session = MvcApplication.SessionFactory
          .GetCurrentSession();
        using (var tx = session.BeginTransaction())
        {
          var books = session.QueryOver<Book>()
            .List();
          tx.Commit();
          return books;
        }
      }
    }
  5. Inside the Views folder, create a folder named Books.
  6. In the Books folder, add a view named Index.cshtml:
    @model IEnumerable<ActionFilterExample.Models.BookModel>
    
    @{
      ViewBag.Title = "Books";
    }
    
    <h2>Books</h2>
    <ul>
      @foreach (var book in Model)
      {
    <li>@book.Name - @book.Author</li>
    }
    </ul>
  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 builds on the session-per-request recipe at the beginning of the chapter.

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. An option here would be just keep a reference to the session instance inside the controller.

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 as last in, first out. This provides a determinate order, so we can combine it with session-dependent filters with higher Order values.

What about Web API?

We can apply exactly the same concept to Web API, since that framework uses an almost identical action filter model. Instead of deriving from System.Web.Mvc.ActionFilterAttribute we go for the System.Web.Http.Filters.ActionFilterAttribute and override the corresponding methods:

public override void OnActionExecuting(HttpActionContext 
  actionContext)
{
  var session = sessionFactory.OpenSession();
  CurrentSessionContext.Bind(session);
}

public override void OnActionExecuted(HttpActionExecutedContext  
 actionExecutedContext)
{
  var session = CurrentSessionContext.Unbind(sessionFactory);
  session.Close();
}

This attribute can now be applied to any ApiController that requires an NHibernate session.

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.

Make sure that the 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 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 writing 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.

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
3.139.70.21