Corey Coogan

Python, .Net, C#, ASP.NET MVC, Architecture and Design

  • Subscribe

  • Archives

  • Blog Stats

    • 97,772 hits
  • Meta

StructureMap + WCF + NHibernate Part 2

Posted by coreycoogan on May 27, 2010

Introduction

In Part 1 of this two part series, I showed what it takes to create a library that will enable Dependency Injection in WCF services by leveraging the extension points in the WCF pipeline. My solution had very slight modifications to what was provided by Jimmy Bogard with plans for extending to allow an NHibernate (NH) Session/Call pattern in WCF utilizing StructureMap (SM).

In this post I’ll show my implementation, derived from the solution posted on Real Fiction, that is built on top of my Wcf.IoC library. This library will be responsible for getting a single ISession opened at the start of each call, injecting it into all dependent classes and then flushing and disposing it at the end of the call.

Why Session per Call?

Session per Call, also known as Session per Request, is the pattern where the application is given the responsibility of opening an ISession (DataContext or other UnitOfWork) and then disposing of it at the end of the call. Some variations of this pattern may also open an ITransaction at the beginning of the call and Commit or Rollback at the end. I prefer to return a status in my service response when something goes wrong, therefore I give the service the responsibility of creating and managing transactions. This way I can catch exceptions and react accordingly.

Employing this technique has several advantages.

  • Once setup, developers don’t have to worry about the ISession and where it comes from.
  • Once setup, developers don’t have to worry about properly disposing of the ISession when they are done with it.
  • If there are unhandled exceptions, the ISession is always disposed.
  • Ensures a single ISession is created and used by the service and component dependencies, thereby reducing resource consumption.

First, a special point of emphasis on the last bullet above, which is very important to get the full benefit of the Session/Call pattern we’re implementing here. For this to work, any classes that depend on an ISession must be configured to have an ISession injected into them (I prefer constructor injection for this). It is important that your components don’t configure ISession resolution, but rather let the application handle this detail. To learn more about configuring components from your application using StructureMap, have a look at this post on the topic.

NHibernate Session Context

To ensure the same NHibernate session is used throughout the WCF service call we’ll need to store the session somewhere. In a web application, this place is typically the HttpContext. In WCF, this is done by creating and adding an extension to the InstanceContext, which is basically a storage area scoped to the instance of the service object.

To do this, we simply implement the IExtension<> interface with a class that is responsible for holding a reference to the call’s ISession object and handles Flush and Disposal when the call is over. My implementation here is just like that of the inspiring post with the exception of my try/catch around the Session disposal.

/// <summary>
/// Holds a reference to an NH ISession and gets cached in the InstanceContext of a
/// WCF call.
/// </summary>
public class NhContextManager : IExtension<InstanceContext>
{
	public ISession Session { get; set; }

	public void Attach(InstanceContext owner)
	{
	}

	public void Detach(InstanceContext owner)
	{
		if (Session != null)
		{
			try
			{
				Session.Flush();
				Session.Dispose();
			}
			catch (Exception ex)
			{
				//log the error but don't throw
			}

		}

	}
}

IInstanceProvider

We’ll use a custom IInstanceProvider to add our Extension into the InstanceContext. This is a very straight forward process, as you’ll see. In Part 1 I showed the custom IInstanceProvider implementation that used SM to create an instance of the requested service. We’ll derive our NH Session-aware InstanceProvider from this one so we can add our extension before resolving the type from SM.

public class NhInstanceProvider : IocInstanceProvider
{
	public NhInstanceProvider(Type serviceType) : base(serviceType)
	{

	}

	public override object GetInstance(InstanceContext instanceContext, System.ServiceModel.Channels.Message message)
	{
		//add the NH Session manager to the context of this WCF request
		var nhSessMgrExtension = instanceContext.Extensions.Find<NhContextManager>();
		if (nhSessMgrExtension == null)
			instanceContext.Extensions.Add(new NhContextManager());

		//let the base handle the IoC resolution
		return base.GetInstance(instanceContext, message);
	}

	public override void ReleaseInstance(InstanceContext instanceContext, object instance)
	{
		var nhSessMgrExtension = instanceContext.Extensions.Find<NhContextManager>();
		if (nhSessMgrExtension != null)
			instanceContext.Extensions.Remove(nhSessMgrExtension);

		base.ReleaseInstance(instanceContext, instance);
	}

}

