Windows.Web.Http.HttpClient#GetAsync выдает неполное исключение, когда неверные учетные данные используются с базовой аутентификацией

#c# #.net #windows-runtime #async-await #dotnet-httpclient

#c# #.net #windows-среда выполнения #async-await #dotnet-httpclient

Вопрос:

Я работаю над компонентом среды выполнения Windows, который выполняет вызовы API. До сегодняшнего дня я использовал HttpClient и связанные модели из System.Net , но Windows.Web вместо этого переключился на использование потоков WinRT.

Помимо изменения using операторов, замены HttpContent IHttpContent и использования WindowsRuntimeExtensions для изменения моего IInputStream на Stream для JSON.NET Мне не нужно было делать ничего особенного. Однако внезапно 3 из моих 16 тестов завершились неудачей, тогда как раньше все работало.

Все 3 (интеграционных) теста подтверждают, что я получаю ответ об ошибке при входе в систему с неверными учетными данными. Существуют и другие тесты, которые также включают вход в систему (но с действительными учетными данными), и они работают просто отлично. Данное сообщение об ошибке имеет тип AggregateException и имеет вид сообщения

System.AggregateException : Произошла одна или несколько ошибок. —> System.Exception : Элемент не найден.

Диалоговое окно не может быть отображено, поскольку дескриптор родительского окна не был установлен.

Исключение содержит значения HRESULT. Исключение outerexception имеет значение -2146233088 , соответствующее 0x80131500 , в то время как исключение innerexception имеет -2147023728 значение, соответствующее 0x80070490 . Ни один из них не является известным кодом ошибки на странице MSDN.

Следующее расследование:

Отслеживание стека:

 Result StackTrace:  
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at xx.Models.Requests.GetRequest.<ExecuteRequestAsync>d__0.MoveNext() in c:UsersjeroenGithubWindows-appxxxxModelsRequestsRequest.cs:line 17

