Fluent NHibernate, auto mappings and ASP.NET MVC 3

In my previous article, I demonstrated how to use Fluent NHibernate with auto mappings. After that, my plan was to move the code into an ASP.NET MVC 3 application. I assumed it would be relatively simple, which goes to show just how little I know about these things. Anyways, hopefully someone else out there will be able to learn from my experience.

Here’s what the completed project will look like:

Create a new ASP.NET MVC 3 web application called FluentNHibernateMvc3 in Microsoft Visual Studio 2010 and use the empty project template. Next, right click on the project in the Solution Explorer and choose Manage NuGet Packages… Search the online gallery for Fluent NHibernate and install it.

EDIT 12/18/2012: You can get the source code on GitHub.

Once you’ve installed Fluent NHibernate, copy the Employee, Product and Store classes from the Entities folder of our previous project to the Models folder of our current project. Make sure you change the namespace in each file accordingly.

Next, create a new controller called HomeController in the Controllers folder. Since my goal is to move the code from our previous project to our new project with as little changes as possible, the HomeController class will look very similar to the Program class with the following exceptions:

  1. We’ll move the NHibernate specific methods to a new custom HTTP module.
  2. We’ll use a repository to create a layer of abstraction between our main application and our persistence layer.
  3. We’ll split the Main method into two actions: the Index action, which will display the data, and the Seed action, which will add sample data to our database. This is because we don’t want to add sample data every time our application runs.

Here’s a rough skeleton of the home controller:

namespace FluentNHibernateMvc3.Controllers
{
    public class HomeController : Controller
    {
        private readonly IRepository<Store> storeRepository;
        
        // Constructs our home controller
        public HomeController() { }
        
        // Gets all the stores from our database and returns a view that displays them
        public ActionResult Index() { }
        
        // Adds sample data to our database
        public ActionResult Seed() { }
        
        // AddProductsToStore and AddEmployeesToStore methods go here...
    }
}

The first thing you should notice is that the home controller has a field called storeRepository, which implements an IRepository of type Store. This needs to be set in the constructor method:

// Constructs our home controller
public HomeController()
{
    storeRepository = new Repository<Store>();
}

We’ll create the Repository class and IRepository interface later.

Next, we’ll move the bulk of the Main method from our previous project to the Seed action here:

// Adds sample data to our database
public ActionResult Seed()
{
    // Create a couple of Stores each with some Products and Employees
    var barginBasin = new Store { Name = "Bargin Basin" };
    var superMart = new Store { Name = "SuperMart" };

    var potatoes = new Product { Name = "Potatoes", Price = 3.60 };
    var fish = new Product { Name = "Fish", Price = 4.49 };
    var milk = new Product { Name = "Milk", Price = 0.79 };
    var bread = new Product { Name = "Bread", Price = 1.29 };
    var cheese = new Product { Name = "Cheese", Price = 2.10 };
    var waffles = new Product { Name = "Waffles", Price = 2.41 };

    var daisy = new Employee { FirstName = "Daisy", LastName = "Harrison" };
    var jack = new Employee { FirstName = "Jack", LastName = "Torrance" };
    var sue = new Employee { FirstName = "Sue", LastName = "Walkters" };
    var bill = new Employee { FirstName = "Bill", LastName = "Taft" };
    var joan = new Employee { FirstName = "Joan", LastName = "Pope" };

    // Add Products to the Stores
    // The Store-Product relationship is many-to-many
    AddProductsToStore( barginBasin, potatoes, fish, milk, bread, cheese );
    AddProductsToStore( superMart, bread, cheese, waffles );

    // Add Employees to the Stores
    // The Store-Employee relationship is one-to-many
    AddEmployeesToStore( barginBasin, daisy, jack, sue );
    AddEmployeesToStore( superMart, bill, joan );

    storeRepository.SaveOrUpdateAll( barginBasin, superMart );

    return RedirectToAction( "Index" );
}

There are a few important changes you should notice:

  1. We don’t create a new session factory in the Seed action, or anywhere else in the home controller for that matter.
  2. We don’t open a session here.
  3. We don’t create or commit a transaction.

These tasks will be handled by our custom HTTP module at the beginning and end of each web request. Separating these tasks allows us to minimize the dependencies in our home controller, which is often referred to as loose coupling. Our controller doesn’t know anything about sessions or transactions – it isn’t even aware of NHibernate!

The Index action is very simple:

// Gets all the stores from our database and returns a view that displays them
public ActionResult Index()
{
    var stores = storeRepository.GetAll();

    return View( stores.ToList() );
}

Here, we call the GetAll method of our repository, cast the result to a list and send the list to the view. The Index view should go in your Views/Home folder and should look like this:

@model List<FluentNHibernateMvc3.Models.Store>

@{
    ViewBag.Title = "Index";
}

<h2>Index</h2>

