I am currently involved in a new CSD project where we have just started to discuss some basic architecture and design aspects. We still have A LOT to talk about, but some things are already pretty certain: WCF (no doubt!), NHibernate (many vendors to support), general principles (separation of concerns, etc.),…
One of the first things that come to mind with the above combination (WCF & NHibernate, that is) is data session management (or data context for LINQ, same issue there). In an ASP.Net application you’ll probably go for a request-scoped ISession
whose lifetime you manage through an HttpModule
or so.
WCF is actually very similar, except that you’ve got more options in terms of instance and concurrency management, to name a few. However, since a per-call model is considered the most valid option in most cases, we can focus on the similar case.
After some experimenting, and going through the (still very few) examples on the subject I came up with the following first attempt to extend WCF:
1. Implement a simple ICallContextInitializer
to manage the scope and lifetime of the (potentially needed) NHibernate session:
public class NHibernateContext : ICallContextInitializer { private static readonly ISessionFactory sessionFactory = new Configuration().BuildSessionFactory(); [ThreadStatic] // who needs OperationContext? ;-) private static ISession session; /// <summary> /// Gets the NHibernate session for the current /// service operation/thread. /// </summary> public static ISession Session { get { // lazy init (we might not always need it) if (session == null) session = sessionFactory.OpenSession(); return session; } } object ICallContextInitializer.BeforeInvoke( System.ServiceModel.InstanceContext instanceContext, System.ServiceModel.IClientChannel channel, System.ServiceModel.Channels.Message message) { Debug.Assert(session == null); return null; // we don't need no correlation } void ICallContextInitializer.AfterInvoke(object state) { // Dispose the session if it as created if (session != null) session.Dispose(); } }
I decided to provide lazy initialization of the ISession. This allows you to apply the service behavior (see later) without having to worry about potential overhead; if the operation does not go to the database, no behavior is added. Asserting that the thread-scoped session variable is null, is to make sure the behavior is applied no more than once and the operation is running in ConcurrencyMode.Single
. Finally, the AfterInvoke
makes sure the session (if any) is disposed of.
2. Create a service behavior that assures the above ICallContextInitializer
is added to the service operation(s).
public class NHibernateServiceBehavior : Attribute, IServiceBehavior { public void ApplyDispatchBehavior( ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { foreach (ChannelDispatcher cd in serviceHostBase.ChannelDispatchers) { foreach (EndpointDispatcher ed in cd.Endpoints) { foreach (DispatchOperation operation in ed.DispatchRuntime.Operations) { operation.CallContextInitializers.Add( new NHibernateContext()); } } } } public void AddBindingParameters( ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, System.Collections.ObjectModel.Collection endpoints, BindingParameterCollection bindingParameters) { } public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { } }
You might ask yourself why I chose to use a service behavior and not just an operation behavior (since that’s basically what I’m doing). Well, the main reason is that I wanted as much flexibility as available and service behaviors are the only behaviors that allow all configuration options: through attributes, .Net configuration files or explicitly coded.
With the above behavior in place you can simply call and work with your session, probably not straight from your service code, but through a Repository maybe. Just call NHibernateContext.Session
and you’re done.
One last (optional) thing to make sure .Net configuration files really are an option for applying our service behavior is a BehaviorExtensionElement:
public class NHibernateServiceBehaviorExtensionElement : BehaviorExtensionElement { public override Type BehaviorType { get { return typeof(NHibernateServiceBehavior); } } protected override object CreateBehavior() { return new NHibernateServiceBehavior(); } }
This allows you to apply the behavior as follows.
<services> <service name="[namespace].SomeService" behaviorConfiguration="nhibernateBehaviorCfg" > ... </service> </services> <extensions> <behaviorExtensions> <add name="nhibernateBehavior" type="[namespace].NHibernateExtesionConfigurationElement, <assemblyName>, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> </behaviorExtensions> </extensions> <behaviors> <serviceBehaviors> <behavior name=" nhibernateBehaviorCfg "> <nhibernateBehavior /> ... </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
Image may be NSFW.
Clik here to view.
Clik here to view.
Clik here to view.
Clik here to view.
