3

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?

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
amin mohammadi
  • 901
  • 2
  • 15
  • 33

2 Answers2

4

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

Radim Köhler
  • 122,561
  • 47
  • 239
  • 335
1

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.

Low Flying Pelican
  • 5,974
  • 1
  • 32
  • 43