OperationContext.Current имеет значение null после первого ожидания при использовании async / wait в службе WCF

Я использую шаблон async / await в .NET 4.5 для реализации некоторых методов обслуживания в WCF. Пример сервиса:

Контракт:

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

Реализация:

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

Проблема, с которой я столкнулась, заключается в том, что после первого await OperationContext.Current возвращает null и я не могу получить доступ к OperationContext.Current.IncomingMessageHeaders .

В этом простом примере это не проблема, так как я могу захватить контекст перед await . Но в реальном мире OperationContext.Current получает доступ из глубины стека вызовов, и я действительно не хочу менять много кода, чтобы передать контекст дальше.

Есть ли способ получить контекст работы после точки await не передавая его в стек вручную?

Я думаю, что ваш лучший вариант – фактически захватить его и передать его вручную. Вы можете обнаружить, что это улучшает тестируемость вашего кода.

Тем не менее, есть еще пара вариантов:

  1. Добавьте его в LogicalCallContext .
  2. Установите свой собственный SynchronizationContext который будет устанавливать OperationContext.Current когда он делает Post ; это то, как ASP.NET сохраняет свой HttpContext.Current .
  3. Установите собственный TaskScheduler который устанавливает OperationContext.Current .

Вы также можете затронуть эту проблему в Microsoft Connect.

К сожалению, это не работает, и мы увидим, как получить исправление в будущей версии.

В то же время существует способ повторно применить контекст к текущему streamу, чтобы вам не пришлось передавать объект:

  public async Task Add(double n1, double n2) { OperationContext ctx = OperationContext.Current; await Task.Delay(100); using (new OperationContextScope(ctx)) { DoSomethingElse(); } return n1 + n2; } 

В приведенном выше примере метод DoSomethingElse () будет иметь доступ к OperationContext.Current, как ожидалось.

Кажется, это исправлено в .Net 4.6.2. См. Объявление

Вот пример реализации SynchronizationContext :

 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); } } 

И использование:

 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); } 

Расширяясь в опции № 1 г-на Клири, следующий код может быть помещен в конструктор службы WCF для хранения и извлечения OperationContext в контексте логического вызова:

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

При этом в любом месте, где возникают проблемы с нулевым контекстом, вы можете написать примерно следующее:

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

Отказ от ответственности: это годовой код, и я не помню причину, по которой я нуждался в else if в конструкторе, но это как-то связано с асинксом, и я знаю, что это было необходимо в моем случае.

К счастью для нас, наша реальная реализация сервиса создается через контейнер Unity IoC. Это позволило нам создать IWcfOperationContext который был настроен на PerResolveLifetimeManager который просто означает, что для каждого экземпляра нашего RealService будет только один экземпляр RealService .
В конструкторе WcfOperationContext мы WcfOperationContext OperationContext.Current а затем все требующие его места получают его из IWcfOperationContext . Это фактически то, что предложил Стивен Клири в своем ответе.

Обновление. Как указано в комментариях ниже, это решение не является streamобезопасным, поэтому, я думаю, решения, о которых говорилось выше, по-прежнему являются наилучшим способом.

Я столкнулся с проблемой, зарегистрировав HttpContext в моем контейнере DI (Application_BeginRequest) и разрешив его, когда мне это нужно.

Регистр:

 this.UnityContainer.RegisterInstance(new HttpContextWrapper(HttpContext.Current)); 

Разрешить:

 var context = Dependencies.ResolveInstance(); 
  • Управление сложными файлами Web.Config между средами развертывания
  • Пользовательский WCF DataContractSerializer
  • (413) Запросить сущность слишком большой | UploadReadAheadSize
  • Почему мой веб-сервис WCF представляет этот объект в другом пространстве имен с разными именами полей?
  • Декомпрессия streamа GZip из ответа HTTPClient
  • каков эквивалент Global.asax Application_Start при использовании WAS в IIS7
  • WCF ResponseFormat для WebGet
  • Каковы различия между веб-службами WCF и ASMX?
  • Соединение не может быть выполнено, потому что целевая машина активно отказалась от него 127.0.0.1:3446
  • Вызов службы WCF с помощью VBScript
  • Ошибка: невозможно получить метаданные из службы WCF
  • Давайте будем гением компьютера.