.NET Client Profile Support in NHibernate 3 | James Kovacs
[Code for this article is available on GitHub here.]
NHibernate 3 introduces support for both the .NET 3.5 Client Profile and .NET 4.0 Client Profile. This means that applications built with NHibernate can be deployed to client desktops without the full .NET Framework installed. Why hasn’t this been available all along?
NHibernate 2.X was built to support .NET 2.0 and above. The Client Profile wasn’t introduced until after NHibernate 2.0 was released. Microsoft introduced the Client Profile in .NET 3.5 to reduce the size of the .NET Framework on client machines. (The reality is that end users don’t need ASP.NET, server-side WCF features, and MSBuild on their machines to run client-side applications.)
So why didn’t NHibernate support the Client Profile once it was released? What was holding them back? What was holding NHibernate back from supporting the Client Profile immediately was a dependency on System.Web. Now why the heck would NHibernate depend on System.Web? There aren’t many places that NHibernate touches System.Web, but there are a few. The first is in supporting session-per-request semantics in web applications using Contextual Sessions. I won’t go into the details here, but once you configure cfg.CurrentSessionContext<T>() in Loquacious or hibernate.current_session_context_class in hibernate.cfg.xml, you can get the current session from your static session factory. (ASIDE: If none of the built-in ICurrentSessionContext classes suffices for your needs, it is very easy to implement your own.)
var session = sessionFactory.GetCurrentSession();
The ManagedWebSessionContext and WebSessionContext classes can be used for session-per-request semantics and both store the current session in the HttpContext. Hence they need a reference to System.Web. So to support the Client Profile, the NHibernate team had to break this dependency on System.Web. They did this by accessing the HttpContext via a compiled dynamic method, which is evaluated at run-time. (A compiled dynamic method has much better performance than accessing properties through reflection.)
Another more insidious dependency on System.Web was in the logging infrastructure. Before NHibernate 3, NHibernate took a hard dependency on log4net. If you wanted logging, you used log4net. Now here is the insidious part… log4net has a dependency on System.Web for its AspNetTraceAppender, which writes to the ASP.NET TraceContext. (You can access the ASP.NET TraceContext via http://example.com/trace.axd.) To break this dependency, NHibernate 3 introduces LoggerProvider and the IInternalLogger. If a logger is explicitly configured, it uses that one. Now for a bit of cleverness. If no logger is explicitly configured, and LoggerProvider is asked for an IInternalLogger, it checks the bin directory. If it finds log4net, it uses log4net. Otherwise it defaults to the NoLoggingLogger. (N.B. Out-of-the-box NHibernate 3 only supports log4net or no logging, though it isn’t too onerous support other logging frameworks by implementing an IInternalLogger adapter and some support classes.)
I haven’t done an exhaustive search of the NHibernate 2.X codebase looking for other dependencies on System.Web, but those two give you an idea of why supporting the .NET Client Profile wasn’t as simple as recompiling NHibernate 2.X. The team had to break some dependencies on assemblies not include with the .NET Client Profile while not breaking backward compatibility. For most developers, supporting the .NET Client Profile is as simple as switching the Target Framework on their assemblies.
One word of warning… If you’re using NHibernate Profiler (and you should be), the NHibernateProfiler.Appender doesn’t support the .NET Client Profile because it requires log4net. You can use a preprocessor directive around the initialization line of NHibernate Profiler and then define that conditional compilation constant (using #define ENABLE_NHPROF) to enable/disable profiling. You’ll also have to change the target framework to .NET 3.5 or .NET 4.0 temporarily for the profiling session so that your project compiles.
#if ENABLE_NHPROF HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize(); #endif
UPDATE: NHibernate Profiler build 796 and later supports profiling applications built against the .NET Client Profile. More information can be found here.