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?
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
I presume that for each request to your service the following happens:
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.