Question

This is probably somewhat of a niche problem, but perhaps someone can help me. I'm porting my web services from ASMX to WCF, however I'm totally built on Castle ActiveRecord. To make sure I don't have some sort of weird issue in my configuration, I've built an isolated repro from scratch using all the latest Castle and NHibernate libraries from NuGet.

The first thing I do is initialize ActiveRecord in Application_Start. Normally I'd use a web.config instance but this probably shouldn't matter:

protected void Application_Start(object sender, EventArgs e)
{
   //NHibernate.Driver.OracleClientDriver
   IDictionary<string, string> properties = new Dictionary<string, string>();

   properties.Add("connection.driver_class", "NHibernate.Driver.OracleClientDriver");
   properties.Add("dialect", "NHibernate.Dialect.Oracle10gDialect");
   properties.Add("connection.provider", "NHibernate.Connection.DriverConnectionProvider");
   properties.Add("connection.connection_string", "user Id=xxx;password=xxx;server=localhost;persist security info=True");
   properties.Add("proxyfactory.factory_class", "NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle");

   InPlaceConfigurationSource source = new InPlaceConfigurationSource();
   source.IsRunningInWebApp = true;
   source.ThreadScopeInfoImplementation = typeof(Castle.ActiveRecord.Framework.Scopes.HybridWebThreadScopeInfo);

   source.Add(typeof(ActiveRecordBase), properties);

   ActiveRecordStarter.Initialize(source, typeof(Task), typeof(Project));
}

Notice I'm also using the HybridWebThreadScopeInfo implementation, since HttpContext.Current will be null in WCF.

Next, I implement my web service:

public class Service1 : IService1
{
   public string GetData(int value)
   {
      Project p;

      p = Project.Find(123M);
      var count = p.Tasks.Count(); //Force count query

      return p.GoldDate.ToString();
   }
}

When I call Project.Find(), it works fine. Next, I call p.Tasks.Count() which will force a new query, as the Tasks property is lazy. When I do this, I get the exception:

