Asp.net mvc ASP.NET MVC中的NInject、nHibernate和审核

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

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

创建会话工厂和会话:

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

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

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

        return _userName;
    }

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

有问题的部分如下:

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初始化的,SessionFactoryProvider立即出去获取IStamper的实例,并将其作为常量参数传递以初始化会话工厂

IStamper依次由StamperProvider初始化,StamperProvider使用设置为当前用户主体/标识的常量名称初始化Stamper类

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

写这篇文章的人只得到了一半的正确答案。IStamper绑定到请求范围,但它被提供给一个单例,这意味着只会创建一个IStamper。幸运的是,Stamper没有任何资源或终结器,否则可能会导致大量ObjectDisposedException和其他奇怪的错误

有三种可能的解决方案:

建议-重写Stamper类以在每次调用时查找当前用户,而不是使用静态用户信息初始化。之后,Stamper类将不再接受任何构造函数参数。您可以在SingletonScope而不是InRequestScope中绑定IStamper

使用GetStamper方法创建一个抽象的IStamperFactory,并创建一个具体的StamperFactory,该StamperFactory通过包装IKernel实例来实现它。在单体示波器中将这些连接在一起。让您的混凝土工厂返回kernel.Get。修改会话工厂以接受并保留IStamperFactory而不是IStamper。每次需要盖章时,使用工厂获取一个新的IStamper实例

将ISessionFactory更改为在RequestScope中。不建议这样做,因为如果您不使用DB生成的标识,它将影响性能并可能会弄乱ID生成器,但它将解决您的审核问题


有问题的部分如下:

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初始化的,SessionFactoryProvider立即出去获取IStamper的实例,并将其作为常量参数传递以初始化会话工厂

IStamper依次由StamperProvider初始化,StamperProvider使用设置为当前用户主体/标识的常量名称初始化Stamper类

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

写这篇文章的人只得到了一半的正确答案。IStamper绑定到请求范围,但它被提供给一个单例,这意味着只会创建一个IStamper。幸运的是,Stamper没有任何资源或终结器,否则可能会导致大量ObjectDisposedException和其他奇怪的错误

有三个po 可能的解决办法:

建议-重写Stamper类以在每次调用时查找当前用户,而不是使用静态用户信息初始化。之后,Stamper类将不再接受任何构造函数参数。您可以在SingletonScope而不是InRequestScope中绑定IStamper

使用GetStamper方法创建一个抽象的IStamperFactory,并创建一个具体的StamperFactory,该StamperFactory通过包装IKernel实例来实现它。在单体示波器中将这些连接在一起。让您的混凝土工厂返回kernel.Get。修改会话工厂以接受并保留IStamperFactory而不是IStamper。每次需要盖章时,使用工厂获取一个新的IStamper实例

将ISessionFactory更改为在RequestScope中。不建议这样做,因为如果您不使用DB生成的标识,它将影响性能并可能会弄乱ID生成器,但它将解决您的审核问题


Aaronaught,你的分析准确地描述了我的怀疑。然而,我发现还有第四种解决方案,它更简单、更直接。 我修改了sessionprovider,这样对OpenSession的调用将IIInterceptor的实例作为参数。事实证明,事件侦听器实际上不应该用于审计

AuditionInterceptor实现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;
    }
}

Aaronaught,你的分析准确地描述了我的怀疑。然而,我发现还有第四种解决方案,它更简单、更直接。 我修改了sessionprovider,这样对OpenSession的调用将IIInterceptor的实例作为参数。事实证明,事件侦听器实际上不应该用于审计

AuditionInterceptor实现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;
    }
}

UserProvider和StamperProvider中的代码是什么样子的?SessionFactory被配置为单例,因为创建SessionFactory的成本相对较高,而且您实际上只需要一个。ISession是一个NHibernate会话,它实际上与数据库对话,它是为每个RequestScope创建的,这意味着您可以为每个web请求获得一个新会话。我想知道UserProvider如何获取当前用户,StamperProvider如何将其附加到实体。Nathan,如果您使用session和session factory在代码块中向下滚动一点,您也可以看到这两个提供程序。我认为这很重要,但是代码块有点大,所以so的样式表用滚动条隐藏溢出。UserProvider和StamperProvider中的代码是什么样子的?SessionFactory被配置为单例,因为创建SessionFactory的成本相对较高,而且您实际上只需要一个。ISession是一个NHibernate会话,它实际上与数据库对话,它是为每个RequestScope创建的,这意味着您可以为每个web请求获得一个新会话。我想知道UserProvider如何获取当前用户,StamperProvider如何将其附加到实体。Nathan,如果您使用session和session factory在代码块中向下滚动一点,您也可以看到这两个提供程序。我认为这很重要,但是代码块有点大,所以so的样式表用滚动条隐藏溢出。这是一个非常糟糕的做法。依赖项不应该知道拦截器或任何其他DI/AOP管道的存在。如果您想采用这种方法,请使用Ninject.Extensions.Interception库(最好是Castle实现),并将拦截器添加到ISession绑定中,这可能是拦截器实际连接的位置。这是一种非常糟糕的做法。依赖项不应该知道拦截器或任何其他DI/AOP管道的存在。如果您想采用这种方法,请使用Ninject.Extensions.Interception库(最好是Castle实现),并向ISession绑定添加一个拦截器,这可能是拦截器实际连接的地方。您能帮我解决这个问题吗?在这个问题上你能帮我吗?