65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Casablanca 消耗 REST API

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (17投票s)

2013年6月6日

CPOL

4分钟阅读

viewsIcon

104603

本文介绍了如何使用 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 对象的属性。使用 cbegincend,我们遍历复合对象。迭代器是一个 std::vector,其元素是 std::pairjson::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.

进行 PUTDELETE 调用

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 日 - 文章发布
© . All rights reserved.