Reputation: 7028
I am pretty new with DI and IoC pattern.
public class LazySessionContext
{
private readonly ISessionFactoryImplementor factory;
private const string CurrentSessionContextKey = "NHibernateCurrentSession";
public LazySessionContext(ISessionFactoryImplementor factory)
{
this.factory = factory;
}
/// <summary>
/// Retrieve the current session for the session factory.
/// </summary>
/// <returns></returns>
public ISession CurrentSession()
{
Lazy<ISession> initializer;
var currentSessionFactoryMap = GetCurrentFactoryMap();
if (currentSessionFactoryMap == null ||
!currentSessionFactoryMap.TryGetValue(factory, out initializer))
{
return null;
}
return initializer.Value;
}
/// <summary>
/// Bind a new sessionInitializer to the context of the sessionFactory.
/// </summary>
/// <param name="sessionInitializer"></param>
/// <param name="sessionFactory"></param>
public static void Bind(Lazy<ISession> sessionInitializer, ISessionFactory sessionFactory)
{
var map = GetCurrentFactoryMap();
map[sessionFactory] = sessionInitializer;
}
/// <summary>
/// Unbind the current session of the session factory.
/// </summary>
/// <param name="sessionFactory"></param>
/// <returns></returns>
public static ISession UnBind(ISessionFactory sessionFactory)
{
var map = GetCurrentFactoryMap();
var sessionInitializer = map[sessionFactory];
map[sessionFactory] = null;
if (sessionInitializer == null || !sessionInitializer.IsValueCreated) return null;
return sessionInitializer.Value;
}
/// <summary>
/// Provides the CurrentMap of SessionFactories.
/// If there is no map create/store and return a new one.
/// </summary>
/// <returns></returns>
private static IDictionary<ISessionFactory, Lazy<ISession>> GetCurrentFactoryMap()
{
var currentFactoryMap = (IDictionary<ISessionFactory, Lazy<ISession>>)
HttpContext.Current.Items[CurrentSessionContextKey];
if (currentFactoryMap == null)
{
currentFactoryMap = new Dictionary<ISessionFactory, Lazy<ISession>>();
HttpContext.Current.Items[CurrentSessionContextKey] = currentFactoryMap;
}
return currentFactoryMap;
}
}
public interface ISessionFactoryProvider
{
IEnumerable<ISessionFactory> GetSessionFactories();
}
public class SessionFactoryProvider
{
public const string Key = "NHibernateSessionFactoryProvider";
}
public class NHibernateSessionModule : IHttpModule
{
private HttpApplication app;
public void Init(HttpApplication context)
{
app = context;
context.BeginRequest += ContextBeginRequest;
context.EndRequest += ContextEndRequest;
context.Error += ContextError;
}
private void ContextBeginRequest(object sender, EventArgs e)
{
var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
foreach (var sf in sfp.GetSessionFactories())
{
var localFactory = sf;
LazySessionContext.Bind(
new Lazy<ISession>(() => BeginSession(localFactory)),
sf);
}
}
private static ISession BeginSession(ISessionFactory sf)
{
var session = sf.OpenSession();
session.BeginTransaction();
return session;
}
private void ContextEndRequest(object sender, EventArgs e)
{
var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
var sessionsToEnd = sfp.GetSessionFactories()
.Select(LazySessionContext.UnBind)
.Where(session => session != null);
foreach (var session in sessionsToEnd)
{
EndSession(session);
}
}
private void ContextError(object sender, EventArgs e)
{
var sfp = (ISessionFactoryProvider)app.Context.Application[SessionFactoryProvider.Key];
var sessionstoAbort = sfp.GetSessionFactories()
.Select(LazySessionContext.UnBind)
.Where(session => session != null);
foreach (var session in sessionstoAbort)
{
EndSession(session, true);
}
}
private static void EndSession(ISession session, bool abort = false)
{
if (session.Transaction != null && session.Transaction.IsActive)
{
if (abort)
{
session.Transaction.Rollback();
}
else
{
session.Transaction.Commit();
}
}
session.Dispose();
}
public void Dispose()
{
app.BeginRequest -= ContextBeginRequest;
app.EndRequest -= ContextEndRequest;
app.Error -= ContextError;
}
}
I got this sample by chinooknugets and jfmarillo from GitHub. From the code above I am injecting the session into repository and controlling the transaction via IHttpmodule. Now there are two concerns for me:
If I implement the transaction management via the code above it would be called whenever there is a request and it would open a session for that request. That's the sole purpose of implementing "Session Per Request" approach. But what if among all my controller methods I have only one method which actually uses the repository then I don't want to open the session every time I make a request. Only during the actions which are marked by Transaction attribute in controller would handle the transaction.
I would make a request and session would be opened only when the repository requests for it, so can it be implemented via any IoC container.
I still want the httpcontext events to handle the transaction so that context+=BegingRequest and context+=EndRequest. and the transaction would be handled inside that httpmodule with the httpcontext on request. But I don't want to implement IhttpModule and put into web.config. Is there any other alternative for this approach ?
The session opening and closing would be done solely inside those httpcontext only however I want to manage it via an IoC container (preferably ninject) but only when the repository is being requesting for that session. Mind it the repository may be initialised when the controller is invoked but that shouldn't open the session inside that repository. The session should open when actually repository is performing any transient actions.
Would someone clarify what practise should I follow for this scenario? I am using Mvc 3 with ninject and nhibernate.
Upvotes: 1
Views: 1503
Reputation: 32725
1, 2) Ninject allows Lazy object creation using Ninject.Extensions.Factory 3.0.0
class MyController
{
public MyController(Lazy<SomeRepository> repository) { ... }
}
This way the repository (and the context) is created when used.
3) Why do you want to use a HttpModule for this? There are much easier ways e.g.:
kernel.Bind<ISession>()
.ToMethod(ctx => ctx.Kernel.Get<ISessionFactory().OpenSession())
.InRequestScope()
.OnActivation(session => OpenTransaction(session))
.OnDeactivation(session => EndTransaction(session));
Starting from Ninject 3.0.0 you can add a binding for HttpModules instead of registering them in the web.config and use construcotr injection for them. But since the HttpModule has no knowledge if the context is used you have to open the transaction for all requests.
Upvotes: 5