Click here to Skip to main content
15,885,216 members
Articles / Programming Languages / C++
Article

Using Casablanca to consume a REST API

Rate me:
Please Sign up or sign in to vote.
4.86/5 (17 votes)
6 Jun 2013CPOL4 min read 101.4K   36   12
The article walks through using the Casablanca REST API library to consume a REST web service from C++ code

Introduction

Casablanca is the quasi-official C++ REST library from Microsoft published as an open source project on CodePlex. And I say "quasi" as it does not come with Visual C++ by default, but for all you know that may change in future. The VC++ team has backed it strongly and has recommended using it for all your REST access requirements.

Casablanca allows you to write native code to access REST services and uses an asynchronous approach to consuming HTTP and JSON based services. There are extensions that allow you to write Windows 8 store applications in Casablanca, but you can use it in desktop apps as well. The code is written in a portable manner, so you can use it from Linux too, should you want to.

This article quickly demonstrates a skeletal ASP.NET MVC 4 REST web service that is consumed by C++ code that uses Casablanca to do the four common HTTP operations - GET, POST, PUT, and DELETE. It also shows how to parse and create JSON, and also how to use the PPL extensions to write asynchronous code.

The skeletal web service

I wanted to keep the example simple, so this service uses a very simple business object called Member.

C#
public class Member
{
    public int Id { get; set; }

    public string Name { get; set; }

    public string Sport { get; set; }
}

Instead of reading and witting from an actual database or backend, I have a mock storage class that can add, edit, fetch, and delete Member objects.

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

And here's the controller.

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

Making GET calls and parsing JSON

Typically, these are the include files you'd need to use Casablanca.

#include <http_client.h> 
#include <ppltasks.h>
#include <json.h>

Here's the C++ version of the business object.

class Member
{
public:
  int Id;
  std::wstring Name;
  std::wstring Sport;

  void Display()
  {
    std::wcout << Id << L", " << Name << L", " << Sport << std::endl;
  }
};

I added a Display method for logging/display purposes. I also added a helper class to create a Member object given JSON data.

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

I wish C++ had reflection the way C# has (or rather .NET has). That'd have made writing this much easier and cleaner. But this is close enough and is probably better from a performance perspective. The GetMemberFromJson gets a json::value object as its argument. The json::value class is basically a C++ abstraction over a JSON value. In my example, this will be a composite JSON value that contains the properties of the Member object. Using cbegin and cend, we iterate through the composite object. The iterator is an std::vector of an

std::pair
of json::value objects. The pair represents the property name and its associated value. The SetField method then looks up the property name and for each property, we know the type and thus call one of the as_xxx() methods which converts the JSON value into the requested type. Obviously, a more real world scenario would have multi-level nested business objects, so you'd need a more involved conversion framework, but I would imagine that the core approach would remain very similar to what I did there.

Here's the code that does a GET call to get all objects. This is basically the implementation for GET api/values.

pplx::task<void> GetAll()
{
  return pplx::create_task([]
  {
    http_client client(L"http://localhost: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();
    }
  });
}

The http_client class, rather unsurprisingly named, is the core class that handles the HTTP connection to the web service. The request method sends the HTTP request asynchronously, and I've specified this to be a GET request. The continuation gets an http_response object that represents the response from the server. (These methods and types are so lucidly named that I feel like an idiot repeating things. I mean saying things like - the http_response class represents an HTTP response. Oh well!) It's got methods to get the body, headers, status code, etc. Once I verify that the response code was 200, I call the extract_json method, also asynchronous.  When that's completed, the continuation receives a

json::value
object.  In this case, I know it's an array of JSON values representing Member objects, and so I iterate through the list and extract the Member objects using my object conversion class. Here's the code that does a GET api/values/id call.

pplx::task<void> Get(int id)
{
  return pplx::create_task([id]
  {
    std::wstringstream ws;
    ws << L"http://localhost: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();   
  });
}