--- End of stack trace from previous location where exception was thrown ---

   at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
   at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
   at xx.ApiDispatcher.<ExecuteAsync>d__0`2.MoveNext() in c:UsersjeroenGithubWindows-appxxxxApiDispatcher.cs:line 40

 --- End of inner exception stack trace ---

    at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions)
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   at System.Threading.Tasks.Task`1.get_Result()
   at xx.ApiDispatcher.Execute[TCallResult,TResponseObject](ApiCall`2 call) in c:UsersjeroenGithubWindows-appxxxxApiDispatcher.cs:line 22
 

Первоначально мой вопрос был сформулирован несколько иначе, потому что реальная проблема, казалось, была скрыта. Я обнаружил, что запрос GET от the HttpClient возвращается обратно вызывающей стороне вместо ожидания результата вызова (и выполнения остальной части метода).

В моем проекте выполнение строки var data = await myHttpClient.GetAsync(url); приведет к возврату к вызывающему методу с неконструированным объектом, а последующие строки, которые следуют за GetAsync() вызовом, просто не выполняются.

Добавление .ConfigureAwait(false) , чтобы остановить его возврат, не имело значения.

Выдается AggregateException , когда пользователь пытается войти в систему с неверными учетными данными. По какой-то причине HttpClient он решает создать исключение, не предоставляя мне возвращаемое значение, которое я мог бы использовать. Проблема здесь в том, что он не сообщает мне, какое исключение: перехват COMException , TaskCanceledException , AggregateException и Exception только запускает последнее.

Я также обнаружил, что асинхронные интеграционные тесты плохо работают с многопоточной средой MSTest, так что это объясняет несколько других неудачных тестов, которые у меня были (но отлично работали по отдельности)

У меня также, наконец, есть пример, демонстрирующий проблему (но я не могу предоставить веб-сервис, который принимает базовую аутентификацию)!

 [TestMethod]
public void TestMethod3()
{
    Assert.IsTrue(new Test().Do().AsTask().Result);
}

public sealed class Test
{
   public IAsyncOperation<bool> Do()
   {
       return DoSomething().AsAsyncOperation();
   } 

   private async Task<bool> DoSomething()
   {
       var client = new HttpClient();
       var info = "jeroen.vannevel@something.com:nopass";
       var token = Convert.ToBase64String(Encoding.UTF8.GetBytes(info));
       client.DefaultRequestHeaders.Authorization = new HttpCredentialsHeaderValue("Basic", token);

       var data = await client.GetAsync(new Uri("https://mytestdomain/v2/apikey?format=Json"));
       return true;
   }
}
 

Выполнение этого кода с действительным паролем вернет true , а неверный пароль выдаст AggregateException .

Прямо сейчас я работаю над решением проблемы, улавливая общие Exception сведения о вызове GetAsync() , но это очень элементарно, и я хотел бы знать, почему это неполное исключение выдается в первую очередь.

Комментарии:

1. Не могли бы вы попробовать установить другие учетные данные для аутентификации: client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", token);

2. @YuvalItzchakov: это часть System.Net.Http пространства имен и несовместимо с Windows.Web.Http.HttpClient , если я что-то не упускаю из виду.

3. Правильно, вы используете Windows.Web . Моя ошибка.

4. Вы пробовали создать HttpHandler и настроить его Credentials свойство, а затем передать его HttpClient ? var client = new HttpClient(handler)

5. @YuvalItzchakov: это System.Net тоже хорошо.

Ответ №1:

После восстановления вашего примера и игры я понял, что происходит.

 var data = await client.GetAsync(new Uri("https://mytestdomain/v2/apikey?format=Json"));
 

GetAsync Метод вызывает HTTP-запрос с неверными учетными данными. Что происходит, так это то, что возвращенный запрос пытается найти окно, в котором вы можете ввести правильные учетные данные, но не находит его. Следовательно, он выдает Element Not Found запрос при поиске этого окна.

Это можно исправить, создав HttpBaseProtocolFilter и установив AllowUI для свойства false значение, а затем передав его в HttpClient :

 private async Task<bool> DoSomething()
{
    var httpBaseFilter = new HttpBaseProtocolFilter
    {
        AllowUI = false
    };

    var client = new HttpClient(httpBaseFilter);
    var info = "jeroen.vannevel@something.com:nopass";
    var token = Convert.ToBase64String(Encoding.UTF8.GetBytes(info));
    client.DefaultRequestHeaders.Authorization = new HttpCredentialsHeaderValue("Basic", token);

    var data = await client.GetAsync(new Uri("https://mytestdomain/v2/apikey?format=Json"));
    return true;
}
 

Ответ после добавления HttpBaseProtocolFilter

Комментарии:

1. Я ценю тщательную помощь! Вы совершенно правы, это решило проблему. Я оставлю щедрость открытой еще на некоторое время, чтобы у вашего ответа было больше просмотров, но будьте уверены, что вы его получите.

2. Зависит ли это поведение от платформы устройства, отправляющего запрос? Когда я отправляю этот запрос с планшета Win RT, я получаю ошибку «Элемент не найден» (и там сработала установка AllowUI в false), но на устройстве WP я получил правильную ошибку: несанкционированный (даже без установки AllowUI в false).

3. @Ashish Не уверен, я никогда не пробовал отправлять запрос из WinRT.

Ответ №2:

Установка AllowUI on HttpBaseProtocolFilter на false остановит эту ошибку.

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

Ответ №3:

Я думаю, проблема в том, что исключение фактически генерируется вашим вызовом EnsureSuccessStatusCode .

Вы пытались добавить класс HttpResponseMessage EnsureSuccessStatusCode() метод и проверку.

http://msdn.microsoft.com/en-us/library/system.net.http.httpresponsemessage.ensuresuccessstatuscode.aspx

После этой строки:

 var data = await client.GetAsync(new Uri("https://mytestdomain/v2/apikey?format=Json"));
date.EnsureSuccessStatusCode();
 

Комментарии:

1. Исключение генерируется в await точке. Это не имеет ничего общего с EnsureSuccessStatusCode