如何在WPF应用程序中通过HttpClient调用Web API

Asynchronous Calls

异步调用

HttpClient is designed to be non-blocking. Potentially long-running operations are implemented as asynchonrous methods, such as GetAsync and PostAsync. These methods return without waiting for the operation to complete. The previous tutorial (Calling a Web API From a Console Application) showed only blocking calls:

HttpClient被设计成是非阻塞的。潜在地,长时间运行的操作是作为异步方法实现的,例如,GetAsync和PostAsync。这些方法不会等待操作完成便会返回。上一教程(通过控制台应用程序调用Web API)只展示了阻塞调用:

HttpResponseMessage response = client.GetAsync("api/products").Result; // Blocking call(阻塞)!

This code blocks the calling thread by taking the Result property. That's OK for a console application, but you should not do it from a UI thread, because it blocks the UI from responding to user input.

这段代码通过采用Result属性,会阻塞调用线程。对于一个控制台应用程序,这没问题,但你不应该在一个UI线程中采用这一做法,因为这会阻止UI去响应用户输入。

The asynchronous methods of HttpClient return Task objects that represent the asynchronous operation.

HttpClient的异步方法会返回表示异步操作的Task对象。

Create the WPF Project

创建WPF项目

Start Visual Studio. From the Start menu, select New Project. In the Templates pane, select Installed Templates and expand the Visual C# node. In the list of project templates, select WPF Application. Name the project and click OK.

启动Visual Studio。从“开始”菜单选择“新项目”。在“模板”面板中,选择“已安装模板”,并展开“Viusal C#”节点。在项目模板列表中,选择“WPF应用程序”。命名此项目并点击“OK”。

Open MainWindow.xaml and add the following XAML markup inside the Grid control:

打开MainWindow.xaml,并在Grid控件中添加以下XAML标记:

<StackPanel Width="250" >

<Button Name="btnGetProducts" Click="GetProducts">Get Products</Button>

<ListBox Name="ProductsList">

<ListBox.ItemTemplate>

<DataTemplate>

<StackPanel Margin="2">

<TextBlock Text="{Binding Path=Name}" />

<TextBlock >Price: $<Run Text="{Binding Path=Price}" />

(<Run Text="{Binding Path=Category}" />)</TextBlock>

</StackPanel>

</DataTemplate>

</ListBox.ItemTemplate>

</ListBox>

</StackPanel>

This markup defines a ListBox that will be data-bound to the list of products. The DataTemplate defines how each product will be displayed.

这段标记定义了一个将被数据绑定到产品列表的ListBox(列表框)。DataTemplate(数据模板)定义了如何显示每个产品。(其效果如图3-4所示)。

图3-4. WPF界面效果

Add the Model Class

添加模型类

Add the following class to the application:

将以下类添加到应用程序:

class Product

{

public string Name { get; set; }

public double Price { get; set; }

public string Category { get; set; }

}

This class defines a data object that HttpClient will write into the HTTP request body and read from the HTTP response body.

这个类定义了一个数据对象,HttpClient将把它写入HTTP请求体,也从HTTP响应体中读取它。

We'll also add an observable class for data binding:

我们也要添加一个用于数据绑定的可见对象类(observable class):

class ProductsCollection : ObservableCollection<Product>

{

public void CopyFrom(IEnumerable<Product> products)

{

this.Items.Clear();

foreach (var p in products)

{

this.Items.Add(p);

}

this.OnCollectionChanged(

new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));

}

}

Install NuGet Package Manager

安装NuGet包管理器

NuGet Package Manager is the easiest way to add the Web API Client library to a project. If you do not have NuGet Package Manager already installed, install it as follows.

将Web API客户端库添加到项目最容易的办法是安装“NuGet包管理器(NuGet Package Manager)”。如果尚未安装NuGet包管理器,按如下步骤安装。

1.Start Visual Studio.

启动Visual Studio.

2.From the Tools menu, select Extensions and Updates.

从“工具”菜单选择“扩展与更新”

3.In the Extensions and Updates dialog, select Online.

在“扩展与更新”对话框中,选择“在线”

4.If you don't see "NuGet Package Manager", type "nuget package manager" in the search box.

如果未看到“NuGet包管理器”,在搜索框中输入“nuget package manager”。

5.Select the NuGet Package Manager and click Download.

选择“NuGet包管理器”,并点击“下载”。

