My Little MEF Problem
The simplest solution to my problem would be to create a MessageHandler that pulls the header information out, places into a context object and inserts that object into the HttpRequestMessage.Properties collection so that it is passed to the service method. This would look something like:
public class ClientContextMessageHandler : DelegatingHandler
{
public ClientContextMessageHandler()
: base()
{
}
protected override Task SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var language = request.Headers.AcceptLanguage.First().Value;
var context = new ClientContext()
{
Culture = new CultureInfo(language)
};
request.Properties.Add("ClientContext", context);
return base.SendAsync(request, cancellationToken);
}
}
Unfortunately, I am working with a layered architecture and we use Dependency Injection to decouple our dependencies (for testing, maintainability and extensibility). We have chosen to use MEF. This decision is part of a larger discussion but it boiled down to the fact that it is a part of the .NET Framework now, will receiving continued support from Microsoft and allows us to have dynamic composition of our API so we can easily extend the solution by simply dropping assemblies in the runtime folder.
My problem with the above solution is that I need the context information to be available throughout the application - in any layer, anywhere it's needed. I do not want to pass the object around in every method call; therefore, I need a way to inject the object at runtime while processing a request.
I'll come back to this.
WCF Threading
I won't give a dissertation on WCF threading - mostly because I'm no expert. But, suffice it to say, that WCF uses thread pooling and, as a result, a specific thread may be reused for multiple requests.Why does this matter? Because any data saved in the thread, either using the [TreadStatic] attribute or a named slot, will be persisted across multiple requests. So it is possible that data stored for one request is exposed to another. Not what we want.
However, knowing that a thread will only ever be servicing a single request at any one time should help lead us to a workable solution so long as we eliminate any risk of 'bleed through'.
Defining a Thread-Safe Context
I think I have enough information at this point to at least define my ClientContext class. Here's what I came up with:
internal sealed class ClientContext
: IClientContext
{
[ThreadStatic()]
private static ClientContext _instance;
private ClientContext() { }
public CultureInfo Culture { get; set; }
[Export(typeof(IClientContext)]
public static ClientContext Current
{
get
{
if (_instance == null)
_instance = new ClientContext();
return _instance;
}
}
}
I've essentially created a per-thread singleton that I can access through the Current property. Whenever I call ClientContext.Current, I know I will get the object for the thread I am currently executing on.
Notice that I've marked the Current property with the Export attribute. This tells MEF to use this property whenever I need to inject an instance of the IClientContext interface in my code.
Reworking the MessageHandler
Now that I have a thread-safe context, let's rework the message handler to use this class instead of instantiating a new object each time.
public class ClientContextMessageHandler : DelegatingHandler
{
public ClientContextMessageHandler()
: base()
{
}
protected override Task SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var language = request.Headers.AcceptLanguage.First().Value;
var context = ClientContext.Current;
context.Culture = new CultureInfo(language);
return base.SendAsync(request, cancellationToken);
}
}
Notice that I am no longer storing the context in the HttpRequestMessage.Properties collection. I don't need to do this because the instance is stored in the TreadStatic variable and exposed via the MEF export.
Message Handlers vs Operation Handlers
Despite all looking good, I found the solution wasn't reliable. Sometimes, when pumping a lot of requests into my service, the code would fail with a NullReferenceException. After some digging, I found that the Culture property was sometimes null in my service code even after setting it properly in the message hander. I quickly learned that the message handler occassionally ran on a different thread than my service method. Uh-oh!Fortunately I had an opportunity to discuss this problem with Glenn Block (of Prism, MEF, WCF Web API and now Node.js fame) who was in town for a speaking engagement. It turns out that it is just a matter of making sure the context is set on the same thread the request handler (the service) is executing. The solution is to use an HttpOperationHandler instead of a MessageHandler.
According to Glenn, message handlers operate asynchronously which means they could execute on a different thread from the request handler (service) so we should never do anything in a message handler that requires thread affinity. On the other hand, operation handlers run synchronously on the same thread as the request handler, therefore we can rely on thread affinity.
Moving to an Operation Handler
So, I simple converted my message handler code to an operation handler as follows:
internal class ClientContextOperationHandler
: HttpOperationHandler<HttpRequestMessage, HttpRequestMessage>
{
public ClientContextOperationHandler()
: base("request")
{
}
protected override HttpRequestMessage OnHandle(HttpRequestMessage input)
{
var context = ClientContext.Current;
var language = input.Headers.AcceptLanguage.First().Value;
context.Culture = new CultureInfo(language);
return input;
}
}
I'm not sure if I'm passing the right information to the base class constructor, but the rest of the code was a simple port from the original message handler. I'm also using the generic base class to make the code simpler.
WTF!
So I run the application, make a test call and see that my operation handler is called as expected, the context information set but the Culture property is now always null in my service method! What's worse is that I am able to verify that the operation handler and service method are, in fact, running on the same thread by checking the Thread.CurrentThread.ManagedThreadId value.Coping with MEF
The problem is in how MEF satisfies the imports. Unfortunately I don't know the details why this is the case but know how I got around it.First, I removed the Export attribute from the Context property of my ClientContext class. Then I created the following ClientContextExporter whose sole purpose is to make MEF behave the way I want.
[Export(typeof(IClientContext))]
internal sealed class ClientContextExporter
: IClientContext
{
public ClientContextExporter()
{
}
public CultureInfo Culture
{
get
{
return ClientContext.Current.Culture;
}
}
}
This type implements the same interface but delegates all members to the ClientContext.Current object. The class is marked with the Export attribute so MEF will use this class to satisfy any imports.
Wrap It Up Already!
So, that's it. I have the following types:
- ClientContext (with the Export attribute removed) is the implementation of the context and provides the container for the thread-safe instances injected by MEF,
- ClientContextOperationHandler plugs into the WCF Web API request pipeline to extract the header information and set the properties of the current ClientContext object.
- ClientContextExporter simply delegates to ClientContext.Current and is the export used by MEF to satisfy any imports.