1

我的基于 ServiceStack 的应用程序使用内置的身份验证功能并在 SelfHosted 模式 ( AppHostHttpListenerLongRunningBase) 下运行。

我正在使用 NHibernate 和 Envers 进行审计跟踪。可以为 Envers 提供可选的单例实例,该实例填充额外的修订信息。我想将当前用户的身份验证名称存储在修订信息中。

我需要在我的单例实例中访问服务代码之外当前请求(即“当前用户会话”) 。如何使用 ServiceStack 执行此操作?如何使其线程安全?

4

1 回答 1

2

我自己不使用 NHibernate 或 Envers,所以也许只是在这里吐口水。我不认为当前用户会话真的存在于服务范围之外。但是您应该能够传递 Envers 范围。我认为您想要做的是使用AppHost IoC 将对您的单例实例的引用传递给服务。

在您的AppHost设置中,您的单例实例,然后将其注册到容器中,以便将其注入到每个服务请求中。

通过执行以下操作:

container.Register(c => singletonInstance).ReusedWithin(ReuseScope.None);

您需要扩展Service以使用自定义基础:

public class MyServiceBaseWithEnversSupport : Service
{
    public EnversSingletonInstanceType Envers { get; set; } // IoC will inject here
}

然后你的处理程序需要使用这个扩展的自定义Service基础,所以像这样:CustomerHandler只是一个例子,你的服务处理程序会有所不同

public class CustomerHandler : MyServiceBaseWithEnversSupport
{
    public object Get(ListCustomers request)
    {
        // You can then access the instance in the scope of the request
        // So you now have access to the current user identity
        Envers.Username = Session.Username; // Just an example modify as required.
    }
}

您可以自动填充这些值,通过设置一个自定义的ServiceRunner.

创建自定义ServiceRunner

public class ServiceRunner<T> : ServiceStack.ServiceHost.ServiceRunner<T>
{
    public ServiceRunner(IAppHost appHost, ActionContext actionContext) : base(appHost, actionContext)
    {
    }

    public override object Execute(IRequestContext requestContext, object instance, T request)
    {
        // Check if the instance is of type MyServiceBaseWithEnversSupport
        var ms = instance as MyServiceBaseWithEnversSupport;

        // If the request is not using the MyServiceBaseWithEnversSupport, then allow it to run, as normal.
        if(ms == null)
            return base.Execute(requestContext, instance, request);

        // Access the Envers object, set using the Session Information
        ms.Envers.Username = ms.Session.Username;

        return base.Execute(requestContext, ms, request);
    }
}

通过将其添加到您的以下配置您的应用程序以使用它AppHost

public override IServiceRunner<TRequest> CreateServiceRunner<TRequest>(ActionContext actionContext)
{
    return new ServiceRunner<TRequest>(this, actionContext);
}

抱歉,关于 Enver 的单例对象类型以及调用对象以设置数据的正确属性或方法有些模糊,但我假设您可以替换为适当的值。

正如我所说,我对 NHibernate 或 Envers 都不熟悉,所以这可能会关闭,但至少在 ServiceStack 方面可能会有所帮助


更新的尝试:

我假设对于您的服务的每个请求都会发生以下情况:

  • 应用程序主机为请求创建一个服务上下文线程。
  • 在线程中存在用户会话
  • 每个请求都会执行一些 NHibernate 数据库操作。
  • 数据库操作可能会在您的 envers 侦听器中触发事件处理程序。您需要填充当前请求的用户名,即在当前线程的范围内。

您是否考虑过在侦听器中创建一个对 ServiceStack 和 Envers 事件处理程序都是全局的变量?ThreadStatic static

public static class Global
{
    [ThreadStatic]
    public static string Username;
}

然后在 ServiceStack 中设置UsernameAuthentication 点的值,这将发生在侦听器的处理程序之前。然后在侦听器处理程序中从Global.Username. 该值是线程安全的,并且只存在于请求的范围内。

注意:我假设 NHibernate/Envers 在请求线程上运行,并且它不会为每个请求生成其他工作线程。

于 2014-01-06T14:17:11.920 回答