6.After the download completes, you will be prompted to install.

下载完成后,会提示你安装。

7.After the installation completes, you might be prompted to restart Visual Studio.

安装完成后,可能会提示重启Visual Studio。

上述安装过程如图3-5所示。

图3-5. 安装NuGet包管理器

Install the Web API Client Libraries

安装Web API客户端库

After NuGet Package Manager is installed, add the Web API Client Libraries package to your project.

安装NuGet包管理器后,把Web API客户端库包添加到你的项目。步骤如下:

1.From the Tools menu, select Library Package Manager. Note: If do you not see this menu item, make sure that NuGet Package Manager installed correctly.

从“工具”菜单选择“库包管理器”。注:如果看不到这个菜单项,请确保已正确安装了NuGet包管理器。

2.Select Manage NuGet Packages for Solution...

选择“管理解决方案的NuGet包…”

3.In the Manage NuGet Packages dialog, select Online.

在“管理NuGet包”对话框中,选择“在线”。

4.In the search box, type "Microsoft.AspNet.WebApi.Client".

在搜索框中输入“Microsoft.AspNet.WebApi.Client”。

5.Select the ASP.NET Web API Self Host package and click Install.

选择“ASP.NET Web API自托管包”,并点击“安装”。

6.After the package installs, click Close to close the dialog.

这个包安装后,点击“关闭”,关闭此对话框。

上述安装步骤如图3-6所示。

图3-6. 安装Web API客户端库

Initialize HttpClient

初始化HttpClient

From Solution Explorer, open the file MainWindow.xaml.cs. Add the following code.

在“解决方案资源管理器”中,打开MainWindow.xaml.cs文件。添加以下代码:

namespace WpfProductClient

{

using System;

using System.Collections.Generic;

using System.Net.Http;

using System.Net.Http.Headers;

using System.Windows;

public partial class MainWindow : Window

{

HttpClient client = new HttpClient();

ProductsCollection _products = new ProductsCollection();

public MainWindow()

{

InitializeComponent();

client.BaseAddress = new Uri("plete. The await keyword suspends execution until the operation completes. For example:

正如其名称所暗示的,GetAsync和ReadAsAsync是异步方法,意即,它们立即返回,不会等待操作完成。await关键字会挂起执行,直到操作完成。例如:

var response = await client.GetAsync("api/products");

The code that appears after this statement does not execute until the HTTP request is completed. But that does not mean the event handler blocks, waiting for GetAsync to complete. Just the opposite — control returns to the caller. When the HTTP request is completed, execution continues from the point where it was suspended.

出现在这条语句之后的代码直到HTTP请求完成时才会执行。但这并不意味着事件处理器(event handler,也可以叫做事件处理程序 — 译者注)会阻塞,以等待GetAsync完成。恰恰相反 — 控制会返回给调用者。当HTTP请求完成时,执行会从挂起点继续。

If a method uses await, it must have the async modifier:

如果一个方法使用await,它必须有async修饰符:

private async void GetProducts(object sender, RoutedEventArgs e)

Without the await keyword, you would need to call ContinueWith on the Task object:

没有这个await关键字,你就需要调用Task对象上的ContinueWith:

private void GetProducts(object sender, RoutedEventArgs e)

{

btnGetProducts.IsEnabled = false;

client.GetAsync("api/products/2").ContinueWith((t) =>

{

if (t.IsFaulted)

{

MessageBox.Show(t.Exception.Message);

btnGetProducts.IsEnabled = true;

}

else

{

var response = t.Result;

if (response.IsSuccessStatusCode)

{

response.Content.ReadAsAsync<IEnumerable<Product>>().

ContinueWith(t2 =>

{

if (t2.IsFaulted)

{

MessageBox.Show(t2.Exception.Message);

btnGetProducts.IsEnabled = true;

}

else

{

var products = t2.Result;

_products.CopyFrom(products);

btnGetProducts.IsEnabled = true;

}

}, TaskScheduler.FromCurrentSynchronizationContext());

}

}

}, TaskScheduler.FromCurrentSynchronizationContext());

}

This type of code is difficult to get right, so it's recommended to target .NET 4.5, or if that's not possible, install the Async Targeting Pack.

这种型式的代码难以正确,因此建议把目标定为.NET 4.5,或者,如果这不可能,需安装Async Targeting Pack(Async目标包)。