Windows.Web.Http.HttpClient#GetAsync throws an incomplete exception when invalid credentials are used with basic authentication
Solution 1
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;
}
Solution 2
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.
Related videos on Youtube
Jeroen Vannevel
Graduated with a Bachelors in Applied Computer Science at the University College of Ghent. I focus heavily on C#, Roslyn, Entity-Framework and the general Microsoft ecosystem. Having trouble following best practices? Take a look at VSDiagnostics! Curriculum Vitae LinkedIn Programming is the art of telling another human what one wants the computer to do. ~Donald Knuth
Updated on June 16, 2022Comments
-
Jeroen Vannevel almost 2 years
I am working on a Windows Runtime Component which makes API calls. Until earlier today I used the
HttpClient
and related models fromSystem.Net
but switched over toWindows.Web
instead to leverage the WinRT streams.Aside from changing the
using
statements, swappingHttpContent
toIHttpContent
and using theWindowsRuntimeExtensions
to change myIInputStream
toStream
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 messageSystem.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 to0x80131500
while the innerexception has-2147023728
which corresponds to0x80070490
. Neither of those are a known error code on the MSDN page.Following investigation:
-
0x80131500
corresponds toCOR_E_EXCEPTION
-
0x80070490
corresponds toERROR_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 theGetAsync()
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 theHttpClient
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: catchingCOMException
,TaskCanceledException
,AggregateException
andException
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 anAggregateException
.Right now I am working around the problem by catching a general
Exception
around the call toGetAsync()
but this is very rudimentary and I'd like to know why this incomplete exception is thrown in the first place.-
Yuval Itzchakov almost 10 yearsCould you try setting different authentication credentials:
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", token);
-
Jeroen Vannevel almost 10 years@YuvalItzchakov: that is part of the
System.Net.Http
namespace and not compatible with theWindows.Web.Http.HttpClient
unless I am overlooking something. -
Yuval Itzchakov almost 10 yearsRight, you're using
Windows.Web
. My mistake. -
Yuval Itzchakov almost 10 yearsHave you tried creating a
HttpHandler
and settings itsCredentials
property, then passing it toHttpClient
?var client = new HttpClient(handler)
-
Jeroen Vannevel almost 10 years@YuvalItzchakov: that's
System.Net
as well. -
Yuval Itzchakov almost 10 yearsSorry, i ment an
IHttpFilter
: msdn.microsoft.com/en-us/library/windows/apps/… -
Jeroen Vannevel almost 10 yearsI have made an
IHttpFilter
but I don't see how it can make a difference: it still passes on the credentials to the eventualGetAsync
method; it just does it in a later stage. Setting the credentials in the filter did not change the result. -
Yuval Itzchakov almost 10 yearsI think i got it this time :)
-
Shane over 8 yearsIf appropriate, simply adding "Pragma:no-cache" to my request header fixed this issue for me.
-
-
Yuval Itzchakov almost 10 yearsThe exception is thrown at the
await
point. It has nothing to do withEnsureSuccessStatusCode
-
Jeroen Vannevel almost 10 yearsI appreciate the thorough help! You are entirely correct, this solved it. I'll leave the bounty open for some more time so there will be more views for your answer but rest assured that you'll get it.
-
tavier almost 8 yearsDoes this behavior depend on the platform of the device sending the request? When I send this request from Win RT tablet I get "Element not found" error (and setting AllowUI to false worked there) but in WP device I got the correct error: UnAuthorized (even without setting AllowUI to false).
-
Yuval Itzchakov almost 8 years@Ashish Not sure, I've never tried dispatching a request from WinRT.