I'm hosting my .NET 4.5 WCF services in IIS. There's a piece of information called "BusinessContext" (BC) that is stored in the OperationContext.Current instance, so that any logic downstream can reach it.
Everything worked fine until I introduced async/await, and I ran into this issue. @stephen-cleary mentioned ASP.NET uses the async-friendly AspNetSynchronizationContext to keep the HttpContext.Current across threads. Since I'm hosting in IIS I figured I should be able to take advantage of the AspNetSyncCtx in WCF, and use the HttpContext.Current instead of the OperationContext to store the BC.
I created a WCF service from scratch, which has targetFramework = 4.5, aspnet:UseTaskFriendlySynchronizationContext = true and aspNetCompatibilityEnabled = true set by default in the Web.config. I also added the AspNetCompatibilityRequirements = Required to my service.
At runtime I see the HttpContext.Current is there, but SynchronizationContext.Current is null. After an await the HttpContext becomes null, which is expected because there's no SyncCtx. Shouldn't it be set to AspNetSyncCtx when aspcompatibility is required? How does the AspNetSyncCtx get set in ASP.NET?
-- Possible solution.
Following @Stephen-cleary's here I went ahead and defined a custom SynchronizationContext to preserve the OperationContext across threads.
I'd like to hear the community's input regarding this implementation. Thanks.
public class OperationContextSynchronizationContext : SynchronizationContext
{
public override void Post(SendOrPostCallback d, object state)
{
OperationContext opCtx = OperationContext.Current;
InternalState internalState = new InternalState()
{
OpCtx = opCtx,
Callback = d,
State = state,
SyncCtx = this
};
ThreadPool.QueueUserWorkItem(new WaitCallback(InternalInvoker), internalState);
}
private void InternalInvoker(object internalState)
{
InternalState internalSt = internalState as InternalState;
SynchronizationContext.SetSynchronizationContext(internalSt.SyncCtx);
using (new OperationContextScope(internalSt.OpCtx))
{
internalSt.Callback.Invoke(internalSt.State);
}
}
private class InternalState
{
public SynchronizationContext SyncCtx { get; set; }
public OperationContext OpCtx { get; set; }
public SendOrPostCallback Callback { get; set; }
public object State { get; set; }
}
}
I ran into the issue (bug?) you mentioned where
HttpContext.Current
did not flow withasync
code in a WCF service hosted in IIS even withaspNetCompatiblity
enabled and required.I got my project working using the
LogicalCallContext
withCallContext.LogicalSetData
andCallContext.LogicalGetData
as described by Stephen Cleary in this blog post. I think setting/getting your business context thusly would work very well in your case and might be lighter weight and conceptually simpler than the customSynchronizationContext
. In your case you are having to Set your business context somewhere anyway ... might as well set it to theLogicalCallContext
and I believe everything will be fine.I'll explain my personal 'solution' in more detail, though I admit it is a bit hacky since I'm manually flowing the entire
HttpContext
object (and only in WCF methods). But again your case with your custom context object would seem to be a better fit.In the ASP.NET web app I inherited, there were calls to
HttpContext.Current
littered throughout the business logic (not ideal). Obviously the project worked fine until I wanted several of the WCF methods in the app to beasync
.A lot of the calls in the business logic would be from ASP.NET page loads, etc, where everything functions fine as it is.
I solved (kludged?) my problem in .NET 4.5 by creating a little helper class
ContextHelper
, and replaced all calls toHttpContext.Current
withContextHelper.CurrentHttpContext
.Now anytime the
HttpContext.Current
is defined, my helper method is essentially a no-op.To make this work, the entry points for my WCF calls must call the
PrepareHttpContextFlow
method before the first call toawait
which is admittedly problematic and the main reason I consider this a kludge.In my case this downside is mitigated by the fact that I have some OTHER manual calls required to add logical stack information for added error logging context that is lost when using
async
(since the physical stack information in an exception is not very useful for error logs in this case). So at least if I remember to do one of the "prepare for async" calls, I should remember to do the other :)