NInject, nHibernate, and Auditing in ASP.NET MVC

I am working on a legacy application that uses NInject and nHibernate as part of an ASP.NET MVC (C #) application. I am currently considering a change validation issue. Each object has ChangedOn / ChangedBy and CreatedOn / CreatedBy fields that map to database columns. However, they are either filled in with the wrong username or no username at all. I think this is due to the fact that it was configured incorrectly, but I don't know enough about nHibernate and NInject to solve the problem, so I hope someone can help. Below are some snippets of code that I hope will provide a good idea of ​​the application.

Creating a factory session and session:

public class NHibernateModule : NinjectModule { public override void Load() { Bind<ISessionFactory>().ToProvider(new SessionFactoryProvider()).InSingletonScope(); Bind<ISession>().ToProvider(new SessionProvider()).InRequestScope(); Bind<INHibernateUnitOfWork>().To<NHibernateUnitOfWork>().InRequestScope(); Bind<User>().ToProvider(new UserProvider()).InRequestScope(); Bind<IStamper>().ToProvider(new StamperProvider()).InRequestScope(); } } public class SessionProvider : Provider<ISession> { protected override ISession CreateInstance(IContext context) { // Create session var sessionFactory = context.Kernel.Get<ISessionFactory>(); var session = sessionFactory.OpenSession(); session.FlushMode = FlushMode.Commit; return session; } } public class SessionFactoryProvider : Provider<ISessionFactory> { protected override ISessionFactory CreateInstance(IContext context) { var connectionString = ConfigurationManager.ConnectionStrings["DefaultConnectionString"].ToString(); var stamper = context.Kernel.Get<IStamper>(); return NHibernateHelper.CreateSessionFactory(connectionString, stamper); } } public class StamperProvider : Provider<IStamper> { protected override IStamper CreateInstance(IContext context) { System.Security.Principal.IPrincipal user = HttpContext.Current.User; System.Security.Principal.IIdentity identity = user == null ? null : user.Identity; string name = identity == null ? "Unknown" : identity.Name; return new Stamper(name); } } public class UserProvider : Provider<User> { protected override UserCreateInstance(IContext context) { var userRepos = context.Kernel.Get<IUserRepository>(); System.Security.Principal.IPrincipal user = HttpContext.Current.User; System.Security.Principal.IIdentity identity = user == null ? null : user.Identity; string name = identity == null ? "" : identity.Name; var user = userRepos.GetByName(name); return user; } } 

Factory session setup:

 public static ISessionFactory CreateSessionFactory(string connectionString, IStamper stamper) { // Info: http://wiki.fluentnhibernate.org/Fluent_configuration return Fluently.Configure() .Database(MsSqlConfiguration.MsSql2008 .ConnectionString(connectionString)) .Mappings(m => { m.FluentMappings .Conventions.Add(PrimaryKey.Name.Is(x => "Id")) .AddFromAssemblyOf<NHibernateHelper>(); m.HbmMappings.AddFromAssemblyOf<NHibernateHelper>(); }) // Register .ExposeConfiguration(c => { c.EventListeners.PreInsertEventListeners = new IPreInsertEventListener[] { new EventListener(stamper) }; c.EventListeners.PreUpdateEventListeners = new IPreUpdateEventListener[] { new EventListener(stamper) }; }) .BuildSessionFactory(); } 

Fragment from the list of events:

 public bool OnPreInsert(PreInsertEvent e) { _stamper.Insert(e.Entity as IStampedEntity, e.State, e.Persister); return false; } 

As you can see, the factory session is in one area. Therefore, eventlistener and stamper also get an instance in this area (I think). And this means that when the user has not logged in yet, the user name in the template is set to an empty string or "Unknown." I tried to compensate for this problem by modifying Stamper. It checks if the username is empty or empty. If this is true, he tries to find the active user and fill in the username with this username:

  private string GetUserName() { if (string.IsNullOrWhiteSpace(_userName)) { var user = ServiceLocator.Resolve<User>(); if (user != null) { _userName = user.UserName; } } return _userName; } 

But this leads to a completely different username, which also enters the application, registering in the database. I assume this is because it allows the wrong active user, which is the last user to log in, instead of the user who started the transaction.

+1
source share
2 answers

Aaronaught, you are analyzing exactly what I suspected. However, I found that there is a fourth solution that is simpler and more straightforward IMHO. I changed the sessionprovider so that the OpenSession call takes an instance of IInterceptor as an argument. As it turned out, event listeners should not really be used for auditing (a bit of ranting, but other than that he is right, according to Fabio as well ).

AuditInterceptor implements OnFlushDirty (for auditing existing objects) and OnSave (for auditing newly created objects). SessionProvider as follows:

 public class SessionProvider : Provider<ISession> { protected override ISession CreateInstance(IContext context) { // Create session System.Security.Principal.IPrincipal user = HttpContext.Current.User; System.Security.Principal.IIdentity identity = user == null ? null : user.Identity; string name = identity == null ? "" : identity.Name; var sessionFactory = context.Kernel.Get<ISessionFactory>(); var session = sessionFactory.OpenSession(new AuditInterceptor(name)); session.FlushMode = FlushMode.Commit; return session; } } 
+1
source

Offensive parts here:

 Bind<ISessionFactory>(). .ToProvider(new SessionFactoryProvider()) .InSingletonScope(); Bind<IStamper>() .ToProvider(new StamperProvider()) .InRequestScope(); 

And later:

 public class SessionFactoryProvider : Provider<ISessionFactory> { protected override ISessionFactory CreateInstance(IContext context) { // Unimportant lines omitted var stamper = context.Kernel.Get<IStamper>(); return NHibernateHelper.CreateSessionFactory(connectionString, stamper); } } public class StamperProvider : Provider<IStamper> { protected override IStamper CreateInstance(IContext context) { // Unimportant lines omitted string name = /* whatever */ return new Stamper(name); } } 

Analyze what happens with the code:

  • ISessionFactory bound as a single instance. There will be only ever one lifelong process. This is pretty typical.

  • ISessionFactory initialized with a SessionFactoryProvider , which immediately exits to get an IStamper instance and passes it as a constant argument to initialize the factory session.

  • In turn, IStamper initialized by StamperProvider , which initializes the Stamper class with the name constant set to current user principle / personality.

The end result of this is that as long as the process is alive, each individual β€œstamp” will be given the name of the first person to enter the system. It may even be an anonymous user who explains why you see so many blank entries.

The one who wrote this got only half the equation. IStamper tied to the request area, but it comes in singleton mode, which means that only one IStamper will be created. You are fortunate that Stamper does not contain any resources or does not have finalizers, otherwise you will probably end up with a lot of ObjectDisposedException and other strange errors.

There are three possible solutions:

  • (recommended). Rewrite the Stamper class to find the current user on each call instead of initializing with static user information. After that, the Stamper class Stamper no longer accept constructor arguments. You can bind IStamper InSingletonScope instead of InRequestScope .

  • Create an abstract IStamperFactory using the GetStamper method and a specific StamperFactory that implements it by wrapping an IKernel instance. Link them together InSingletonScope . Make your concrete factory return kernel.Get<IStamper>() . Modify the factory session to accept and hold IStamperFactory instead of IStamper . Each time you need to stamp, use factory to get a new instance of IStamper .

  • Change ISessionFactory as InRequestScope . Not recommended because it can hurt performance and potentially confuse identifier generators if you are not using identifiers created using DB, but this will solve your audit problem.

+4
source

Source: https://habr.com/ru/post/1389236/


All Articles