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!