#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.
Следующее расследование:
0x80131500
соответствуетCOR_E_EXCEPTION
0x80070490
соответствуетERROR_NOT_FOUND
Отслеживание стека:
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;
}
Комментарии:
1. Я ценю тщательную помощь! Вы совершенно правы, это решило проблему. Я оставлю щедрость открытой еще на некоторое время, чтобы у вашего ответа было больше просмотров, но будьте уверены, что вы его получите.
2. Зависит ли это поведение от платформы устройства, отправляющего запрос? Когда я отправляю этот запрос с планшета Win RT, я получаю ошибку «Элемент не найден» (и там сработала установка AllowUI в false), но на устройстве WP я получил правильную ошибку: несанкционированный (даже без установки AllowUI в false).
3. @Ashish Не уверен, я никогда не пробовал отправлять запрос из WinRT.
Ответ №2:
Установка AllowUI
on HttpBaseProtocolFilter
на false
остановит эту ошибку.
Однако, если вы хотите, чтобы отображалось диалоговое окно, позволяющее пользователю вводить учетные данные, тогда веб-запрос необходимо запустить в потоке пользовательского интерфейса.
Ответ №3:
Я думаю, проблема в том, что исключение фактически генерируется вашим вызовом EnsureSuccessStatusCode .
Вы пытались добавить класс HttpResponseMessage EnsureSuccessStatusCode() метод и проверку.
После этой строки:
var data = await client.GetAsync(new Uri("https://mytestdomain/v2/apikey?format=Json"));
date.EnsureSuccessStatusCode();
Комментарии:
1. Исключение генерируется в
await
точке. Это не имеет ничего общего сEnsureSuccessStatusCode