以线程安全的方式获取服务之外的当前用户会话

时间:2014-01-06 12:04:11

标签: c# servicestack nhibernate-envers

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

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

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

1 个答案:

答案 0 :(得分: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中,在身份验证点设置Username的值,这将在侦听器的处理程序之前发生。然后在侦听器处理程序中读取Global.Username的值。该值是线程安全的,仅存在于请求范围内。

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