39

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?

mdonatas
  • 1,770
  • 2
  • 15
  • 15
  • What does it mean to serialize a `Task` instance over the wire to the client? – Steven Oct 09 '12 at 10:07
  • When using async/await the Task doesn't get passed to the client. Wcf understands that as void returning method. Client adding a reference to such service would see void DoSomething(); – mdonatas Oct 09 '12 at 10:30
  • 1
    That's interesting. Still, I'm not sure that you actually want to exeecute operations this way. What do you do when the operation fails for some reason? The client thinks it succeeded succesfully. You'd better queue these operations in a transactional queue of some sort. – Steven Oct 09 '12 at 11:10
  • 2
    @Steven: The WCF runtime will not return the response to the client until the `Task` is complete. – Stephen Cleary Oct 09 '12 at 11:16
  • @StephenCleary: So this is still a synchronous call. When is that actually useful? – Steven Oct 09 '12 at 11:18
  • @Steven WCF assigns a thread to process your request but then you may want to to make a DB query or web service request inside your service method. These operation are best done asynchronously so that the thread assigned to your request could be returned to the pool while you are 'away' querying DB. In short, this is done to either a) improve scalability (threads are not waiting for DB), b) make parallel requests, c) combination of a and b - make asynchronous parallel requests. – mdonatas Oct 09 '12 at 11:58
  • I know this question is old, but there is pretty much no reason to use async inside a WCF service ever. – Chris Marisic Jan 28 '16 at 00:45
  • 1
    @ChrisMarisic Thanks for your comment about not using async inside a WCF service. Solved my problem here: https://stackoverflow.com/questions/44192260/the-value-of-operationcontext-current-is-not-the-operationcontext-value-installe – Mike Taverne May 26 '17 at 03:04
  • 1
    @MikeTaverne yep, that's exactly the reason to not use async inside. Dealing with scope and context being lost by the thread switches is insanity. – Chris Marisic Jun 02 '17 at 19:06

7 Answers7

29

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.

JonCole
  • 2,902
  • 1
  • 17
  • 19
  • 7
    Jon - you clearly know more than most people with a rep of 16 (and I found your blog on MSDN). May I suggest you update your profile so people understand the quality of your answer. – ErnieL Oct 17 '12 at 13:57
  • Jon, we're just trying to get decide an approach to the subject of this question, I wondered if you could explain why, in your code above, you use OperationContextScope, rather than just using the ctx directly? (I have posted more detail here http://stackoverflow.com/questions/13290146/async-wcf-method-weboperationcontext-is-null-after-await) – Justin Harvey Nov 09 '12 at 09:54
  • 2
    The primary reason for using OperationContextScope is to get OperationContext.Current set to the one passed to the constructor. This prevents you from having to pass the ctx instance down a deep call stack (e.g. you don't have to modify your method signatures to take an OperationContext parameter). – JonCole Nov 13 '12 at 17:39
  • 2
    This solution does not allow access to OperationContext.Current from within an await. In the above example though, yes, it works. – Kevin Kalitowski Dec 10 '13 at 20:40
  • Kevin is right. This solution doesn't help when the call is inside the await method. – Rhyous Nov 06 '17 at 20:54
22

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.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
8

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

DixonD
  • 6,557
  • 5
  • 31
  • 52
  • 1
    I upated to 4.6.2 and my OperationContext is still null in await calls. I've found a not to add this too appSettings, but nothing is has changed: – Rhyous Nov 06 '17 at 20:55
  • @Rhyous are you awaiting with `configureAwait(false)` by any chance? – DixonD Nov 07 '17 at 07:29
  • I am worried that my project is Unique. My project is called Entity Anywhere Framework. It is a business applications framework, designed with the idea that I will have a dozen systems and entities in all of them, and I want to build on top of those. Anyway, I am dynamically generating the REST wcf service per entity. I need to test a default project using my design. https://github.com/rhyous/EntityAnywhere – Rhyous Nov 07 '17 at 16:50
  • @Rhyous you also need : in your appSettings section – TSmith May 05 '20 at 13:18
4

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.

Kevin Kalitowski
  • 6,829
  • 4
  • 36
  • 52
3

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);
}
Thresh
  • 943
  • 14
  • 23
  • In case of nested levels of awaited calls the current SynchronizationContext is not replaced. So I had to extend the overridden Post method with a check, if a SynchronizationContext.Current is null I call SynchronizationContext.SetSynchronizationContext(this). I don't know whether it is the proper way, however it worked for me. – Daniel Leiszen Aug 28 '15 at 22:43
2

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.

mdonatas
  • 1,770
  • 2
  • 15
  • 15
-2

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>();
wind23
  • 17
  • 4
  • I have doubts that this is thread safe. Like what happens if there are two requests (A and B) at basically the same time. A registers this, moves on, B registers this and A resolves the instance... instance put in there by B. – mdonatas Apr 28 '16 at 12:55
  • Thank you for your feedback, @mdonates. Indeed it could pose issues like what you desribed. I shall go back to my drawing board and work out something else. Cheers. – wind23 Apr 30 '16 at 02:12