I have used current session contexts in web applications with the ManagedWebContext class. I wanted to get this working in a non-asp.net environment though. And I wanted to keep the UI responsive, which means pushing as much as possible to the background. I took a queue from Jeremy Miller's article on functional programming. To that end, here is one approach to managing Sessions in a threaded environment.
we will utilize the typical MVP triad injecting our view into the presenter.
public class Presenter : IPresenter { private readonly IView view; private readonly IService service; private readonly ICommandExecutor executor; public Presenter(IView view, IService service, ICommandExecutor executor) { this.view = view; this.service = service; this.executor = executor; } public virtual void UpdateView() { executor.Execute(() => { var text = service.GetData(1); return () => view.UpdateUserInterface(text); }); } }
So what is the ICommandExecutor? This is where we centralize threading and session management. For SoC I have devided this into 2 implementations. one for threading the other for session management.
public class AsynchronousExecutor : ICommandExecutor { private readonly SynchronizationContext synchronizationContext; private readonly ICommandExecutor executor; public AsynchronousExecutor(SynchronizationContext synchronizationContext, ICommandExecutor executor) { this.synchronizationContext = synchronizationContext; this.executor = executor; } public void Execute(Action action) { ThreadPool.QueueUserWorkItem(item => executor.Execute(action)); } public void Execute(Func<Action> action) { ThreadPool.QueueUserWorkItem(item => executor.Execute(() => { var continuation = action(); synchronizationContext.Send(x => continuation(), null); })); } } public class UnitOfWorkExecutor : ICommandExecutor { private readonly ISessionFactory factory; public UnitOfWorkExecutor(ISessionFactory factory) { this.factory = factory; } public void Execute(Action action) { ExecuteWithinAUnitOfWork(action); } public void Execute(Func<Action> action) { ExecuteWithinAUnitOfWork(() => action()); } private void ExecuteWithinAUnitOfWork(Action action) { try { using (var transaction = new TransactionScope()) { CurrentSessionContext.Bind(factory.OpenSession()); action(); transaction.Complete(); } } finally { var session = CurrentSessionContext.Unbind(factory); if (session != null) { session.Dispose(); } } } }
AsynchronousExecutor pushes work to the background while UnitOfWorkExecutor sets up the context of a session. For information on synchronizationContext I will defer you to Jeremy's MSDN article. 2 steps left. 1) Configure NH and 2) wire this together.
To configure NH we will use Fluent NHibernate. The important part is setting the CurrentSessionContext property. For those not using programmatic configuration, you would set the property in the hibernate.config file along with the other session factory configs.
For wiring I'm using Castle Windsor. Any IoC should allow for something similar though. after placing the configuration and factory in the kernel we can use a factory method to resolve the session from the kernel. Note that the lifestyle of session is Transient. this is crucial, otherwise the sessions will not resolve correctly. We will use the same technique for resolving the SynchronizationContext as well.
public class NHibernateFacility : AbstractFacility { protected override void Init() { var configuration = Fluently .Configure() .Database(() => MsSqlConfiguration.MsSql2000) .Mappings(m => m.FluentMappings.AddFromAssembly(GetType().Assembly)) .ExposeConfiguration(ExtendConfiguration) .BuildConfiguration(); Kernel.AddComponentInstance("configuration", configuration); Kernel.AddComponentInstance("factory", configuration.BuildSessionFactory()); Kernel.Register(Component .For<ISession>() .LifeStyle.Is(LifestyleType.Transient) .UsingFactoryMethod(k => k
.Resolve<ISessionFactory>()
.GetCurrentSession())); } private static void ExtendConfiguration(Configuration cfg) { var context = typeof(ThreadStaticSessionContext).AssemblyQualifiedName;
cfg.SetProperty(Environment.CurrentSessionContextClass, context);
}
}
Finally, we wire up the entire container.
internal static class Program { private static IWindsorContainer container; [STAThread] private static void Main() { container = new WindsorContainer() .AddFacility<FactorySupportFacility>() .AddFacility<NHibernateFacility>() .AddComponentLifeStyle<ICommandExecutor, AsynchronousExecutor>(LifestyleType.Singleton) .AddComponentLifeStyle<ICommandExecutor, UnitOfWorkExecutor>(LifestyleType.Singleton) .AddComponentLifeStyle<MainForm>(LifestyleType.Transient) .AddComponentLifeStyle<IPresenter, Presenter>(LifestyleType.Transient) .AddComponentLifeStyle<IService, Service>(LifestyleType.Transient) .Register(Component .For<SynchronizationContext>() .LifeStyle.Is(LifestyleType.Singleton) .UsingFactoryMethod<SynchronizationContext>(CreateSynchronizationContext)); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(container.Resolve<MainForm>()); } private static SynchronizationContext CreateSynchronizationContext() { if (SynchronizationContext.Current == null) { var context = new WindowsFormsSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(context);
}
return SynchronizationContext.Current;
}
}