NHibernate in Web API ASP.NET: No session bound to the current context

3.7k Views Asked by At

I'm new to NHibernate and trying to use it in ASP.NET WEB API. Firstly I used it successfully with one table named "Category" which the controller class is as follow:

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;

namespace TestMVCProject.Web.Api.Controllers
{
    [LoggingNHibernateSession]
    public class CategoryController : ApiController
    {
        private readonly ISession _session;
        private readonly ICategoryMapper _categoryMapper;
        private readonly IHttpCategoryFetcher _categoryFetcher;

        public CategoryController(
            ISession session,
            ICategoryMapper categoryMapper,
            IHttpCategoryFetcher categoryFetcher)
        {
            _session = session;
            _categoryMapper = categoryMapper;
            _categoryFetcher = categoryFetcher;
        }

        public IEnumerable<Category> Get()
        {
            return _session
                .QueryOver<Data.Model.Category>()
                .List()
                .Select(_categoryMapper.CreateCategory)
                .ToList();
        }

        public Category Get(long id)
        {
            var category = _categoryFetcher.GetCategory(id);
            return _categoryMapper.CreateCategory(category);
        }


        public HttpResponseMessage Post(HttpRequestMessage request, Category category)
        {
            var modelCategory = new Data.Model.Category
            {
                Description = category.Description,
                CategoryName = category.CategoryName
            };

            _session.Save(modelCategory);

            var newCategory = _categoryMapper.CreateCategory(modelCategory);

            //var href = newCategory.Links.First(x => x.Rel == "self").Href;

            var response = request.CreateResponse(HttpStatusCode.Created, newCategory);
            //response.Headers.Add("Location", href);

            return response;
        }


        public HttpResponseMessage Delete()
        {
            var categories = _session.QueryOver<Data.Model.Category>().List();
            foreach (var category in categories)
            {
                _session.Delete(category);
            }

            return new HttpResponseMessage(HttpStatusCode.OK);
        }


        public HttpResponseMessage Delete(long id)
        {
            var category = _session.Get<Data.Model.Category>(id);
            if (category != null)
            {
                _session.Delete(category);
            }

            return new HttpResponseMessage(HttpStatusCode.OK);
        }


        public Category Put(long id, Category category)
        {
            var modelCateogry = _categoryFetcher.GetCategory(id);

            modelCateogry.CategoryName = category.CategoryName;
            modelCateogry.Description = category.Description;

            _session.SaveOrUpdate(modelCateogry);

            return _categoryMapper.CreateCategory(modelCateogry);
        }
    }
}

But when I add The "Product" table which has a foreign key of the Category table, the product controller doesn't work and throws below exception:

No session bound to the current context

ProductController class is as follow:

using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using TestMVCProject.Web.Api.HttpFetchers;
using TestMVCProject.Web.Api.Models;
using TestMVCProject.Web.Api.TypeMappers;
using TestMVCProject.Web.Common;
//using TestMVCProject.Web.Common.Security;
using NHibernate;

namespace TestMVCProject.Web.Api.Controllers
{
    [LoggingNHibernateSession]
    public class ProductController : ApiController
    {
        private readonly ISession _session;
        private readonly IProductMapper _productMapper;
        private readonly IHttpProductFetcher _productFetcher;


        public ProductController(
            ISession session,
            IProductMapper productMapper,
            IHttpProductFetcher productFetcher)
        {
            _session = session;
            _productMapper = productMapper;
            _productFetcher = productFetcher;
        }

        public IEnumerable<Product> Get()
        {
            return _session
                .QueryOver<Data.Model.Product>()
                .List()
                .Select(_productMapper.CreateProduct)
                .ToList();
        }

        public Product Get(long id)
        {
            var product = _productFetcher.GetProduct(id);
            return _productMapper.CreateProduct(product);
        }


        public HttpResponseMessage Post(HttpRequestMessage request, Product product)
        {
            var modelProduct = new Data.Model.Product
            {
                Description = product.Description,
                ProductName = product.ProductName
            };

            _session.Save(modelProduct);

            var newProduct = _productMapper.CreateProduct(modelProduct);

            //var href = newproduct.Links.First(x => x.Rel == "self").Href;

            var response = request.CreateResponse(HttpStatusCode.Created, newProduct);
            //response.Headers.Add("Location", href);

            return response;
        }


        public HttpResponseMessage Delete()
        {
            var categories = _session.QueryOver<Data.Model.Product>().List();
            foreach (var product in categories)
            {
                _session.Delete(product);
            }

            return new HttpResponseMessage(HttpStatusCode.OK);
        }


        public HttpResponseMessage Delete(long id)
        {
            var product = _session.Get<Data.Model.Product>(id);
            if (product != null)
            {
                _session.Delete(product);
            }

            return new HttpResponseMessage(HttpStatusCode.OK);
        }


        public Product Put(long id, Product product)
        {
            var modelProduct = _productFetcher.GetProduct(id);

            modelProduct.ProductName = product.ProductName;
            modelProduct.Description = product.Description;

            _session.SaveOrUpdate(modelProduct);

            return _productMapper.CreateProduct(modelProduct);
        }
    }
}

and the mapping class for Product table:

using TestMVCProject.Data.Model;
using FluentNHibernate.Mapping;

