Writing a WCF service with NHibernate
Published 9/28/2009 6:00 AM by Toran Billups 7 Comments
Update: Back when I wrote this post I was pulling a great deal of information from Donald Belcham, as he did a ton of in depth research on the subject. He recently wrote me asking that I update my blog because the code I show in the post below is not stable due to some WCF multi-threading issues. Using IServiceBehavior instead of IContractBehavior is the solution that youll need going forward. So if anyone new happens to find my post be sure to checkout the updated code from Donald here and here
After getting comfortable with NHibernate in the web context, I wanted to find out how easy it would be to reuse some functionality via web services. I started with the usual WCF SOAP implementation and found that no session was available. It turns out the http module that creates the session and binds it to the aspnet session context was not being called when a client requested something from the service.
So I started looking for a solution to prove I could create a session when a WCF web service was called. The first example I found worked great on a "per call" basis. And although you could read the post I referenced, I wanted to include my spin on how to get this implemented.
As I mentioned above, this implementation is based on the "per call" context meaning that each service call will be a unit of work. To me this was the obvious way to go because I isolate each web service anyway as it should represent a single piece of business functionality decoupled from other services with different responsibilities.
What does this "per call" concept mean to someone familiar with traditional WCF services? That you must tell WCF explicitly what InstanceContextMode to use. In your service contract, apply the following attribute to express the service behavior.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
public class UserService : IUserService
{
public User GetUserById(int id) { }
}
Once you apply this attribute, WCF will create a new service context each time the service is called. Again this is a good idea because WCF will give you a new Instance Context object that will have its own execution area separate from any other call coming in.
After learning how this worked I thought "ok, so how can I tell WCF to create a new session when I call a service?". I wish I could say "simple ...", but having zero experience with creating a custom WCF attribute, I spent a lot of time wrapping my head around new concepts that were required to work with NHibernate in this "per call" way.
First you need to create the attribute itself that will be applied to each service using NHibernate. I called this attribute "NHibernateContext" and the implementation is shown below.
public class NHibernateContextAttribute : Attribute, IContractBehavior
{
private ISessionFactory _SessionFactory;
public ISessionFactory SessionFactory
{
private get { return _SessionFactory; }
set { _SessionFactory = value; }
}
public void AddBindingParameters(System.ServiceModel.Description.ContractDescription contractDescription, System.ServiceModel.Description.ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(System.ServiceModel.Description.ContractDescription contractDescription, System.ServiceModel.Description.ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)
{
}
public void ApplyDispatchBehavior(System.ServiceModel.Description.ContractDescription contractDescription, System.ServiceModel.Description.ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)
{
dispatchRuntime.InstanceContextInitializers.Add(new NHibernateContextInitializer());
}
public void Validate(System.ServiceModel.Description.ContractDescription contractDescription, System.ServiceModel.Description.ServiceEndpoint endpoint)
{
}
}
Now this class is just one small piece of the puzzle because it only has a single property for the session factory and one implemented behavior "ApplyDispatchBehavior". This is where we need to initialize the actual context by calling another class that will have the responsibility of creating the actual NHibernate session.
public class NHibernateContextInitializer : IInstanceContextInitializer
{
public void Initialize(InstanceContext instanceContext, Message message)
{
instanceContext.Extensions.Add(new NHibernateContextExension(NHibernateFactory.OpenSession()));
}
}
Notice the above class simply asks the SessionFactory for a new session and hands that session off to the class that does the real work. The NHibernateContextExtension class holds the relevant session acting as our unit of work. It then begins the unit of work by calling the "BeginTransaction" method on the session object. And finally a delegate is added so that each session is committed when the InstanceContext is closed.
public class NHibernateContextExension : IExtension
{
public NHibernateContextExension(ISession session__1)
{
Session = session__1;
}
private ISession _Session;
public ISession Session
{
get { return _Session; }
private set { _Session = value; }
}
public void Attach(System.ServiceModel.InstanceContext owner)
{
_Session.BeginTransaction();
//also make sure when the per call is done we close out the transaction and close the session
owner.Closed += new EventHandler(delegate(object sender, EventArgs args)
{
this.CallDetach((InstanceContext)sender);
});
}
private void CallDetach(System.ServiceModel.InstanceContext owner)
{
if (_Session != null)
{
try
{
_Session.Transaction.Commit();
}
catch (Exception ex)
{
_Session.Transaction.Rollback();
}
finally
{
_Session.Close();
}
}
}
public void Detach(System.ServiceModel.InstanceContext owner) { }
}
But how can you actually get to this session without diving into the internals of WCF each time the user needs an object? One last WCF specific class named "NHibernateContext" that will provide access to the current InstanceContext so you can pull out the session object.
public static NHibernateContextExension Current()
{
return OperationContext.Current.InstanceContext.Extensions.Find();
}
And to abstract this away even further I simply created a variation of the base repository that overrides the "GetSession" method to instead query NHibernateContext for the current session.
public abstract class WCFNHibernateRepository : NHibernateRepository where T : class
{
public WCFNHibernateRepository() : base()
{
}
protected override ISession GetSession()
{
return NHibernateContext.Current().Session;
}
}
Now you have a functional attribute that can be applied to any service that requires an NHibernate session. The below is one example of how you might decorate a service with both the "per call" attribute and the special "NHibernateContext" attribute.
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)]
[NHibernateContext()]
public class UserService : IUserService
{
public User GetUserById(int id)
{
return mRepository.GetUserById(id);
}
}
If I missed anything, be sure to download the source code and play with the sample application. In addition to the WCF server and WCF client samples I also included a separate download for the base class library that contains all the WCF classes shown above.
Wonderful, one stop link for WCF and nHIbernate Integration
Is there anyway we can remove NHibernateContext attribute in WCF layer and decouple service layer from data access layer?
When I wrote this post I was trying to show my basic understanding of how one might use NHibernate with the WCF stack to accomplish typical line of business development. Also when I used the above in a production application, I felt the service classes were decoupled from the repository classes. But for me I always use NHibernate to achieve persistence ignorance so I don't even think of data access explicitly. For example, I would ask the service class to interact with the repository just long enough to hydrate an entity, then I would ask that entity to do something and optionally I might transform that entity into a DTO and send it over the wire. The entire time I'm not actually thinking much about the database. But to be clear this post does assumes one autonomous transaction per incoming request and because of this you are stuck with one NHibernate session throughout.
Please, update again your source... links are dead. If you can, please, let me know.
Why in the article source code is wrote in C#, but the sources are in VB ?
@midavik sorry for the confusion but when I started writing this article last year it was about the time I switched from vb to c#. So the solution itself was already completed in vb but when it came time for me to blog about it I decided to show these in c#.
Thanks for providing this article and source code. I was running into intermittent NHibernate Session problems and by converting my code to use this solution, my WCF service started working much more reliably.