<ul>
    @foreach ( var store in Model ) {
        <li>@store.Name</li>
        <ul>
            <li>Products:</li>
            <ul>
                @foreach ( var product in store.Products ) {
                    <li>@product.Name</li>
                }
            </ul>
            <li>Staff:</li>
            <ul>
                @foreach ( var employee in store.Staff ) {
                    <li>@employee.FirstName @employee.LastName</li>
                }
            </ul>
        </ul>
    }
</ul>

Now, let’s tackle that repository. Place these files in a new folder called Repositories at the root level of the project. This is where we’ll define what exactly the GetAll and SaveOrUpdateAll methods do that we’re using in our controller actions. First, create the interface:

namespace FluentNHibernateMvc3.Repositories
{
    public interface IRepository<T>
    {
        IQueryable<T> GetAll();
        IQueryable<T> Get( Expression<Func<T, bool>> predicate );
        IEnumerable<T> SaveOrUpdateAll( params T[] entities );
        T SaveOrUpdate( T entity );
    }
}

Then create the implementation:

namespace FluentNHibernateMvc3.Repositories
{
    public class Repository<T> : IRepository<T>
    {
        private readonly ISession session;

        public Repository()
        {
            session = NHibernateSessionPerRequest.GetCurrentSession();
        }

        public IQueryable<T> GetAll()
        {
            return session.Query<T>();
        }

        public IQueryable<T> Get( Expression<Func<T, bool>> predicate )
        {
            return GetAll().Where( predicate );
        }

        public IEnumerable<T> SaveOrUpdateAll( params T[] entities )
        {
            foreach ( var entity in entities )
            {
                session.SaveOrUpdate( entity );
            }

            return entities;
        }

        public T SaveOrUpdate( T entity )
        {
            session.SaveOrUpdate( entity );

            return entity;
        }
    }
}

Speaking of loose coupling, our repository doesn’t know anything about session factories or transactions, but it does have a session field that implements ISession. This field is set by calling the GetCurrentSession method of our custom HTTP module, which we’ll name NHibernateSessionPerRequest.

Here’s an outline of the NHibernateSessionPerRequest class, which you can put in a new Modules folder at the root level of the project:

namespace FluentNHibernateMvc3.Modules
{
    /// 
    /// http://www.bengtbe.com/blog/2009/10/08/nerddinner-with-fluent-nhibernate-part-3-the-infrastructure
    /// 
    public class NHibernateSessionPerRequest : IHttpModule
    {
        private static readonly ISessionFactory sessionFactory;

        // Constructs our HTTP module
        static NHibernateSessionPerRequest() { }
        
        // Initializes the HTTP module
        public void Init( HttpApplication context ) { }

        // Disposes the HTTP module
        public void Dispose() { }

        // Returns the current session
        public static ISession GetCurrentSession() { }

        // Opens the session, begins the transaction, and binds the session
        private static void BeginRequest( object sender, EventArgs e ) { }

        // Unbinds the session, commits the transaction, and closes the session
        private static void EndRequest( object sender, EventArgs e ) { }

        // Returns our session factory
        private static ISessionFactory CreateSessionFactory() { }

        // Returns our database configuration
        private static MsSqlConfiguration CreateDbConfig()

        // Returns our mappings
        private static AutoPersistenceModel CreateMappings()

        // Updates the database schema if there are any changes to the model,
        // or drops and creates it if it doesn't exist
        private static void UpdateSchema( Configuration cfg ) { }
    }
}

Notice that the sessionFactory field, which implements ISessionFactory, is static. Session factories can be expensive to create, especially with a large domain model. So we only want to instantiate one of these during the lifetime of our app, which will hang around in memory until our app dies:

// Constructs our HTTP module
static NHibernateSessionPerRequest()
{
    sessionFactory = CreateSessionFactory();
}

// Returns our session factory
private static ISessionFactory CreateSessionFactory()
{
    return Fluently.Configure()
        .Database( CreateDbConfig )
        .Mappings( m => m.AutoMappings.Add( CreateMappings() ) )
        .ExposeConfiguration( UpdateSchema )
        .CurrentSessionContext<WebSessionContext>()
        .BuildSessionFactory();
}

// Returns our database configuration
private static MsSqlConfiguration CreateDbConfig()
{
    return MsSqlConfiguration
        .MsSql2008
        .ConnectionString( c => c.FromConnectionStringWithKey( "testConn" ) );
}

// Returns our mappings
private static AutoPersistenceModel CreateMappings()
{
    return AutoMap
        .Assembly( System.Reflection.Assembly.GetCallingAssembly() )
        .Where( t => t.Namespace != null && t.Namespace.EndsWith( "Models" ) )
        .Conventions.Setup( c => c.Add( DefaultCascade.SaveUpdate() ) );
}

// Updates the database schema if there are any changes to the model,
// or drops and creates it if it doesn't exist
private static void UpdateSchema( Configuration cfg )
{
    new SchemaUpdate( cfg )
        .Execute( false, true );
}

