从 ConnectionFilter 访问 ServiceStack 会话

Access ServiceStack session from ConnectionFilter

我正在使用 SQL 服务器和数据库触发器来保持对系统所有更改的数据级审计。此审核包括发起更改的任何人的用户 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 属性,不再有效,因为我已经将一些活动抽象成它们自己的接口实现允许在测试期间进行模拟。这些中的每一个都传递了一个函数,该函数预期 return 数据库连接。示例:

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

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

前面的 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()) {
       //...
    }
}),

另一种方法是使用工厂模式,类似于 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;
        }
    }
}

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