使用 Casablanca 消耗 REST API






4.86/5 (17投票s)
本文介绍了如何使用 Casablanca REST API 库从 C++ 代码中消耗 REST Web 服务
引言
Casablanca 是微软官方发布的 C++ REST 库,作为一个开源项目在 CodePlex 上发布。我之所以说“官方发布”,是因为它默认不包含在 Visual C++ 中,但也许未来会有所改变。VC++ 团队大力支持该库,并推荐您在所有 REST 访问需求中使用它。
Casablanca 允许您编写原生代码来访问 REST 服务,并采用异步方式来消费基于 HTTP 和 JSON 的服务。有一些扩展允许您使用 Casablanca 编写 Windows 8 应用商店应用,但您也可以在桌面应用中使用它。代码以可移植的方式编写,因此如果您愿意,也可以从 Linux 中使用它。
本文将快速演示一个骨架式的 ASP.NET MVC 4 REST Web 服务,该服务将由使用 Casablanca 进行四种常用 HTTP 操作(GET、POST、PUT 和 DELETE)的 C++ 代码消费。文章还将展示如何解析和创建 JSON,以及如何使用 PPL 扩展来编写异步代码。
骨架式的 Web 服务
我希望示例简单化,因此该服务使用一个非常简单的业务对象,名为 Member
。
public class Member
{
public int Id { get; set; }
public string Name { get; set; }
public string Sport { get; set; }
}
我没有从实际数据库或后端读取和写入数据,而是使用了一个模拟存储类,该类可以添加、编辑、获取和删除 Member
对象。
public class Members
{
private Collection<Member> members = new Collection<Member>();
private int nextId = 1;
public Member Add(string name, string sport)
{
var member = new Member()
{
Id = nextId++,
Name = name,
Sport = sport
};
members.Add(member);
return member;
}
public IEnumerable<Member> GetAll()
{
return members;
}
public Member Get(int id)
{
return members.FirstOrDefault(m => m.Id == id);
}
public Member Update(int id, string name, string sport)
{
var item = Get(id);
if (item != null)
{
item.Name = name;
item.Sport = sport;
return item;
}
return null;
}
public bool Delete(int id)
{
var item = Get(id);
if (item != null)
{
members.Remove(item);
return true;
}
return false;
}
}
这是控制器。
public class ValuesController : ApiController { static ValuesController() { members.Add("Nish", "Tennis"); members.Add("Andrew", "Baseball"); // . . . } private static Members members = new Members(); // GET api/values public IEnumerable<Member> Get() { return members.GetAll(); } // GET api/values/id public Member Get(int id) { return members.Get(id); } // POST api/values public int Post(dynamic data) { return members.Add((string)data.name, (string)data.sport).Id; } // PUT api/values/id public Member Put(int id, dynamic data) { return members.Update(id, (string)data.name, (string)data.sport); } // DELETE api/values/id public bool Delete(int id) { return members.Delete(id); } }
进行 GET
调用并解析 JSON
通常,您需要这些头文件来使用 Casablanca。
#include <http_client.h> #include <ppltasks.h> #include <json.h>
这是业务对象的 C++ 版本。
class Member { public: int Id; std::wstring Name; std::wstring Sport; void Display() { std::wcout << Id << L", " << Name << L", " << Sport << std::endl; } };
我添加了一个 Display
方法用于日志记录/显示。我还添加了一个辅助类,用于在给定 JSON 数据的情况下创建 Member
对象。
enum FieldValue {Id, Name, Sport }; class MemberGenerator { std::map<std::wstring, FieldValue> fieldMap; Member member; public: MemberGenerator() { fieldMap[L"Id"] = FieldValue::Id; fieldMap[L"Name"] = FieldValue::Name; fieldMap[L"Sport"] = FieldValue::Sport; } void SetField(std::wstring name, json::value value) { switch(fieldMap[name]) { case FieldValue::Id: member.Id = value.as_integer(); break; case FieldValue::Name: member.Name = value.as_string(); break; case FieldValue::Sport: member.Sport = value.as_string(); break; } } Member GetMemberFromJson(json::value jsonValue) { for(auto iterInner = jsonValue.cbegin(); iterInner != jsonValue.cend(); ++iterInner) { const json::value &propertyName = iterInner->first; const json::value &propertyValue = iterInner->second; SetField(propertyName.as_string(), propertyValue); } return member; } };
我希望 C++ 像 C#(或者说 .NET)一样拥有反射功能。那样会使编写这段代码更容易、更清晰。但这个版本也足够好了,而且可能在性能方面更好。GetMemberFromJson
方法以 json::value
对象作为其参数。json::value
类基本上是对 JSON 值的一种 C++ 抽象。在我的示例中,这将是一个复合 JSON 值,其中包含 Member
对象的属性。使用 cbegin
和 cend
,我们遍历复合对象。迭代器是一个 std::vector
,其元素是 std::pair
的 json::value
对象。这个 pair 代表属性名及其关联的值。然后,SetField
方法查找属性名,对于每个属性,我们知道其类型,因此调用其中一个 as_xxx()
方法,该方法将 JSON 值转换为请求的类型。显然,在更真实的应用场景中,会存在多层嵌套的业务对象,因此您需要一个更复杂的转换框架,但我认为核心方法会与我在这里所做的非常相似。
这是执行 GET 调用以获取所有对象的代码。这基本上是 GET api/values 的实现。
pplx::task<void> GetAll() { return pplx::create_task([] { http_client client(L"https://:5540/api/values"); return client.request(methods::GET); }).then([](http_response response) { if(response.status_code() == status_codes::OK) { return response.extract_json(); } return pplx::create_task([] { return json::value(); }); }).then([](json::value jsonValue) { if(jsonValue.is_null()) return; MemberGenerator generator; for(auto iterArray = jsonValue.cbegin(); iterArray != jsonValue.cend(); ++iterArray) { const json::value &arrayValue = iterArray->second; auto member = generator.GetMemberFromJson(arrayValue); member.Display(); } }); }
http_client
类,顾名思义,是处理与 Web 服务的 HTTP 连接的核心类。request
方法异步发送 HTTP 请求,我将其指定为 GET 请求。续调函数(continuation)会接收一个 http_response
对象,该对象代表服务器的响应。(*这些方法和类型命名得如此清晰,以至于我觉得重复它们很傻。我的意思是说,像“http_response 类代表一个 HTTP 响应”这样的说法。唉!*)它提供了获取正文、头信息、状态码等的方法。一旦我验证了响应代码是 200,我就会调用 extract_json
方法,这也是异步的。当它完成后,续调函数会接收一个 json::value
对象。在这种情况下,我知道它是一个 JSON 值数组,代表 Member
对象,因此我遍历列表并使用我的对象转换类来提取 Member
对象。这是执行 GET api/values/id 调用的代码。
pplx::task<void> Get(int id) { return pplx::create_task([id] { std::wstringstream ws; ws << L"https://:5540/api/values/" << id; http_client client(ws.str()); return client.request(methods::GET); }).then([](http_response response) { if(response.status_code() == status_codes::OK) { return response.extract_json(); } return pplx::create_task([] { return json::value(); }); }).then([](json::value jsonValue) { if(jsonValue.is_null()) return; MemberGenerator generator; auto member = generator.GetMemberFromJson(jsonValue); member.Display(); }); }
它非常相似,只是 URL 现在包含了要获取的 id,并且 JSON 响应是一个单独的 Member
对象。
提交 POST
这是展示如何将数据 POST 到服务中的代码。
pplx::task<int> Post() { return pplx::create_task([] { json::value postData; postData[L"name"] = json::value::string(L"Joe Smith"); postData[L"sport"] = json::value::string(L"Baseball"); http_client client(L"https://:5540/api/values"); return client.request(methods::POST, L"", postData.to_string().c_str(), L"application/json"); }).then([](http_response response) { if(response.status_code() == status_codes::OK) { auto body = response.extract_string(); std::wcout << L"Added new Id: " << body.get().c_str() << std::endl; return std::stoi(body.get().c_str()); } return 0; }); }
json::value
类重载了 [] operator
,因此您可以使用类似数组的语法来设置数据。在进行 request
调用时,您需要指定 POST,提供要发送的数据,并将 content-type 设置为 application/json。Web 服务会返回新添加对象的 ID,因此 there's code to parse that and return the int
value.
进行 PUT
和 DELETE
调用
PUT 的实现与 POST 非常相似,只是您需要传递一个 ID。
pplx::task<void> Put(int id) { return pplx::create_task([id] { json::value postData; postData[L"name"] = json::value::string(L"Joe Y Smith"); postData[L"sport"] = json::value::string(L"Baseball 2"); std::wstringstream ws; ws << L"https://:5540/api/values/" << id; http_client client(ws.str()); return client.request(methods::PUT, L"", postData.to_string().c_str(), L"application/json"); }).then([](http_response response) { if(response.status_code() == status_codes::OK) { auto body = response.extract_string(); std::wcout << L"Updated: " << body.get().c_str() << std::endl; } }); }
DELETE 也相当简单。
pplx::task<void> Delete(int id) { return pplx::create_task([id] { std::wstringstream ws; ws << L"https://:5540/api/values/" << id; http_client client(ws.str()); return client.request(methods::DEL); }).then([](http_response response) { if(response.status_code() == status_codes::OK) { auto body = response.extract_string(); std::wcout << L"Deleted: " << body.get().c_str() << std::endl; } }); }
这实际上返回一个 bool
,但我没有解析它,只是将其显示到控制台。但您实际上可以对其做任何您想做的事情。此时,将所有这些调用链接在一起并执行类似这样的操作非常简单。
GetAll().then([] { Post().then([](int newId) { Get(newId).then([newId] { Put(newId).then([newId] { GetAll().then([newId] { Delete(newId).then([] { GetAll(); }); }); }); }); }); });
就是这些了。一如既往,请通过本文底部的论坛提供任何反馈或批评。
参考文献
历史
- 2013 年 6 月 6 日 - 文章发布