Initializing[NHTest.Project#123]-failed to lazily initialize a collection of role: NHTest.Project.Tasks, no session or session was closed

The reason this is happening is because there's no session scope. I guess the internal ActiveRecordBase method will create a session if it doesn't exist or something. Now, I could create one manually with this:

public string GetData(int value)
{
   Project p;

   using (new SessionScope())
   {
      p = Project.Find(123M);
      var count = p.Tasks.Count(); //Force count query

      return p.GoldDate.ToString();
   }
}

This will work great. However, I'd like to not have to do this in all my code, as this works perfectly in an ASP.NET Web Service.

So why does it work in ASP.NET?

The reason is works is because ActiveRecord comes with an httpModule called Castle.ActiveRecord.Framework.SessionScopeWebModule. This module gets run before every HTTP request and creates a default session within ActiveRecord. However, this module is not called before WCF HTTP requests.

What about ASP.NET Compatibility Mode?

You can enable ASP.NET Compatibility Mode using:

<serviceHostingEnvironment aspNetCompatibilityEnabled="true" ... />

This will also fix the problem, as well as provide other access to the HTTP request pipeline within WCF. This would be one solution. However, though it works on the Visual Studio test webserver, I've never been able to get compatibility mode working on IIS7. Plus, I feel the best design is to work completely within the WCF infrastructure.

My Question:

Does Castle ActiveRecord provide the ability to create session scopes within WCF requests? If so, how is this configured?

Was it helpful?

Solution

After digging through the Castle ActiveRecord source code, I found the answer to this is no; ActiveRecord does not have any sort of native WCF support. However, this isn't hugely complicated to handle as ActiveRecord takes care of all the messy NHibernate details such as configuration and session factories. In fact, creating a session scope automatically for each WCF operation is pretty much as straight forward as implementing a custom IDisplayMessageInspector. This message inspector simply has to new up a SessionScope object when a request is received, and dispose of it when the request ends:

public class MyInspector : IDispatchMessageInspector
{
   public MyInspector()
   {
   }

   public object AfterReceiveRequest(ref System.ServiceModel.Channels.Message request, System.ServiceModel.IClientChannel channel, System.ServiceModel.InstanceContext instanceContext)
   {
      return new SessionScope();
   }

   public void BeforeSendReply(ref System.ServiceModel.Channels.Message reply, object correlationState)
   {
      SessionScope scope = correlationState as SessionScope;

      if (scope != null)
      {
         scope.Flush();
         scope.Dispose();
      }
   }
}

The constructor for SessionScope will set the current session scope, which is then accessible anywhere else in that thread. This makes things like lazy loading and passing data readers around simply work. You'll also note that I use the correlationState state to track the reference to the SessionScope during a request, analogous to how SessionScopeWebModule uses the HttpContext items collection.

This IDispatchInspector can be configured in web.config, or attached to a WCF service through an Attribute class:

public class MyServiceBehaviorAttribute : Attribute, IServiceBehavior
{
   public void AddBindingParameters(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection<ServiceEndpoint> endpoints, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
   {
   }

   public void ApplyDispatchBehavior(ServiceDescription serviceDescription, System.ServiceModel.ServiceHostBase serviceHostBase)
   {
      foreach (ChannelDispatcher cDispatcher in serviceHostBase.ChannelDispatchers)
         foreach (EndpointDispatcher eDispatcher in cDispatcher.Endpoints)
            eDispatcher.DispatchRuntime.MessageInspectors.Add(new MyInspector());

   }

   public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
   {
   }
}

Then of course tag the service class with [MyServiceBehavior].

Ack Threads!

Ah yes, this all might work in theory but in reality, the IDispatchMessageInspector might not run in the same thread as the operation. To fix this, we need to implement an ActiveRecord IWebThreadScopeInfo class. This object is used when ActiveRecord looks up the current session scope. This is usually keyed using the HttpContext when using ASP.NET, but the HybridThreadScopeInfo is also able to key each session scope stack by thread. Neither is going to work for WCF, so we need a super-duper-hybrid implementation that supports ASP.NET (when HttpContext.Current exists), WCF (when OperationContext.Current) exists, and per-thread when you're just running in some arbitrary thread.

My implementation (which isn't highly tested) is based on the HybridThreadScopeInfo implementation with a few tweaks:

public class WcfThreadScopeInfo : AbstractThreadScopeInfo, IWebThreadScopeInfo
{
   const string ActiveRecordCurrentStack = "activerecord.currentstack";

   [ThreadStatic]
   static Stack stack;

   public override Stack CurrentStack
   {
      [MethodImpl(MethodImplOptions.Synchronized)]
      get
      {
         Stack contextstack;

         if (HttpContext.Current != null) //We're running in an ASP.NET context
         {
            contextstack = HttpContext.Current.Items[ActiveRecordCurrentStack] as Stack;
            if (contextstack == null)
            {
               contextstack = new Stack();
               HttpContext.Current.Items[ActiveRecordCurrentStack] = contextstack;
            }

            return contextstack;
         }

         if (OperationContext.Current != null) //We're running in a WCF context
         {
            NHibernateContextManager ctxMgr = OperationContext.Current.InstanceContext.Extensions.Find<NHibernateContextManager>();
            if (ctxMgr == null)
            {
               ctxMgr = new NHibernateContextManager();
               OperationContext.Current.InstanceContext.Extensions.Add(ctxMgr);
            }

            return ctxMgr.ContextStack;
         }

         //Working in some random thread
         if (stack == null)
         {
            stack = new Stack();
         }

         return stack;
      }
   }
}

You'll also need an IExtension<InstanceContext> derived class, which WCF uses to store data within an operation context:

public class NHibernateContextManager : IExtension<InstanceContext>
{
   public Stack ContextStack { get; private set; }

   public NHibernateContextManager()
   {
      this.ContextStack = new Stack();
   }

   public void Attach(InstanceContext owner)
   {
   }

   public void Detach(InstanceContext owner)
   {
   }
}

You can then configure ActiveRecord to use this IWebThreadScopeInfo class in web.config by setting the threadinfotype property on the <activerecord> configuration node, or the ThreadScopeInfoImplementation property if you're using an InPlaceConfigurationSource object.

Hope this helps someone!

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top