It's quite similar except the URL now includes the id to fetch and the JSON response is for a single Member object.

Submitting a POST

Here's code showing how to POST data to the service.

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"http://localhost: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;
  });
}

The json::value class has overloaded [] operators, so you can use an array-like syntax to set data. When making the request call, you need to specify POST, provide the data to sent, and set the content-type to application/json. The web service returns back the ID of the newly added object, so there's code there to parse that and return the int value.

Making PUT and DELETE calls

The PUT implementation is very similar to POST, except you pass an 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"http://localhost: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;
    }
  });
}

The DELETE is fairly simple too.

pplx::task<void> Delete(int id)
{
  return pplx::create_task([id]
  {
    std::wstringstream ws;
    ws << L"http://localhost: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;
    }
  });
}

This actually returns a bool, but I am not parsing it and merely displaying it to the console. But you can really do whatever you want with it. At this point, it's fairly easy to chain all these calls together and do something like this.

GetAll().then([] 
{
  Post().then([](int newId)
  {
    Get(newId).then([newId]
    {
      Put(newId).then([newId]
      {
        GetAll().then([newId]
        {
          Delete(newId).then([]
          {
            GetAll();
          });
        });
      });
    });     
  });
});

That's all. As usual, do post any feedback or criticism via the forum at the bottom of this article.

References

History

  • June 6th, 2013 - Article published

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
Nish Nishant is a technology enthusiast from Columbus, Ohio. He has over 20 years of software industry experience in various roles including Chief Technology Officer, Senior Solution Architect, Lead Software Architect, Principal Software Engineer, and Engineering/Architecture Team Leader. Nish is a 14-time recipient of the Microsoft Visual C++ MVP Award.

Nish authored C++/CLI in Action for Manning Publications in 2005, and co-authored Extending MFC Applications with the .NET Framework for Addison Wesley in 2003. In addition, he has over 140 published technology articles on CodeProject.com and another 250+ blog articles on his WordPress blog. Nish is experienced in technology leadership, solution architecture, software architecture, cloud development (AWS and Azure), REST services, software engineering best practices, CI/CD, mentoring, and directing all stages of software development.

Nish's Technology Blog : voidnish.wordpress.com

Comments and Discussions

 
Generalvery nice! Pin
Member 1378489217-Apr-18 14:37
Member 1378489217-Apr-18 14:37 
QuestionSource Code Pin
Member 1062882323-Feb-17 23:38
Member 1062882323-Feb-17 23:38 
AnswerRe: Source Code Pin
column.column1-Jul-17 21:30
column.column1-Jul-17 21:30 
AnswerRe: Source Code Pin
ychuangcp7-Jan-19 8:48
ychuangcp7-Jan-19 8:48 
AnswerRe: Source Code Pin
jpc347-Feb-19 2:14
jpc347-Feb-19 2:14 
GeneralMy vote of 5 Pin
Ahmed Ben Salem12-Oct-16 2:45
Ahmed Ben Salem12-Oct-16 2:45 
GeneralRe: My vote of 5 Pin
Nish Nishant12-Oct-16 2:52
sitebuilderNish Nishant12-Oct-16 2:52 
Questionjson and casablanca Pin
Member 1195001931-Aug-15 23:22
Member 1195001931-Aug-15 23:22 
QuestionHow to use C++ REST SDK with ANSI charset ? Pin
Nacereddine25-Feb-15 3:38
professionalNacereddine25-Feb-15 3:38 
SuggestionNice Intro - Part2? Pin
JoeCane21-Jun-13 6:10
JoeCane21-Jun-13 6:10 
GeneralRe: Nice Intro - Part2? Pin
Nish Nishant21-Jun-13 13:21
sitebuilderNish Nishant21-Jun-13 13:21 
GeneralRe: Nice Intro - Part2? Pin
JoeCane24-Jun-13 5:20
JoeCane24-Jun-13 5:20 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.