【问题标题】:Obtain current user session outside of the service in a thread safe manner以线程安全的方式获取服务外的当前用户会话
【发布时间】:2014-01-06 12:04:11
【问题描述】:

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

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

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

【问题讨论】:

  • 你有ThreadStatic的工作方式吗?
  • @Scott 很抱歉这么久没有回答。我修改了一些你的方法,所以我最终得到了类似于自定义 HttpContext 的东西。它被保存在由 ServiceRunner 设置的 ThreadStatic 字段中。它似乎有效,我必须进行一些处理并行连接的测试。我希望不会像一个请求被不同的线程处理这样的意外......
  • 很高兴你设法让某些东西发挥作用,我希望正如你所说的那样没有任何意外。祝你好运。 :)

标签: c# servicestack nhibernate-envers


【解决方案1】:

我自己不使用 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 侦听器中的事件处理程序。您需要填充当前请求的用户名,即在当前线程的范围内。

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

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

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

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

【讨论】:

  • 嗨,谢谢你的想法。我正在考虑以某种方式访问​​实例并“手动”为 envers 侦听器设置当前用户(我认为您的代码“手动”方式,无论它多么自动化;))。但是我非常害怕它的线程安全性。当同时处理两个请求时会发生什么?恐怕这需要我为整个请求处理锁定“envers-listener”单例实例。这听起来不是个好主意……
  • @migajek 我可以看到你的担忧,你能发布你的单身代码,你会如何设置它?所以我知道您是在告诉单身用户是谁,它如何知道他们在请求期间更改了哪些记录?如果我能理解我认为解决方案是构建一个队列,它会使用已完成的请求更新单例,并且因为它是后台工作人员,所以它不会阻塞正常操作。
  • Envers 是审计跟踪库,因此它负责跟踪我的实体中的更改。我的单身人士是负责为修订分配额外数据的 envers 侦听器 - 在我的情况下,我想分配当前用户名。因此,每次 Envers 调用单例方法时,我都需要知道当前用户的名称。如果任何实体发生更改,Envers 将创建修订版并在我手动创建的同一事务中询问其详细信息,因此 - 它将在请求处理结束之前被调用。它不会以任何方式排队,所以至少执行顺序是确定的
  • @migajek 我已经更新了我的答案。我认为也许使用[ThreadStatic] 使请求线程可以访问Username 可能会起作用。请告诉我你的进展情况。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-11-28
  • 2016-05-06
  • 2017-01-28
  • 2014-10-16
  • 1970-01-01
  • 2017-01-18
  • 2014-12-21
相关资源
最近更新 更多