.NET Client Profile Support in NHibernate 3

[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.

ClientProfileTargetFramework

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.

About James Kovacs

James Kovacs is a Technical Evangelist for JetBrains. He is passionate in sharing his knowledge about OO, SOLID, TDD/BDD, testing, object-relational mapping, dependency injection, refactoring, continuous integration, and related techniques. He blogs on CodeBetter.com as well as his own blog, is a technical contributor for Pluralsight, writes articles for MSDN Magazine and CoDe Magazine, and is a frequent speaker at conferences and user groups. He is the creator of psake, a PowerShell-based build automation tool, intended to save developers from XML Hell. James is the Ruby Track Chair for DevTeach, one of Canada’s largest independent developer conferences. He received his Bachelors degree from the University of Toronto and his Masters degree from Harvard University.
This entry was posted in NHibernate. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Anonymous

    @odalet – I fully agree. I’ve used log4net on many projects and it’s a great fit-for-purpose logging framework. I just wish someone would give it some TLC to fix minor issues like .NET Client Profile support. Given that it’s OSS, someone such as ourselves could send the team a patch. Hint, hint. :)

  • Anonymous

    It’s a shame log4net doesn’t come with a Client profile version… Well, for my own needs I recompiled it (stripping out the AspNetAppender and fixing the few issues with .NET4 with some new #if) to .NET 4 CP and that was not really difficult.

    Though it seems log4net is not really maintained any more, at least regarding the official releases. It’s been years since v1.2.10 (seems to be around 2008) has been released! I suppose more and more people will use their own derived log4net. And I’m not sure that’s a good thing (/trunk vs /vendors…)

  • Anonymous

    @Nikos – Yes, invoking NHProf via reflection would work and save you from having to reference the appender assembly directly. You still have the minor problem of flipping between Client and Full profiles as the .NET Client Profile cannot load the appender assembly due to its reference to System.Web.

  • http://twitter.com/nikosbaxevanis Nikos Baxevanis

    Great post! You can also (optionally) invoke the NHibernate Profiler via reflection. Here is how:

    http://www.nikosbaxevanis.com/bonus-bits/2010/11/using-reflection-to-dynamically-invoke-the-nhibernate-profiler.html

    So you won’t need to add a reference to the profiler .dll at all.

  • Anonymous

    Information Post!!!!!!!!!!!!!!!!!

    ————————————-www.gametradeeasy.com

  • c4mel0t

    James, thank you for such informational and interesting article ;)