I was reading this article and found it quite interesting (thanks @Aaronaught). Was what came closest to solve my problem.
The only detail is that in my case I would use the NHibernate interceptor, but an exception is thrown An unhandled exception of type 'System.StackOverflowException' occurred in System.Core.dll
Code
Session factory:
public class SessionFactoryBuilder : IProvider
{
private ISessionFactory _sessionFactory;
private readonly Configuration _configuration;
public SessionFactoryBuilder(AuditInterceptor auditInterceptor)
{
_configuration = Fluently.Configure(new Configuration().Configure())
.Mappings(m => m.AutoMappings.Add(AutoMap.AssemblyOf<IEntidade>(new AutomappingConfiguration())))
.ExposeConfiguration(SetupDatabase)
.BuildConfiguration();
_configuration.SetInterceptor(auditInterceptor);
_sessionFactory = _configuration.BuildSessionFactory();
}
private static void SetupDatabase(Configuration config)
{
var schema = new SchemaExport(config);
//schema.Execute(true, true, false);
}
public object Create(IContext context)
{
return _sessionFactory;
}
public Type Type
{
get { return typeof(ISessionFactory); }
}
}
I have a module that sets up my repositories and ORM (NHibernate)
public class RepositoriosModule : NinjectModule
{
public override void Load()
{
Bind<AuditInterceptor>().ToSelf().InRequestScope();
// NHibernate
Bind<ISessionFactory>().ToProvider<SessionFactoryBuilder>().InSingletonScope();
Bind<ISession>().ToMethod(CreateSession).InRequestScope();
Bind<NHUnitOfWork>().ToSelf().InRequestScope();
//Model Repositories
Bind<IRepositorio<Usuario>, IUsuariosRepositorio>().To<UsuariosRepositorio>().InRequestScope();
}
private ISession CreateSession(IContext context)
{
return context.Kernel.Get<ISessionFactory>().OpenSession();
}
}
Interceptor to update auditable properties (CriadoEm
(create at), CriadoPor
(create by), AtualizadoEm
and AtualizadoPor
)
public class AuditInterceptor : EmptyInterceptor
{
private readonly IUsuario _usuarioLogado;
public AuditInterceptor(IUsuario usuarioLogado)
{
_usuarioLogado = usuarioLogado;
}
public override bool OnFlushDirty(object entity, object id, object[] currentState, object[] previousState, string[] propertyNames, NHibernate.Type.IType[] types)
{
var auditableObject = entity as IAuditavel;
if (auditableObject != null)
{
currentState[Array.IndexOf(propertyNames, "AtualizadoEm")] = DateTime.Now;
return true;
}
return false;
}
public override bool OnSave(object entity, object id, object[] state, string[] propertyNames, NHibernate.Type.IType[] types)
{
var auditableObject = entity as IAuditavel;
if (auditableObject != null)
{
var currentDate = DateTime.Now;
state[Array.IndexOf(propertyNames, "CriadoEm")] = currentDate;
return true;
}
return false;
}
}
A provider to retrieve the logged in user:
public class UsuarioProvider : Provider { private Usuario _usuario;
protected override Usuario CreateInstance(IContext context)
{
var usuariosRepositorio = context.Kernel.Get<IUsuariosRepositorio>(); // Stackoverflow on this line!!
if (_usuario == null && WebSecurity.IsAuthenticated)
_usuario = usuariosRepositorio.Get(WebSecurity.CurrentUserId);
return _usuario;
}
}
And the class NinjectWebCommon
(web application) define:
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IUsuario>().ToProvider<UsuarioProvider>().InRequestScope(); //.When((req) => WebSecurity.IsAuthenticated)
kernel.Load(new RepositoriosModule(), new MvcSiteMapProviderModule());
}
[Add] Repository class
public class UsuariosRepositorio : Repositorio<Usuario>, IUsuariosRepositorio
{
public UsuariosRepositorio(NHUnitOfWork unitOfWork)
: base(unitOfWork)
{ }
}
public class Repositorio<T> : IRepositorio<T>
where T : class, IEntidade
{
private readonly NHUnitOfWork _unitOfWork;
public IUnitOfWork UnitOfWork { get { return _unitOfWork; } }
private readonly ISession _session;
public Repositorio(IUnitOfWork unitOfWork)
{
_unitOfWork = (NHUnitOfWork)unitOfWork;
_session = _unitOfWork.Context.SessionFactory.GetCurrentSession();
}
public void Remover(T obj)
{
_session.Delete(obj);
}
public void Armazenar(T obj)
{
_session.SaveOrUpdate(obj);
}
public IQueryable<T> All()
{
return _session.Query<T>();
}
public object Get(Type entity, int id)
{
return _session.Get(entity, id);
}
public T Get(Expression<Func<T, bool>> expression)
{
return Query(expression).SingleOrDefault();
}
public T Get(int id)
{
return _session.Get<T>(id);
}
public IQueryable<T> Query(Expression<Func<T, bool>> expression)
{
return All().Where(expression);
}
}
Problem
The problem occurs in the class UsuarioProvider
while trying to retrieve the user repository.
Stackoverflow error:
An unhandled exception of type 'System.StackOverflowException' occurred in System.Core.dll
I see two problems :
The main problem I see is that
SessionFactoryBuilder
needs anAuditInterceptor
which needs anIUsuario
, which needs aUsuarioProvider
, which needs aSessionFactoryBuilder
, thus introducing a cycle, and a stack-overflow.The second problem I see is that your
AuditInterceptor
is linked to a request when yourSessionFactoryBuilder
is singleton like. I must confess I can't see how it work with several logged users.You should instantiate and attach the
AuditInterceptor
as part of theCreateSession
, instead of trying to create it once and for all as part of the Session builder. Once this is done, your interceptor should not rely on a Session that needs an AuditInterceptor as part of its creation (you may need a separate Session creation mechanism for that. A stateless Session might do the trick)