Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows.Web.Http.HttpClient#GetAsync throws an incomplete exception when invalid credentials are used with basic authentication

Tags:

I am working on a Windows Runtime Component which makes API calls. Until earlier today I used the HttpClient and related models from System.Net but switched over to Windows.Web instead to leverage the WinRT streams.

Aside from changing the using statements, swapping HttpContent to IHttpContent and using the WindowsRuntimeExtensions to change my IInputStream to Stream for JSON.NET, I didn't have to do anything special. However suddenly 3 out of my 16 tests fail whereas previously everything worked.

All 3 (integration) tests validate that I receive an error response when logging in with invalid credentials. There are other tests that include logging in as well (but with valid credentials) and they work just fine. The given error message is of type AggregateException and has as message

System.AggregateException: One or more errors occurred. ---> System.Exception: Element not found.

A dialog cannot be displayed because the parent window handle has not been set.

The exception contains HRESULT values. The outerexception has value -2146233088 which corresponds to 0x80131500 while the innerexception has -2147023728 which corresponds to 0x80070490. Neither of those are a known error code on the MSDN page.

Following investigation:

  • 0x80131500 corresponds to COR_E_EXCEPTION
  • 0x80070490 corresponds to ERROR_NOT_FOUND

Stacktrace:

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:\Users\jeroen\Github\Windows-app\xx\xx\Models\Requests\Request.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:\Users\jeroen\Github\Windows-app\xx\xx\ApiDispatcher.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:\Users\jeroen\Github\Windows-app\xx\xx\ApiDispatcher.cs:line 22

Originally my question was worded somewhat differently because the actual problem seemed to be hidden. I have found out that the GET request by the HttpClient returns back to the caller instead of awaiting the result of the call (and executing the rest of the method).

In my project, executing the line var data = await myHttpClient.GetAsync(url); will return to the calling method with a non-constructed object and subsequent lines that come after the GetAsync() call are simply not executed.

Adding .ConfigureAwait(false) to stop it from going back did not make a difference.

The AggregateException is thrown when a user tries to login with invalid credentials. For some reason the HttpClient decides to throw an exception then without giving me a return value I could use. The problem here is that it does not tell me what kind of exception: catching COMException, TaskCanceledException, AggregateException and Exception only trigger the latter.

I have also found out that asynchronous integration tests do not work well with the multithreaded MSTest environment, so that explains several other failed tests that I had (but worked just fine individually)

I also, finally, have an example that demonstrates the problem (but I can't provide a webservice that takes basic auth)!

[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 = "[email protected]: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;
   }
}

Executing this code with a valid password will return true while an invalid password will throw an AggregateException.

Right now I am working around the problem by catching a general Exception around the call to GetAsync() but this is very rudimentary and I'd like to know why this incomplete exception is thrown in the first place.

like image 268
Jeroen Vannevel Avatar asked Jun 23 '14 08:06

Jeroen Vannevel


People also ask

What is HttpClient used for?

An HttpClient can be used to send requests and retrieve their responses. An HttpClient is created through a builder . The builder can be used to configure per-client state, like: the preferred protocol version ( HTTP/1.1 or HTTP/2 ), whether to follow redirects, a proxy, an authenticator, etc.

What is GetAsync?

GetAsync(Uri) Send a GET request to the specified Uri as an asynchronous operation. GetAsync(String, HttpCompletionOption) Send a GET request to the specified Uri with an HTTP completion option as an asynchronous operation.

What is a HttpClient C#?

The HttpClient class instance acts as a session to send HTTP requests. An HttpClient instance is a collection of settings applied to all requests executed by that instance. In addition, every HttpClient instance uses its own connection pool, isolating its requests from requests executed by other HttpClient instances.

What is HttpClient in VB net?

The HttpClient class is used to send and receive requests to an HTTP endpoint.


2 Answers

After reconstructing your example and playing around, I figured out what happens.

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

The GetAsync method invokes the HTTP request with invalid credentials. What happens is that the returned request tries to look for a window where you can enter the correct credentials, but doesn't find one. Hence it throws an Element Not Found while searching for that window.

This can be fixed by creating a HttpBaseProtocolFilter and setting the AllowUI property to false and then passing it to the HttpClient:

private async Task<bool> DoSomething() {     var httpBaseFilter = new HttpBaseProtocolFilter     {         AllowUI = false     };      var client = new HttpClient(httpBaseFilter);     var info = "[email protected]: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; } 

Response after adding HttpBaseProtocolFilter

like image 180
Yuval Itzchakov Avatar answered Oct 05 '22 06:10

Yuval Itzchakov


Setting AllowUI on HttpBaseProtocolFilter to false will stop this error.

If however you do want a dialog box to display, allowing the user to enter credentials, then the web request needs to be started on the UI thread.

like image 40
James Newton-King Avatar answered Oct 05 '22 07:10

James Newton-King