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

前端 未结 7 582
礼貌的吻别
礼貌的吻别 2020-12-02 22:59

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

Contract:

[ServiceContract(Namespace = \"http:/         


        
相关标签:
7条回答
  • 2020-12-02 23:02

    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.

    0 讨论(0)
  • 2020-12-02 23:10

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

    0 讨论(0)
  • 2020-12-02 23:11

    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.

    0 讨论(0)
  • 2020-12-02 23:13

    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>();
    
    0 讨论(0)
  • 2020-12-02 23:19

    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.

    0 讨论(0)
  • 2020-12-02 23:22

    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);
    }
    
    0 讨论(0)
提交回复
热议问题