Most of these methods can be copied to this class from our previous project. There are only a few changes:

  1. We’ve replaced the DropCreateSchema method with a new method called UpdateSchema. As the name suggests, this method will only create the database schema if it doesn’t already exist. Otherwise, it will update it from the model.
  2. NHibernate needs to be told what type of context it’s running in. We do that by calling the CurrentSessionContext extension method in our fluent configuration. It’s strongly typed, so we give it a type of WebSessionContext.
  3. Instead of building a connection string in the CreateDbConfig method, I’m using a connection string from the Web.config file. You can do it either way.
  4. The CreateMappings method will create auto mappings for all types in our assembly that have a namespace ending in “Models”. This is preferable over matching the entire namespace, since it will require less changes moving forward.

Here are the remaining methods:

// Initializes the HTTP module
public void Init( HttpApplication context )
{
    context.BeginRequest += BeginRequest;
    context.EndRequest += EndRequest;
}

// Disposes the HTTP module
public void Dispose() { }

// Returns the current session
public static ISession GetCurrentSession()
{
    return sessionFactory.GetCurrentSession();
}

// Opens the session, begins the transaction, and binds the session
private static void BeginRequest( object sender, EventArgs e )
{
    ISession session = sessionFactory.OpenSession();

    session.BeginTransaction();

    CurrentSessionContext.Bind( session );
}

// Unbinds the session, commits the transaction, and closes the session
private static void EndRequest( object sender, EventArgs e )
{
    ISession session = CurrentSessionContext.Unbind( sessionFactory );

    if ( session == null ) return;

    try
    {
        session.Transaction.Commit();
    }
    catch ( Exception )
    {
        session.Transaction.Rollback();
    }
    finally
    {
        session.Close();
        session.Dispose();
    }
}

Previously, we weren’t catching any exceptions when we tried to commit our transaction. Wrapping our call to commit in a try-catch statement gives us a chance to rollback if something goes wrong.

We’re almost done! We still need to register our custom HTTP module in IIS, so that it can receive and process HTTP requests. If you are using IIS 6.0 or IIS 7.0 in classic mode, add this to your Web.config file:

<configuration>
  <system.web>
    <httpModules>
      <add name="NHibernateSessionPerRequest" type="FluentNHibernateMvc3.Modules.NHibernateSessionPerRequest" />
     </httpModules>
  </system.web>
</configuration>

If you are using IIS 7.0 in integrated mode, add this to your Web.config file:

<configuration>
  <system.webServer>
    <modules>
      <add name="NHibernateSessionPerRequest" type="FluentNHibernateMvc3.Modules.NHibernateSessionPerRequest" />
    </modules>
  </system.webServer>
</configuration>

Finally, if you decided not to build a connection string in the CreateDbConfig method, you’ll need to add one under the configuration section of your Web.config file. Here’s mine, which is using SQL Server 2008:

<connectionStrings>
  <add name="testConn" connectionString="Server=testServer;Database=testDB;User ID=testUser;Password=testPass" providerName="System.Data.SqlClient" />
</connectionStrings>

Before running the application, make sure you create an empty database, and replace “testServer”, “testDB”, “testUser” and “testPass” with your actual server, database, username, and password.

When you run the application, you should see this:

Index

Not very exciting, it is? That’s because we haven’t added any data to the database yet! In your browser, navigate to the Seed action of the home controller, i.e. append /Home/Seed to the URL in your browser’s address bar. You should see this:

Index

  • Bargin Basin
    • Products:
      • Potatoes
      • Fish
      • Milk
      • Bread
      • Cheese
    • Staff:
      • Daisy Harrison
      • Jack Torrance
      • Sue Walkters
  • SuperMart
    • Products:
      • Bread
      • Cheese
      • Waffles
    • Staff:
      • Bill Taft
      • Joan Pope

Look familiar? It should if you read my previous article.

I’d like to demonstrate one more thing before I sign off. Add the following action to the home controller:

// Gets and modifies a single store from our database
public ActionResult Test()
{
    var barginBasin = storeRepository.Get( s => s.Name == "Bargin Basin" ).SingleOrDefault();

    if ( barginBasin == null )
    {
        return RedirectToAction( "Index" );
    }

    barginBasin.Name = "Bargain Basin";

    return RedirectToAction( "Index" );
}

This action is fairly self-explanatory: It will find the Store record named “Bargin Basin” and rename it to “Bargain Basin.” The cool part is that we don’t have to manually save anything because it’s all taken care of by our repository and HTTP module! Navigate to the Test action of the home controller in your browser and you’ll see what I mean.

That does it for this article. Next time, I’ll cover unit testing and dependency injecting using Castle Windsor.

Advertisements

One thought on “Fluent NHibernate, auto mappings and ASP.NET MVC 3

  1. Lelala

    I’m note sure if you could skip the close() call at:

    session.Close();
    session.Dispose();

    maybe dispose() runs an automatic close, not sure yet…

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s