1

我正在开发一个继承的应用程序,它使用 NInject 和 nHibernate 作为 ASP.NET MVC (C#) 应用程序的一部分。目前,我正在研究修改审计的问题。每个实体都有 ChangedOn/ChangedBy 和 CreatedOn/CreatedBy 字段,它们映射到数据库列。但是,这些要么填写错误的用户名,要么根本没有用户名。我认为这是因为它的配置方式错误,但是我对nHibernate和NInject的了解还不够,无法解决问题,所以希望有人能提供帮助。下面是一些代码片段,希望能在应用程序中提供足够的洞察力。

创建会话工厂和会话:

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;
    }
}

配置会话工厂:

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();
     }

来自事件监听器的片段:

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

如您所见,会话工厂位于单例范围内。因此 eventlistener 和 stamper 也在这个范围内被实例化(我认为)。这意味着当用户尚未登录时,压模中的用户名设置为空字符串或“未知”。我试图通过修改 Stamper 来弥补这个问题。它检查用户名是空还是空。如果这是真的,它会尝试找到活动用户,并用该用户的名称填充用户名属性:

    private string GetUserName()
    {
        if (string.IsNullOrWhiteSpace(_userName))
        {
            var user = ServiceLocator.Resolve<User>();

            if (user != null)
            {
                _userName = user.UserName;
            }
        }

        return _userName;
    }

但这会导致一个完全不同的用户名,它也登录到应用程序,登录到数据库中。我猜这是因为它解决了错误的活动用户,即最后一个登录的用户,而不是启动交易的用户。

4

2 回答 2

4

有问题的部分在这里:

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

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

后来:

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);
    }
}

让我们分析一下代码发生了什么:

  • ISessionFactory绑定为单实例。在整个过程的生命周期中只会有一个。这是相当典型的。

  • ISessionFactory初始化SessionFactoryProvider,立即出去以获取 的实例IStamper,并将其作为常量参数传递以初始化会话工厂。

  • IStamper依次由初始化StamperProvider一个具有设置为当前用户主体/身份的常量Stamper的类。 name

这样做的最终结果是,只要进程处于活动状态,每个“戳记”都将分配给第一个登录的用户的名称。这甚至可能是匿名用户,这就解释了为什么你会看到这样许多空白条目。

写这个的人只猜对了一半。IStamper绑定到请求范围,但它被提供单例,这意味着只会IStamper创建一个。你很幸运,Stamper它没有任何资源或任何终结器,否则你最终可能会遇到很多ObjectDisposedException其他奇怪的错误。

对此有三种可能的解决方案:

  1. (推荐) - 重写类以在每次调用Stamper时查找当前用户,而不是使用静态用户信息进行初始化。之后,该类将不再采用任何构造函数参数。您可以绑定而不是。StamperIStamper InSingletonScopeInRequestScope

  2. 创建一个IStamperFactory带有GetStamper方法的抽象,以及一个通过包装实例StamperFactory来实现它的具体。IKernel将这些绑定在一起InSingletonScope。有你的混凝土工厂return kernel.Get<IStamper>()。修改会话工厂以接受并持有一个IStamperFactory 而不是一个IStamper. 每次需要打戳时,使用工厂获取一个新的IStamper实例。

  3. 更改ISessionFactoryInRequestScope. 不推荐,因为如果您不使用数据库生成的身份,它会损害性能并可能会弄乱 ID 生成器,但它解决您的审计问题。

于 2011-08-19T22:35:34.127 回答
1

Aaronaught,你的分析完全描述了我的怀疑。但是,我发现有第四种解决方案更容易和更直接的恕我直言。我修改了 sessionprovider,以便调用将OpenSessionIInterceptor 的实例作为参数。事实证明,事件监听器实际上不应该用于审计(有点咆哮,但除此之外他是对的,根据 Fabio 的说法)。

AuditInterceptor工具(OnFlushDirty用于审计现有实体)和OnSave(用于审计新创建的实体)。SessionProvider外观如下:

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;
    }
}
于 2011-08-22T07:09:24.740 回答