I am using async/await pattern in .NET 4.5 to implement some service methods in WCF. Example service:
Contract:
[ServiceContract(Namespace = \"http:/
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:
LogicalCallContext
.SynchronizationContext
which will set OperationContext.Current
when it does a Post
; this is how ASP.NET preserves its HttpContext.Current
.TaskScheduler
which sets OperationContext.Current
.You may also want to raise this issue on Microsoft Connect.
It seems to be fixed in .Net 4.6.2. See the announcement
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>();
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.
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);
}