当基本身份验证使用无效凭据时,Windows.Web.Http.HttpClient#GetAsync会引发不完整的异常

时间:2014-06-23 08:35:54

标签: c# .net windows-runtime async-await dotnet-httpclient

我正在开发一个可以进行API调用的Windows运行时组件。直到今天早些时候,我使用HttpClient中的System.Net及相关模型,但转而使用Windows.Web来利用WinRT流。

除了更改using语句,将HttpContent交换为IHttpContent并使用WindowsRuntimeExtensions将我的IInputStream更改为Stream以获取JSON .NET,我没有做任何特别的事情。然而,在我的16次测试中突然有3次失败,而之前的一切都有效。

所有3个(集成)测试验证我在使用无效凭据登录时收到错误响应。还有其他测试包括登录(但使用有效的凭据),它们工作得很好。给定的错误消息类型为AggregateException,并具有消息

  

System.AggregateException:发生了一个或多个错误。 ---> System.Exception:找不到元素。

     

由于尚未设置父窗口句柄,因此无法显示对话框。

该异常包含HRESULT值。 outerexception的值-2146233088对应0x80131500,而innerexception的-2147023728对应0x80070490。这些都不是the MSDN page上的已知错误代码。

经过调查:

堆栈跟踪:

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

最初我的问题措辞有所不同,因为实际问题似乎是隐藏的。我发现HttpClient的GET请求返回给调用者而不是等待调用的结果(并执行方法的其余部分)。

在我的项目中,执行行var data = await myHttpClient.GetAsync(url);将返回带有非构造对象的调用方法,并且在GetAsync()调用之后的后续行不会被执行。

添加.ConfigureAwait(false)以阻止其返回没有任何区别。

当用户尝试使用无效凭据登录时,会引发AggregateException。由于某种原因,HttpClient决定抛出一个异常然后没有给我一个我可以使用的返回值。这里的问题是它没有告诉我什么样的例外:捕获COMExceptionTaskCanceledExceptionAggregateExceptionException只会触发后者。

我还发现异步集成测试在多线程MSTest环境中效果不佳,因此我解释了其他几个失败的测试(但是单独工作得很好)

最后,我还有一个演示问题的示例(但我无法提供基本身份验证的Web服务)!

[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()来解决这个问题,但这非常简陋,我想知道为什么在第一次抛出这个不完整的异常地点。

3 个答案:

答案 0 :(得分:30)

重建你的例子并玩弄后,我想出了会发生什么。

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

Response after adding HttpBaseProtocolFilter

答案 1 :(得分:1)

AllowUI上设置HttpBaseProtocolFilterfalse将停止此错误。

但是,如果您确实需要显示一个对话框,允许用户输入凭据,则需要在UI线程上启动Web请求。

答案 2 :(得分:0)

我认为问题在于您调用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();