【问题标题】:Access ServiceStack session from ConnectionFilter从 ConnectionFilter 访问 ServiceStack 会话
【发布时间】:2015-02-04 04:28:09
【问题描述】:

我正在使用 SQL Server 和数据库触发器来对系统的所有更改进行数据级审计。此审核包括发起更改的用户 ID / 名称。理想情况下,我想在我的 AppHost.Configure 方法中做这样的事情:

SqlServerDialect.Provider.UseUnicode = true;
var dbFactory = new OrmLiteConnectionFactory(ConnectionString, SqlServerDialect.Provider)
        {
            ConnectionFilter = (db =>
            {
                IAuthSession session = this.Request.GetSession();
                if (session != null && !session.UserName.IsNullOrEmpty())
                {
                    System.Data.IDbCommand cmd = db.CreateCommand();
                    cmd.CommandText = "declare @ci varbinary(128); select @ci = CAST(@Username as varbinary(128)); set context_info @ci";
                    System.Data.IDbDataParameter param = cmd.CreateParameter();
                    param.ParameterName = "Username";
                    param.DbType = System.Data.DbType.String;
                    //param.Value = session.UserName;
                    param.Value = session.UserAuthId;
                    cmd.Parameters.Add(param);
                    cmd.ExecuteNonQuery();
                }

                return new ProfiledDbConnection(db, Profiler.Current);
            }),
            AutoDisposeConnection = true
        };
        container.Register<IDbConnectionFactory>(dbFactory);

当然,这不起作用,因为this.Request 不存在。有什么方法可以从 OrmLite 连接上的 ConnectionFilter 或 ExecFilter 访问当前会话?

我开始的另一种方法是覆盖ServiceDb 属性,它不再起作用,因为我已经将一些活动抽象到它们自己的接口实现中以允许在测试期间进行模拟。这些中的每一个都传递了一个函数,该函数期望返回一个 DB 连接。示例:

// Transaction processor
container.Register<ITransactionProcessor>(new MockTransactionProcessor(() => dbFactory.OpenDbConnection()));

那么,我如何确保执行的任何 DML 都具有我的数据库审计触发器所需的(公认的特定于数据库的)上下文信息?

【问题讨论】:

    标签: servicestack ormlite-servicestack


    【解决方案1】:

    较早的multi tenant ServiceStack example 展示了如何使用请求上下文来存储每个请求的项目,例如您可以从全局请求过滤器填充请求上下文:

    GlobalRequestFilters.Add((req, res, dto) =>
    {
        var session = req.GetSession();
        if (session != null)
            RequestContext.Instance.Items.Add(
                "UserName", session.UserName);
    });
    

    并在您的连接过滤器中访问它:

    ConnectionFilter = (db =>
    {
        var userName = RequestContext.Instance.Items["UserName"] as string;
        if (!userName.IsNullOrEmpty()) {
           //...
        }
    }),
    

    【讨论】:

    • 我看到RequestContext 并引发了一些东西,但在调试时它是空的。我当然没想过要填充它!
    【解决方案2】:

    另一种方法是使用工厂模式,类似于 ServiceStack 首先创建 OrmLite 数据库连接的方式。由于所有与用户相关的调用都是通过 ServiceRunner 进行的,因此我捎带了由 ServiceStack 管理的会话。

    public class TransactionProcessorFactory : ITransactionProcessorFactory
    {
        public ITransactionProcessor CreateTransactionProcessor(IDbConnection Db)
        {
            return new TransactionProcessor(Db);
        }
    }
    
    public abstract MyBaseService : Service
    {
        private IDbConnection db;
    
        public override System.Data.IDbConnection Db
        {
            get
            {
                if (this.db != null) return db;
    
                this.db = this.TryResolve<IDbConnectionFactory>().OpenDbConnection();
    
                IAuthSession session = this.Request.GetSession();
    
                if (session != null && !session.UserName.IsNullOrEmpty())
                {
                    IDbCommand cmd = db.CreateCommand();
                    cmd.CommandText = "declare @ci varbinary(128); select @ci = CAST(@Username as varbinary(128)); set context_info @ci";
                    IDbDataParameter param = cmd.CreateParameter();
                    param.ParameterName = "Username";
                    param.DbType = DbType.String;
                    //param.Value = session.UserName;
                    param.Value = session.UserAuthId;
                    cmd.Parameters.Add(param);
                    cmd.ExecuteNonQuery();
                }
                return db;
            }
        }
    
        private ITransactionProcessor tp = null;
        public virtual ITransactionProcessor TransactionProcessor
        {
            get
            {
                if (this.tp != null) return tp;
                var factory = this.TryResolve<ITransactionProcessorFactory>();
                this.tp = factory.CreateTransactionProcessor(this.Db);
    
                return tp;
            }
        }
    }
    

    【讨论】:

    • 酷,我更喜欢这种方法:)
    • 不好。如果您需要在您的存储库中实现Db 属性怎么办——您将在您的服务开始充实的那一刻需要存储库。将整个数据库连接上下文初始化包装在自定义数据库工厂中,并使用 DI 自动连接公共 IDbConnectionFactory 属性,以便您以后可以在存储库中重用它。
    • 所以,基本上是IDbConnectionFactoryFactory?
    • @jklemmack:什么?我的意思是一个被注入的public class MySqlServerFactory : IDbConnectionFactory 实现。
    【解决方案3】:

    为了未来潜在的 ServiceStack 用户,另一种方法是使用 OrmLite 的 Global Insert/Update filters 结合 Mythz's approach above 仅在进行 DML 操作时注入必要的 SQL。它不是 100%,因为可能存在存储过程或手动 SQL,但这可能通过 IDbConnection 扩展方法来处理,以手动设置所需的审计信息。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-08-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-12-29
      • 2017-06-08
      相关资源
      最近更新 更多