Obtain current user session outside of the service in a thread safe manner

544 views Asked by At

My ServiceStack-based app uses built-in Authentication feature and runs in SelfHosted mode (AppHostHttpListenerLongRunningBase).

I'm using NHibernate with Envers for audit trailing. Envers could be provided with optional singleton instance which fills additional revision info. I'd like to store current user's auth name in the revision info.

I need to access current request (i.e. "current user session") outside of the Services code, in my singleton instance. How do I do this with ServiceStack? How to make it thread safe?

1

There are 1 answers

4
Scott On BEST ANSWER

I don't use NHibernate or Envers myself, so perhaps just spitballing here. I don't think a current user session really exists outside the scope of the service. But you should be able to pass the Envers scope in. I think what you would want to do, is pass a reference to your singleton instance into service using the AppHost IoC.

In your AppHost setup your singleton instance, then register it with the container so it is injected into each Service request.

By doing something like:

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

You would need to extend Service to use a custom base:

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

Then your handlers would need to use this extended custom Service base, so something like this: CustomerHandler is just an example, your service handlers will vary

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.
    }
}

You could have the values auto-populated, to save having to set the values in each action handler by setting up a custom ServiceRunner.

Create a custom 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);
    }
}

Configure you application to use it by adding this to your AppHost:

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

Sorry parts are a little vague with regards to the Enver's singleton object type and the correct properties or methods to call on the object to set the data, but I assume you can substitute in the appropriate values.

As I said I am not familiar with either NHibernate or Envers, so this could be off, but it may be of some help at least regarding the ServiceStack side


Updated attempt:

I presume that for each request to your service the following happens:

  • The application host creates a Service context thread for the request.
  • Within the thread the user session exists
  • Each request will perform some NHibernate database action.
  • The database action may trigger an event handler in your envers listener. You need to populate with the username of the current request, i.e. in the scope of the current thread.

Have you considered creating a ThreadStatic static variable that is global to both ServiceStack and your Envers event handler in the listener?

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

Then in ServiceStack set the value of Username at the point of Authentication, which will occur before your listener's handler. Then in the listener handler read the value from Global.Username. The value is thread safe and only exists in the scope of the request.

Note: I assume that NHibernate/Envers runs on the request thread, and that it doesn't spawn other worker threads for each request.