Setting the NhContextManager.Session Property

The NhContextManager’s Session property is what will be used access the current call’s open ISession object. Remember that the NhContextManager is just our way of storing the ISession in the context of the call. This is where we’ll utilize the ISessionFactory.GetCurrentSession() extension point that was graciously granted to us by NHibernate for things just like this.

NHibernate’s ISessionFactory interface has a method, GetCurrentSession(), intended for getting a contextual instance of a session. Getting this working requires two steps.

First, we must create an implementation of the ICurrentSessionContext interface. This is what NHibernate will use to get an open session when one is requested from the GetCurrentSession() method. The other step is to tell the NHibernate configuration what our implementation of ICurrentSessionContext is so it knows how to respond to GetCurrentSession() calls.

ICurrentSessionContext Implementation

First thing to notice is that the constructor takes an ISessionFactory instance. This is required for getting an open Session the first time. The CurrentSession() method is where all the work is done. First, search extensions on the InstanceContext and find the NhContextManager instance. Now get the Session from the NhContextManager.Session property. If it’s there, we’ll return it. If it’s not there, we’ll open a new one, set it on the NhContextManager object, then return it. This is the nuts and bolts of getting this whole thing working. By delegating the ISession.CurrentSession() method to use the WCF instance context, we are ensuring a single open ISession is used for each WCF service instance, which is created once per call.

public class WcfSessionContext : ICurrentSessionContext
{
	private readonly ISessionFactory _factory;
	public WcfSessionContext(ISessionFactory factory)
	{
		_factory = factory;
	}

	public ISession CurrentSession()
	{
		// Get the WCF InstanceContext:
		var contextManager = OperationContext.Current
			.InstanceContext.Extensions.Find<NhContextManager>();

		if (contextManager == null)
		{
			throw new InvalidOperationException(
				@"There is no context manager available.
				Check whether the NHibernateContextManager is added as InstanceContext extension.
				Make sure the service is being created with the NhServiceHostFactory.
				This Session Provider is intended only for WCF services.");
		}

		var session = contextManager.Session;
		if (session == null)
		{

			session = _factory.OpenSession();

			contextManager.Session = session;
		}

		return contextManager.Session;
	}
}

Setting up NHibernate with our ICurrentSessionContext Implementation

Now we have to tell NHibernate what we want to use to extend the ISessionFactory.GetCurrentSession() method. This may seem a bit goofy, but it works. Before building the SessionFactory, we add a property to the NHibernate.Cfg.Configuration using the necessary magic string and the fully qualified type of our ICurrentSessionContext class.

NOTE: this step would be done by the application in the BootStrapper, where NHibernate initialization should be taking place.

void AddCurrentSessionImpl(NHibernate.Cfg.Configuration config)
{

var sessionContextType = typeof (WcfSessionContext);

            var currentSessionContextImplTypeName = sessionContextType.FullName + ", " +
                                                    sessionContextType.Assembly.FullName;

            var props = config.Properties;
            if(props==null)
            {
                props = new Dictionary<string, string>();
                config.AddProperties(props);
            }

            props.Add("current_session_context_class", currentSessionContextImplTypeName);
}

Configuring StructureMap for ISession Resolution

Since we are relying our StructureMap to handle the Dependency Injection of our services, it must be configured to get our ISession from the ISessionFactory.GetCurrentSession() method. Even though StructureMap is to returning a transient instance, which is the default behavior, we’re trusting our WcfSessionContext to give us the same instance per WCF service call.

The best way to do this is create an SM Registry in our Wcf.NHib library. Our Registry will take an instance of an ISessionFactory. This is required so that our application can configure and initialize NHibernate however the application requires and then let our Wcf.Nhib library handle the specific registration. In this way, the library can be leveraged across any number of WCF services that have their own distinct NHibernate configurations.

/// <summary>
/// A StructureMap registry for telling the container how to resolve an ISession request.
/// This must be instantiated and added to the SM configuration so it has an instance of the
/// SessionFactory to use.
/// </summary>
public class WcfNHibernateRegistry : Registry
{
	public WcfNHibernateRegistry(ISessionFactory sessionFactory)
	{

		For<NHibernate.ISession>()
			.Use(() => sessionFactory.GetCurrentSession())
			;
	}
}

Ensuring the Proper IInstanceProvider Implementation

The lowest level object in our WCF pipeline is the InstanceProvider, which is responsible for creating the instance of our service, fully injected with Repositories, ISession, and whatever else is necessary. To make sure my NhInstanceProvider is used throughout the WCF call pipeline, I’ll want to implement a custom ServiceBehavior, ServiceHost and ServiceHostFactory. Luckily, I can just derive from the classes created in my Wcf.IoC library and override the necessary pieces. This is pretty simple and ensures that my service is both DI-enabled and Session/Call aware.

ServiceBehavior

All I have to do here is construct my NhInstanceProvider.

public class NhServiceBehavior : IocServiceBehavior
{
	/// <summary>
	/// A Func that takes the ServiceType in the constructor and instantiates a new IInstanceProvider.
	/// Defaults to an IocInstanceProvider
	/// </summary>
	public override Func<Type, IocInstanceProvider> InstanceProviderCreator
	{
		get
		{
			return (type) => new NhInstanceProvider(type);
		}
	}

}

ServiceHost

The custom ServiceHost has a little more custom code. We’re going to derive from the IocServiceHost to meet our IoC requirement. We’ll override the ServiceBehavior method where we’ll create an instance of the NhServiceHost. Before we return our instance though, we’ll make one modification.

Since our Session/Call implementation requires that WCF is operating in PerCall mode, let’s make sure that’s always the case by forcing that setting. If we use a stateful mode, we may run into the danger of the same ISession being shared between calls which could lead to all sorts of obvious problems.

To do this, we simply find the ServiceBehaviorAttribute of the service instance. If it’s not there, we’ll add it. Now it’s just a matter of setting the InsntaceContextMode property to InstanceContextMode.PerCall.

public class NhServiceHost : IocServiceHost
{
	public NhServiceHost(Type serviceType, params Uri[] baseAddresses)
		: base(serviceType, baseAddresses)
	{
	}

