Search code examples
nhibernateasp.net-web-apihttpmodule

Web API 2 HTTP module EndRequest event being fired first?


I'm trying to implement the NHibernate session management/repository pattern code found here related to the implementation I originally read on the NHibernate Forge page about effective session management.

I am using ASP.NET Web API 2 and am having issues with the HTTPModule events. When I run the app with a simple Home/Index action, I get a System.Collections.Generic.KeyNotFoundException error in the UnBind method.

When I debug, BeginRequest is never called, and somehow EndRequest is falling through. I believe this is where the error is coming from.

Am I missing something obvious? Why is EndRequest being called before anything else?

LazySessionContext and HTTPModule:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NHibernate;
using NHibernate.Context;
using NHibernate.Engine;

namespace MyService.Service.Infrastructure.SessionManagement
{
    //Is up to you to:
    //-set the currentsessioncontextclass in nhibernate as follows:
    //      configuration.Properties[Environment.CurrentSessionContextClass]
    //      = typeof (LazySessionContext).AssemblyQualifiedName;
    //-implement ISessionFactoryProvider or use Castle Typed Factories:
    //      container.Register(Component.For<ISessionFactoryProvider>().AsFactory());
    //-load the SessionFactoryProvider in the HttpApplication as follows:
    //      HttpContext.Current.Application[SessionFactoryProvider.Key]
    //              = your instance of ISessionFactoryProvider;
    //-inject ISessionFactory in Daos and use GetCurrentSessionContext()


    public class LazySessionContext : ICurrentSessionContext
    {
        private readonly ISessionFactoryImplementor factory;
        private const string CurrentSessionContextKey = "NHibernateCurrentSession";

        public LazySessionContext(ISessionFactoryImplementor factory)
        {
            this.factory = factory;
        }

        /// <summary>
        /// Retrieve the current session for the session factory.
        /// </summary>
        /// <returns></returns>
        public ISession CurrentSession()
        {
            Lazy<ISession> initializer;
            var currentSessionFactoryMap = GetCurrentFactoryMap();
            if (currentSessionFactoryMap == null ||
                !currentSessionFactoryMap.TryGetValue(factory, out initializer))
            {
                return null;
            }
            return initializer.Value;
        }

        /// <summary>
        /// Bind a new sessionInitializer to the context of the sessionFactory.
        /// </summary>
        /// <param name="sessionInitializer"></param>
        /// <param name="sessionFactory"></param>
        public static void Bind(Lazy<ISession> sessionInitializer, ISessionFactory sessionFactory)
        {
            var map = GetCurrentFactoryMap();
            map[sessionFactory] = sessionInitializer;
        }

        /// <summary>
        /// Unbind the current session of the session factory.
        /// </summary>
        /// <param name="sessionFactory"></param>
        /// <returns></returns>
        public static ISession UnBind(ISessionFactory sessionFactory)
        {
            var map = GetCurrentFactoryMap();
            var sessionInitializer = map[sessionFactory];
            map[sessionFactory] = null;
            if (sessionInitializer == null || !sessionInitializer.IsValueCreated) return null;
            return sessionInitializer.Value;
        }

        /// <summary>
        /// Provides the CurrentMap of SessionFactories.
        /// If there is no map create/store and return a new one.
        /// </summary>
        /// <returns></returns>
        private static IDictionary<ISessionFactory, Lazy<ISession>> GetCurrentFactoryMap()
        {
            var currentFactoryMap = (IDictionary<ISessionFactory, Lazy<ISession>>)
                                    HttpContext.Current.Items[CurrentSessionContextKey];
            if (currentFactoryMap == null)
            {
                currentFactoryMap = new Dictionary<ISessionFactory, Lazy<ISession>>();
                HttpContext.Current.Items[CurrentSessionContextKey] = currentFactoryMap;
            }
            return currentFactoryMap;
        }
    }

    public interface ISessionFactoryProvider
    {
        IEnumerable<ISessionFactory> GetSessionFactories();
    }

    public class SessionFactoryProvider
    {
        public const string Key = "NHibernateSessionFactoryProvider";
    }

    public class NHibernateSessionModule : IHttpModule
    {
        private HttpApplication app;

        public void Init(HttpApplication context)
        {
            app = context;
            context.BeginRequest += ContextBeginRequest;
            context.EndRequest += ContextEndRequest;
            context.Error += ContextError;
        }

        private void ContextBeginRequest(object sender, EventArgs e)
        {
            var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
            foreach (var sf in sfp.GetSessionFactories())
            {
                var localFactory = sf;
                LazySessionContext.Bind(
                    new Lazy<ISession>(() => BeginSession(localFactory)),
                    sf);
            }
        }

        private static ISession BeginSession(ISessionFactory sf)
        {
            var session = sf.OpenSession();
            session.BeginTransaction();
            return session;
        }

        private void ContextEndRequest(object sender, EventArgs e)
        {
            var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
            var sessionsToEnd = sfp.GetSessionFactories()
                                   .Select(LazySessionContext.UnBind)
                                   .Where(session => session != null);

            foreach (var session in sessionsToEnd)
            {
                EndSession(session);
            }
        }

        private void ContextError(object sender, EventArgs e)
        {
            var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
            var sessionstoAbort = sfp.GetSessionFactories()
                                    .Select(LazySessionContext.UnBind)
                                    .Where(session => session != null);

            foreach (var session in sessionstoAbort)
            {
                EndSession(session, true);
            }
        }

        private static void EndSession(ISession session, bool abort = false)
        {
            if (session.Transaction != null && session.Transaction.IsActive)
            {
                if (abort)
                {
                    session.Transaction.Rollback();
                }
                else
                {
                    session.Transaction.Commit();
                }
            }
            session.Dispose();
        }

        public void Dispose()
        {
            app.BeginRequest -= ContextBeginRequest;
            app.EndRequest -= ContextEndRequest;
            app.Error -= ContextError;
        }
    }
}

Web.config with HttpModule registered:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
  <appSettings>
    <add key="webpages:Version" value="3.0.0.0" />
    <add key="webpages:Enabled" value="false" />
    <add key="ClientValidationEnabled" value="true" />
    <add key="UnobtrusiveJavaScriptEnabled" value="true" />
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5" />
    <httpRuntime targetFramework="4.5" />
    <httpModules>
      <add name="PerRequestLifestyle" type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor" />
      <add name="SessionPerRequest" type="MyService.Service.Infrastructure.SessionManagement.NHibernateSessionModule, MyService.Service" />
    </httpModules>
  </system.web>
  <system.webServer>
    <handlers>
      <remove name="ExtensionlessUrlHandler-Integrated-4.0" />
      <remove name="OPTIONSVerbHandler" />
      <remove name="TRACEVerbHandler" />
      <add name="ExtensionlessUrlHandler-Integrated-4.0" path="*." verb="*" type="System.Web.Handlers.TransferRequestHandler" preCondition="integratedMode,runtimeVersionv4.0" />
    </handlers>
    <modules runAllManagedModulesForAllRequests="true">
      <add name="PerRequestLifestyle" type="Castle.MicroKernel.Lifestyle.PerWebRequestLifestyleModule, Castle.Windsor" />
      <add name="SessionPerRequest" type="MyService.Service.Infrastructure.SessionManagement.NHibernateSessionModule, MyService.Service" />
    </modules>
  </system.webServer>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Optimization" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Helpers" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.WebPages" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="3.0.0.0" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
        <bindingRedirect oldVersion="1.0.0.0-5.1.0.0" newVersion="5.1.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
</configuration>

Solution

  • Turns out I only needed to register the HttpModules in the system.webServer node and not system.Web.