namespace TestMVCProject.Data.SqlServer.Mapping
{
    public class ProductMap : ClassMap<Product>
    {
        public ProductMap()
        {
            Id(x => x.ProductId);
            Map(x => x.ProductName).Not.Nullable();
            Map(x => x.Description).Nullable();
            Map(x => x.CreateDate).Not.Nullable();
            Map(x => x.Price).Not.Nullable();               

            References<Category>(x => x.CategoryId).Not.Nullable();               
        }
    }
}

What is wrong?

2

There are 2 best solutions below

0
On BEST ANSWER

Your snippets are missing the way, how the ISessionFactory is created and how ISession is passed into your controllers... You should follow this really comprehensive story (by Piotr Walat):

NHibernate session management in ASP.NET Web API

Where you can see that we, can use 2.3. Contextual Sessions:

NHibernate.Context.WebSessionContext - stores the current session in HttpContext. You are responsible to bind and unbind an ISession instance with static methods of class CurrentSessionContext.

The configuration

<session-factory>
    ..
    <property name="current_session_context_class">web</property>
</session-factory>

In the article you can check that we need at the app start initialize factory (just an extract):

public class WebApiApplication : System.Web.HttpApplication  
{
    private void InitializeSessionFactory() { ... }

    protected void Application_Start()
    {
        InitializeSessionFactory();
    ...

Next we should create some AOP filter (just an extract):

public class NhSessionManagementAttribute : ActionFilterAttribute  
{
    ...
    public override void OnActionExecuting(HttpActionContext actionContext)
    {
        // init session
        var session = SessionFactory.OpenSession();
        ...

    public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
    {
        // close session
        ...
        session = CurrentSessionContext.Unbind(SessionFactory);
    }

For more details check the source mentioned above

0
On

Your approach of passing the session to the constructor of the controller factory does not seems to be working, there are a few ways to do this

1. Using dependency injection

If you are using a dependency injection framework, you have to configure controller so that it's constructed per request, it should looks like this (I have used the code for Ninject)

Step 1 - setup the session for injection

public class DIModule : NinjectModule
{
    public override void Load()
    {
    this.Bind<ISessionFactory>()... bind to the session factory
        this.Bind<ISession>().ToMethod(ctx => ctx.Kernel.Get<ISessionFactory>().OpenSession())
            .InRequestScope();
    }

    private ISession CreateSessionProxy(IContext ctx)
    {
        var session = (ISession)this.proxyGenerator.CreateInterfaceProxyWithoutTarget(typeof(ISession), new[] { typeof(ISessionImplementor) }, ctx.Kernel.Get<SessionInterceptor>());
        return session;
    }
}

Step 2 - Create the controller factory so that it will inject the session when resolving

public class NinjectControllerFactory : DefaultControllerFactory, IDependencyResolver
    {
        private IDependencyResolver _defaultResolver;

        public NinjectControllerFactory(IDependencyResolver defaultResolver)
        {
            _defaultResolver = defaultResolver;
        }

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            return controllerType == null
                   ? null
                   : (IController)DependencyKernel.Kernel.Get(controllerType);
        }

        public IDependencyScope BeginScope()
        {
            return this;
        }

        public object GetService(Type serviceType)
        {
            try
            {
                return DependencyKernel.Kernel.Get(serviceType);
            }
            catch (Exception)
            {
                return GetService(serviceType);
            }
        }

        public IEnumerable<object> GetServices(Type serviceType)
        {
            try
            {
                object item = DependencyKernel.Kernel.Get(serviceType);
                return new List<object>() {item};
            }
            catch (Exception)
            {
                return GetServices(serviceType);
            }
        }

        public void Dispose()
        {
        }
    }

Step 3 - Register the controller factory

public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {

            var factory = new NinjectControllerFactory(GlobalConfiguration.Configuration.DependencyResolver);
            ControllerBuilder.Current.SetControllerFactory(factory);
            GlobalConfiguration.Configuration.DependencyResolver = factory;
        }
    }

Now what will happen is that when your controller is created it will inject the a new NH session per each request.

2. Using a filter

This is much simpler, but you may need to change your controllers a bit this to work,

Step 1 - Setup the correct session context for the factory

_sessionFactory = CreateConfiguration()
                .ExposeConfiguration(c => c.SetProperty("current_session_context_class","web"))
                .BuildSessionFactory();

Step 2 - Create the filter

public class SessionPerRequestAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var session = SessionFactory.OpenSession();
            NHibernate.Context.CurrentSessionContext.Bind(session);
            base.OnActionExecuting(actionContext);
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
            var session = SessionFactory.GetCurrentSession();
            session.Flush();
            session.Clear();
            session.Close();
            base.OnActionExecuted(actionExecutedContext);
        }
    }

Step 3 - Register the filter in global configuration

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            //Do other config here
            config.Filters.Add(new SessionPerRequestAttribute());
        }
    }

Step 4 - Modify your controller a bit,

public class CategoryController : ApiController
{
    private readonly ICategoryMapper _categoryMapper;
    private readonly IHttpCategoryFetcher _categoryFetcher;

    public CategoryController(
        ICategoryMapper categoryMapper,
        IHttpCategoryFetcher categoryFetcher)
    {
        _categoryMapper = categoryMapper;
        _categoryFetcher = categoryFetcher;
    }

    public IEnumerable<Category> Get()
    {
        var session = SessionFactory.GetCurrentSession();
        return session 
            .QueryOver<Data.Model.Category>()
            .List()
            .Select(_categoryMapper.CreateCategory)
            .ToList();
    }
}

Here what happens is, when a request comes it will create a new session and it is bound to the request context and same is used for the web API method.