	public override IocServiceBehavior ServiceBehavior
	{
		get
		{
			var behavior = Description.Behaviors.Find<ServiceBehaviorAttribute>();
			if (behavior == null)
			{
				behavior = new ServiceBehaviorAttribute();
				Description.Behaviors.Add(behavior);
			}
			//force PerCall to ensure a single session is not shared
			behavior.InstanceContextMode = InstanceContextMode.PerCall;

			return new NhServiceBehavior();
		}
	}
}

ServiceHostFactory

The ServiceHostFactory, like the ServiceBehavior, will do nothing more than override the SvcHost property to ensure that the NhServiceHost is used when the IocServiceHostFactory asks for a ServiceHost instance.

public class NhServiceHostFactory : IocServiceHostFactory
{
	protected override Func<Type, Uri[], IocServiceHost> SvcHost
	{
		get
		{
			return (type, uri) => new NhServiceHost(type, uri);
		}
	}
}

Now all we need to do to get our NH Session/Call wired up is edit the markup of our .SVC files and tell it what factory to use.

<%@ ServiceHost
Language="C#"
Service="Service.ProfileService"
CodeBehind="ProfileService.svc.cs"
Factory="Wcf.NHib.NhServiceHostFactory,Wcf.NHibernate"
 %>

What This Gets Us

Now that we’ve jumped through all these hoops, what’s the payoff? We now have the ability to write a service that looks like this.

public class ProfileService : IProfileService
{
	readonly IProfileRepository _repository;
	readonly ISession _session;
	public ProfileService(IProfileRepository repository, ISession session)
	{
		_session = session;
		_repository = repository;
	}

	public ProfileDto GetProfile(int id)
	{
		using(var trans = _session.BeginTransaction())
		{
			var result = _repository.FindById(id);
			return result;
		}
	}
}

Conclusion

In Part 1 we saw how to build off a previous example to enable an extendable IoC-enabled WCF Service solution. This gives us testable services without relying on Poor Man’s DI. In Part 2, we extended the library so we could have not only DI provided by StructureMap but to also leverage NHibernate’s extension points which help us achieve a Session/Call pattern. This solution involved several moving pieces, but in the end they fit together rather nicely and quite cohesively. As you play with the solution you may be surprised first to see that it actually works, but more importantly how much more time you’ll have to devote to the important stuff since you quit worrying about Data Access plumbing.

About these ads

14 Responses to “StructureMap + WCF + NHibernate Part 2”

