3.2 通过.NET客户端调用Web API(C#)
Create the Console Application
创建控制台应用程序
启动Visual studio,并从“开始”页面选择“新项目”。或者从“文件”菜单选择“新建”,然后选择“项目”。
在“模板”面板中,选择“已安装模板”,并展开“Visual C#”节点。在“Visual C#”下选择“Windows”。在项目模板列表中选择“控制台应用程序”。命名此项目并点击“OK”(见图3-1)。

图3-1. 创建控制台项目
安装NuGet包管理器
“NuGet包管理器(NuGet Package Manager)”是把Web API客户端库添加到项目的一种最容易的方法。如果尚未安装NuGet包管理器,按如下步骤安装。
- 启动Visual Studio.
- 从“工具”菜单选择“扩展与更新”
- 在“扩展与更新”对话框中,选择“在线”
- 如果未看到“NuGet包管理器”,在搜索框中输入“nuget package manager”。
- 选择“NuGet包管理器”,并点击“下载”。
- 下载完成后,会提示你安装。
- 安装完成后,可能会提示重启Visual Studio。
上述安装过程如图3-2所示。

图3-2. 安装NuGet包管理器
安装Web API客户端库
.
安装NuGet包管理器后,把Web API客户端库包添加到你的项目。步骤如下:
- 从“工具”菜单选择“库包管理器”。注:如果看不到这个菜单项,请确保已正确安装了NuGet包管理器。
- 选择“管理解决方案的NuGet包…”
- 在“管理NuGet包”对话框中,选择“在线”。
- 在搜索框中输入“Microsoft.AspNet.WebApi.Client”。
- 选择“ASP.NET Web API自托管包”,并点击“安装”。
- 这个包安装后,点击“关闭”,关闭此对话框。
上述安装步骤如图3-3所示。

图3-3. 安装Web API客户端库
添加模型类
将以下类添加到应用程序:
class Product
{
public string Name { get; set; }
public double Price { get; set; }
public string Category { get; set; }
}
这个类创建一个数据对象,HttpClient将把它写入HTTP请求体中,也从HTTP响应体中读取它。
Initialize HttpClient
初始化HttpClient
创建一个新的HttpClient实例,并像下面这样初始化它:
namespace ProductStoreClient
{
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
class Program
{
static void Main(string[] args)
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:9000/");
// Add an Accept header for JSON format.
// 为JSON格式添加一个Accept报头
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
}
}
}
这段代码把基URI设置为“http://localhost:9000/”,并将Accept报头设置为“application/json”,这是告诉服务器,以JSON格式发送数据。
获取资源(HTTP GET)
以下代码展示如何对产品列表查询API:
// List all products.
// 列出所有产品
HttpResponseMessage response = client.GetAsync("api/products").Result; // Blocking call(阻塞调用)!
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
// 解析响应体。阻塞!
var products = response.Content.ReadAsAsync<IEnumerable<Product>>().Result;
foreach (var p in products)
{
Console.WriteLine("{0}\t{1};\t{2}", p.Name, p.Price, p.Category);
}
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
GetAsync方法发送HTTP GET请求。正如其名称所暗示的,GetAsync是异步的。它立即返回,不会等待服务器的响应。返回值是一个表示异步操作的Task对象。当该操作完成时,Task.Result属性包含HTTP响应。
重要的是理解,直到请求完成(或超时),采用Result属性的过程是应用程序线程阻塞的。控制台应用程序的阻塞没问题,但是,你决不应该在一个Windows应用程序的UI上做这种事,因为这会阻塞UI去响应用户的输入。在本教程的下一部分中,我们将看到如何编写非阻塞调用。
如果HTTP响应指示成功,响应体便含有一个JSON格式的产品列表。要解析这个列表,需调用ReadAsAsync。该方法读取响应体,并试图把它解序列化成一个具体的CLR(公共语言运行时)类型。这个方法也是异步的,因为体可能有任意大小。再次强调,采用Result属性的过程是线程阻塞的。
HTTP会话示例:
GET http://localhost:9000/api/products HTTP/1.1
Accept: application/json
Host: localhost:9000
Connection: Keep-Alive
HTTP/1.1 200 OK
Server: ASP.NET Development Server/11.0.0.0
Date: Mon, 20 Aug 2012 22:14:59 GMT
X-AspNet-Version: 4.0.30319
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: application/json; charset=utf-8
Content-Length: 183
Connection: Close
[{"Id":1,"Name":"Tomato soup","Category":"Groceries","Price":1.39},{"Id":2,"Name":"Yo-yo",
"Category":"Toys","Price":3.75},{"Id":3,"Name":"Hammer","Category":"Hardware","Price":16.99}]
通过ID获取产品是类似的:
// Get a product by ID
// 通过ID获取产品
response = client.GetAsync("api/products/1").Result;
if (response.IsSuccessStatusCode)
{
// Parse the response body. Blocking!
// 解析响应休。阻塞!
var product = response.Content.ReadAsAsync<Product>().Result;
Console.WriteLine("{0}\t{1};\t{2}", product.Name, product.Price, product.Category);
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
媒体类型格式化器
ReadAsAsync是在System.Net.Http.HttpContentExtensions类中定义的一个扩展方法。不带参数,它会使用媒体类型格式化器的默认设置,以试图解析响应体。默认格式化器支持JSON、XML和经过url编码的表单数据(Form-url-encoded data)。(关于媒体类型格式化器的更多信息,参阅“格式化与模型绑定(本教程系列的第6章 — 译者注)”)
也可以明确指定所使用的媒体类型格式化器。如果你有一个自定义媒体类型格式化器,这是有用的。
var formatters = new List<MediaTypeFormatter>() {
new MyCustomFormatter(),
new JsonMediaTypeFormatter(),
new XmlMediaTypeFormatter()
};
resp.Content.ReadAsAsync<IEnumerable<Product>>(formatters);
创建一个资源(HTTP POST)
以下代码发送一个POST请求,它含有一个JSON格式的Product实例:
// Create a new product
// 创建一个新产品
var gizmo = new Product() { Name = "Gizmo", Price = 100, Category = "Widget" };
Uri gizmoUri = null;
response = client.PostAsJsonAsync("api/products", gizmo).Result;
if (response.IsSuccessStatusCode)
{
gizmoUri = response.Headers.Location;
}
else
{
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
}
PostAsJsonAsync是在System.Net.Http.HttpClientExtensions中定义的一个扩展方法。上述代码与以下代码等效:
var product = new Product() { Name = "Gizmo", Price = 100, Category = "Widget" };
// Create the JSON formatter.
// 创建JSON格式化器。
MediaTypeFormatter jsonFormatter = new JsonMediaTypeFormatter();
// Use the JSON formatter to create the content of the request body.
// 使用JSON格式化器创建请求体内容。
HttpContent content = new ObjectContent<Product>(product, jsonFormatter);
// Send the request.
// 发送请求。
var resp = client.PostAsync("api/products", content).Result;
对于XML格式,使用PostAsXmlAsync方法。
HTTP会话示例:
POST http://localhost:9000/api/products HTTP/1.1
Accept: application/json
Content-Type: application/json; charset=utf-8
Host: localhost:9000
Content-Length: 50
Expect: 100-continue
{"Name":"Gizmo","Price":100.0,"Category":"Widget"}
HTTP/1.1 201 Created
Server: ASP.NET Development Server/11.0.0.0
Date: Mon, 20 Aug 2012 22:15:00 GMT
X-AspNet-Version: 4.0.30319
Location: http://localhost:9000/api/products/7
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
Content-Type: application/json; charset=utf-8
Content-Length: 57
Connection: Close
{"Id":7,"Name":"Gizmo","Category":"Widget","Price":100.0}
默认地,JSON格式化器将content-type(内容类型)设置为“application/json”。你也可以明确地指定媒体类型。例如,假设“application/vnd.example.product”是你用于Product实例的媒体类型。可以像下面这样来设置这种媒体:
HttpContent content = new ObjectContent<Product>(product, jsonFormatter,
"application/vnd.example.product+json");
更新一个资源(HTTP PUT)
以下代码发送一个PUT请求:
// Update a product
// 更新一个产品
gizmo.Price = 99.9;
response = client.PutAsJsonAsync(gizmoUri.PathAndQuery, gizmo).Result;
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
PutAsJsonAsync方法与PostAsJsonAsync类似,只是它发送的是一个PUT请求,而不是POST。
删除一个资源(HTTP DELETE)
现在,你可能已经能预测到如何发送一个DELETE请求了:
// Delete a product
// 删除一个产品
response = client.DeleteAsync(gizmoUri).Result;
Console.WriteLine("{0} ({1})", (int)response.StatusCode, response.ReasonPhrase);
与GET一样,DELETE请求没有请求体,因此你不必指定JSON或XML格式。
错误处理
HttpClient在它接收到一个带有错误代码的HTTP响应时,不会抛出异常。但是,该响应的StatusCode属性含有状态码。而且,如果其状态是一个成功的状态码(状态码在200-299之间)时,IsSuccessStatusCode属性为true。
使用这种模式的前面这个例子为:
HttpResponseMessage response = client.GetAsync("api/products").Result;
if (response.IsSuccessStatusCode)
{
// ....
}
如果你喜欢把错误代码处理成异常,可以调用EnsureSuccessStatusCode方法。这个方法在响应状态不是一个成功的代码时,会抛出一个异常。
try
{
var resp = client.GetAsync("api/products").Result;
resp.EnsureSuccessStatusCode(); // Throw if not a success code.
// ...
}
catch (HttpRequestException e)
{
Console.WriteLine(e.Message);
}
当然,由于其它原因,HttpClient也可能抛出异常 — 例如,请求超时的时候。
配置HttpClient
为了配置HttpClient,可以创建一个WebRequestHandler实例,设置其属性,并把它传递给HttpClient构造器:
WebRequestHandler handler = new WebRequestHandler()
{
AllowAutoRedirect = false,
UseProxy = false
};
HttpClient client = new HttpClient(handler);
WebRequestHandler派生于HttpMessageHandler。通过从HttpMessageHandler派生,你也可以插入自定义消息处理程序。更多信息参阅“HTTP消息处理器(本系列教程的第5.1小节 — 译者注)”。
其它资源
控制台应用程序使我们很容易看到代码流程。然而,阻塞调用对于图形UI的应用程序并不是一种好的做法。要了解如何处理非阻塞HttpClient中的异步操作,参阅“通过WPF应用程序调用Web API”(本系列教程的第3.3小节 — 译者注)。