OperationContext.Current is null after first await when using async/await in WCF service

I am using async/await pattern in .NET 4.5 to implement some service methods in WCF. Example service:

Contract:

[ServiceContract(Namespace = "http://async.test/")]
public interface IAsyncTest
{
    Task DoSomethingAsync();
}

Implementation:

MyAsyncService : IAsyncTest
{
    public async Task DoSomethingAsync()
    {
        var context = OperationContext.Current; // context is present

        await Task.Delay(10);

        context = OperationContext.Current; // context is null
    }
}

The problem I am having is that after first await OperationContext.Current returns null and I can't access OperationContext.Current.IncomingMessageHeaders.

In this simple example this is not a problem since I can capture the context before the await. But in the real world case OperationContext.Current is being accessed from deep inside the call stack and I really don't want to change lots of code just to pass the context further.

Is there a way to get operation context after await point without passing it down the stack manually?


ANSWERS:


I think your best option is to actually capture it and pass it manually. You may find this improves the testability of your code.

That said, there are a couple of other options:

  1. Add it to the LogicalCallContext.
  2. Install your own SynchronizationContext which will set OperationContext.Current when it does a Post; this is how ASP.NET preserves its HttpContext.Current.
  3. Install your own TaskScheduler which sets OperationContext.Current.

You may also want to raise this issue on Microsoft Connect.


It is unfortunate that this doesn't work and we will see about getting a fix out in a future release.

In the mean time, there is a way to reapply the context to the current thread so that you don't have to pass the object around:

    public async Task<double> Add(double n1, double n2)
    {

        OperationContext ctx = OperationContext.Current;

        await Task.Delay(100);

        using (new OperationContextScope(ctx))
        {
            DoSomethingElse();
        }
        return n1 + n2;
    }  

In the above example, the DoSomethingElse() method will have access to OperationContext.Current as expected.


It seems to be fixed in .Net 4.6.2. See the announcement


Fortunately for us, our real-life service implementation gets instantiated via Unity IoC container. That allowed us to create a IWcfOperationContext which was configured to have a PerResolveLifetimeManager which simply means that there will be only one instance of WcfOperationContext for each instance of our RealService.
In the constructor of WcfOperationContext we capture OperationContext.Current and then all the places that require it get it from IWcfOperationContext. This is in effect what Stephen Cleary suggested in his answer.


Here's a sample SynchronizationContext implementation:

public class OperationContextSynchronizationContext : SynchronizationContext
{
    private readonly OperationContext context;

    public OperationContextSynchronizationContext(IClientChannel channel) : this(new OperationContext(channel)) { }

    public OperationContextSynchronizationContext(OperationContext context)
    {
        OperationContext.Current = context;
        this.context = context;
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        OperationContext.Current = context;
        d(state);
    }
}

And usage:

var currentSynchronizationContext = SynchronizationContext.Current;
try
{
    SynchronizationContext.SetSynchronizationContext(new OperationContextSynchronizationContext(client.InnerChannel));
    var response = await client.RequestAsync();
    // safe to use OperationContext.Current here
}
finally
{
    SynchronizationContext.SetSynchronizationContext(currentSynchronizationContext);
}

Expanding on Mr. Cleary's #1 option, the following code can be placed in the constructor of the WCF service to store and retrieve the OperationContext in the logical call context:

if (CallContext.LogicalGetData("WcfOperationContext") == null)
{
     CallContext.LogicalSetData("WcfOperationContext", OperationContext.Current);
}
else if (OperationContext.Current == null)
{
     OperationContext.Current = (OperationContext)CallContext.LogicalGetData("WcfOperationContext");
}

With that, anywhere you are having issues with a null context you can write something like the following:

var cachedOperationContext = CallContext.LogicalGetData("WcfOperationContext") as OperationContext;
var user = cachedOperationContext != null ? cachedOperationContext.ServiceSecurityContext.WindowsIdentity.Name : "No User Info Available";

Disclaimer: This is year-old code and I don't remember the reason I needed the else if in the constructor, but it was something to do with async and I know it was needed in my case.


Update: As pointed out by in the comments below, this solution is not thread safe, so I guess the solutions discussed above is still the best way.

I get around with the problem by registering the HttpContext into my DI container (Application_BeginRequest) and resolve it whenever I need it.

Register:

this.UnityContainer.RegisterInstance<HttpContextBase>(new HttpContextWrapper(HttpContext.Current));

Resolve:

var context = Dependencies.ResolveInstance<HttpContextBase>();


 MORE:


 ? WCF can return single object just fine but fails when returning a list
 ? WCF cannot add service due - cannot obtain meta data
 ? WCF service with Windows authentication throws exception "Username not defined" even if it is defined
 ? Windows network authentication the right way
 ? Windows network authentication the right way
 ? Windows network authentication the right way
 ? Windows network authentication the right way
 ? Windows Authentication and Network Service account as a db_owner
 ? Network Authentication when running exe from WMI
 ? windows authentication with a custom login page