  1. [...] StructureMap + WCF + NHibernate Part 2 [...]

  2. Soma Body said

    Could you please post the source if you still have it handy? Thanks for the great post and your efforts!

  3. Soma Body said

    Forgot to mention – by asking for the source I mean the solution or project….

    • coreycoogan said

      Hi Soma,
      I would like to help you out, unfortunately I don’t have the time right now to make this solution available. I know it’s a little more work, but all the source is in the posts. You’ll just have to copy/paste.

      • coreycoogan said

        Hi Soma,
        so you are stuck on the NHibernate part of the solution? There are many ways to configure NHibernate and there’s a lot of information out there, which is why I left that part out of the already long blog post.

        In the AppInitialize method I discussed in Part 1 is where you’ll want to bootstrap NHibernate. I would start simple right now and not worry about code reusability until you get it working. For now, just put configure NH directly in that method and get it working.

        I am using Fluent NHibernate to create my configuration. There’s plenty of examples and documentation on the wiki. I would also look at how they are doing it in S#arpArchitecture. That was the inspiration for what I’m using.

        Unfortunately, my configuration stuff was built to work with a larger set of enterprise libraries and designed for maximum reusability. I’m afraid I can’t easily post the one part without first paring it down to its core.

        Hope this helps,
        Corey

  4. Soma Body said

    Thanks Corey – I know your busy as we all are. I wrongly assumed that the code in this post came from a working solution or project that you could just share. I tried to recreate the project but am stuck on the step that does the initialization and call to AddCurrentSessionImpl in the service. Could you share out the code that does the service bootstrapping and call to AddCurrentSessionImpl? I could also send you the project that I created if it would help.

    In any event the solution above holds promise and I am sure others could benefit from having a working sample.

    Thanks again for any help and time.

  5. Wouter said

    Hi, Thank you for the great post. Any chance you can provide the source code in a working sample? It would be much appreciated.

    • coreycoogan said

      I would love to get something out there. As I mentioned in the other comments, I am truly swamped right now. I realize that it’s a bit more work, but all the source is in the posts, requiring a couple hours of copy/paste. I would have modify the code I have as it was written to incorporate other enterprise components I’ve written, plus changing namespaces, etc.

      Sorry I can’t be of more help. I’m trying to learn Python for a new gig, coupled with my side work those two are maxing me out right now.

      corey

  6. Wouter said

    No problem. I will try and make it work from code samples. Thank you

  7. Juan said

    How can I include impersonation by Kerberos in your code, in developer environment I have no problem but in production server I have this error:
    Error de inicio de sesión del usuario ‘Domain\ServerName$’.

    Thanks for the great post!

    • coreycoogan said

      Hi Juan,
      I know very little Spanish and can only surmise that your error is saying that the server doesn’t have access to the current session? Since your code works in the development environment, but not in production, and considering the error you got, everything points to a security configuration. I’ve seen similar things happen when the server isn’t granted proper rights on the other machine.

      Have you checked here?

      http://www.google.com/search?sourceid=chrome&ie=UTF-8&q=Error+de+inicio+de+sesi%C3%B3n+del+usuario+Domain\

      • Juan said

        Thank you very much! It was very useful, the problem is in the tag, with throw the error mention before, but with I have no problem…

        Thanks for your quick response!!

      • Juan said

        Thank you very much! It was very useful, the problem is in the web.config system.web identity tag, with identity impersonate=”true” throw the error mention before, but with identity impersonate=”true” userName=”Domain\username” password=”*****”, I have no problem…

        Thanks for your quick response!!

  8. Juan said

    Thank you very much! It was very useful, the problem is in the web.config system.web identity tag, with identity impersonate=”true” throw the error mention before, but with identity impersonate=”true” userName=”Domain\username” password=”*****” I have no problem…

    Thanks for your quick response!!

    Posted again because the tags included was deleted.

Sorry, the comment form is closed at this time.

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: