NHibernate Session Per Request Using Castles WcfFacility
I was under the false impression that the NHibernateFacility would handle session by request, needed for lazy loading collections, when used with the WcfFacility in an IIS setting. This might work if you use ASP.NET compatibility mode, however, it’s not a recommended solution and I needed to support multiple database connections. Soooo … after searching and reading some other great posts to get me in the right direction I came up with the following solutions.
First the posts that helped me with this:
- Integrating Castle Windsor and NHibernate wiht WCF
- Integrating Structuremap and Nhibernate with WCF
- Integrating Castle Windsor and WCF
- Using NHibernate Session Per Request with WCF Windsor Integration
- Nhibernate on WCF
Second what I’m building on:
- Castle.Windsor
- Castle.NHibernateFacility
- Castle.WcfFacility
- Running on IIS
The solution:
First off we have a class used to hold the ISession references for the duration of the request.
public sealed class NHibernateContextExtension : IExtension, IDisposable
{
public List Sessions { get; set; }
public NHibernateContextExtension()
{
Sessions = new List();
}
public void Attach(InstanceContext owner)
{ }
public void Detach(InstanceContext owner)
{ }
public void Dispose()
{
if (Sessions != null)
Sessions.ForEach(s => s.Close());
}
}
Next we need to hook into the WCF call at the before and after invoke. This is where we create the needed ISession instances and store in the above class.
public class WcfSessionPerRequestCallContextInitializer : ICallContextInitializer
{
private readonly ISessionManager sessionManager;
private readonly string[] dbAliases;
public WcfSessionPerRequestCallContextInitializer(ISessionManager sessionManager, string[] dbAliases)
{
sessionManager = sessionManager;
dbAliases = dbAliases;
}
public object BeforeInvoke(InstanceContext instanceContext, IClientChannel channel, Message message)
{
var extension = new NHibernateContextExtension();
foreach (var dbAlias in dbAliases)
extension.Sessions.Add(sessionManager.OpenSession(dbAlias));
instanceContext.Extensions.Add(extension);
return extension;
}
public void AfterInvoke(object correlationState)
{
if (correlationState != null)
((IDisposable)correlationState).Dispose();
}
}
Now we needed to hook the ICallContextInitializer into WCF using a IServiceBehavior. The WcfFacility will add this to all WCF services if it’s registered on the container.
public class WcfSessionPerRequestBehavior : IServiceBehavior
{
private readonly ISessionManager sessionManager;
private string[] dbAliases = { "nh.facility.default" };
public string[] DbAliases
{
get { return dbAliases; }
set { dbAliases = value; }
}
public WcfSessionPerRequestBehavior(ISessionManager sessionManager)
{
sessionManager = sessionManager;
}
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection endpoints, BindingParameterCollection bindingParameters)
{
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase)
{
foreach (var cdb in serviceHostBase.ChannelDispatchers)
{
var channelDispatcher = cdb as ChannelDispatcher;
if (null != channelDispatcher)
{
foreach (var endpointDispatcher in channelDispatcher.Endpoints)
{
foreach (var dispatchOperation in endpointDispatcher.DispatchRuntime.Operations)
{
dispatchOperation.CallContextInitializers.Add(new WcfSessionPerRequestCallContextInitializer(sessionManager, _dbAliases));
}
}
}
}
}
}
Lastly, register the behavior with the container and provide any aliases needed.
container.Register(
Component.For()
.ImplementedBy()
.DependsOn(new Hashtable { { "DbAliases", new[] { "nh.facility.default" } } })
);
Webmentions
These are webmentions via the IndieWeb and webmention.io. Mention this post from your site: