Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#5.0

C# CodeProject API Wrapper

4.59/5 (8 votes)
3 Mar 2016CPOL8 min read 63.5K   1.1K  
Let us see how to build a simple CodeProject API Wrapper.

This article is an entry in our Microsoft Azure IoT Contest. Articles in this section are not required to be full articles so care should be taken when voting.

Introduction

It all started when one of our beloved CodeProject member "John Simmons / outlaw programmer" posted the below question in Lounge.

http://www.codeproject.com/Lounge.aspx?msg=5006963#xx5006963xx

"Has anyone taken it upon themselves to come up with a c# wrapper around the CodeProject APIs?" I was in fact having a partial implementation of the so called API helpers. His question brought into my attention in building a useful wrapper.

In this article, let us take a look into how we can write a simple wrapper for accessing the CodeProject API.

First take a look into the CodeProject API

https://api.codeproject.com

There are two types of API’s which are exposed to the consumers.

My API – You can use this APIs for accessing the logged in CodeProject user information such as your articles, blogs, messages, questions etc. Every request must have the OAuth token which is being passed as a part of the HTTP Request header as "Authorization" with a value as "Bearer <token>".

Here’s an example to get all my bookmarks with a page size as 1.

https://api.codeproject.com/v1/my/bookmarks?page=1


Other API’s – It’s the generally available contents from CodeProject such as Articles, ForumMessages, and Questions etc. You can access these contents through the token which you have obtained or you can also access the same through registered ClientId and Client Secret.

The first thing you have to do is, register with an application name and obtain the client Id and Secret key which we will be using at a later point of time for MyAPI queries.

https://www.codeproject.com/script/webapi/userclientregistrations.aspx

We will be discussing the below topics.

Background

If you are new to CodeProject API, please take a look into the below link for more understanding on the API.

https://api.codeproject.com

Getting your hands wet in using CodeProject API


Let us get our hands wet before we code the wrapper implementation. Navigate to the below URL for MyApi CodeProject API Sample.

https://api.codeproject.com/Samples/MyApi

The above URL lets you login with your CodeProject member ID and password. After the OAuth authentication, you should be able to see all your profile information.

Here’s a small trick to get the access token. If you are using Google Chrome, you can use inspect element, which will open up the Developer tools. Navigate to "Session Storage" and you will see the Key-Value pair with the key as "accessToken". Copy the value; we need this to test the same in https://www.hurl.it/

AccessToken

Below is the Http GET Request we are making to get my articles with the authorization header set with a value as Bearer <space> <access token>

https://api.codeproject.com/v1/my/articles?page=1

MyArticle-Request

Below is the HTTP response for the same. If you have posted articles to CodeProject, you should see your articles getting listed.

Cache-Control: no-cache
Content-Length: 25291
Content-Type: application/json; charset=utf-8
Date: Sun, 22 Feb 2015 01:11:50 GMT
Expires: -1
Pragma: no-cache
Server: Microsoft-IIS/7.5
X-Aspnet-Version: 4.0.30319
X-Powered-By: ASP.NET

MyArticle-Response

Using the code

Let us get started in implementing the Wrapper to consume the CodeProject API. We will be implementing the other API and My API functionalities and try to get some useful things.
We will start with the list of entities. Below is the CodeProjectRoot object, contains information related to pagination and items. The "items" holds the actual data.

public class CodeProjectRoot
{
        public Pagination pagination { get; set; }
        public List<Item> items { get; set; }
}

Below is the code snippet of "Item" class. You can see below, it holds all the related entities such as Author, Category, Tag, License information etc.

public class Item
{
        public string id { get; set; }
        public string title { get; set; }
        public List<Author> authors { get; set; }
        public string summary { get; set; }
        public string contentType { get; set; }
        public DocType docType { get; set; }
        public List<Category> categories { get; set; }
        public List<Tag> tags { get; set; }
        public License license { get; set; }
        public string createdDate { get; set; }
        public string modifiedDate { get; set; }
        public ThreadEditor threadEditor { get; set; }
        public string threadModifiedDate { get; set; }
        public double rating { get; set; }
        public int votes { get; set; }
        public double popularity { get; set; }
        public string websiteLink { get; set; }
        public string apiLink { get; set; }
        public int parentId { get; set; }
        public int threadId { get; set; }
        public int indentLevel { get; set; }
}

Most of the result set which is returned by the CodeProject API is paged result. Below is the code snippet of pagination class, holds page and item related information.

public class Pagination
{
        public int page { get; set; }
        public int pageSize { get; set; }
        public int totalPages { get; set; }
        public int totalItems { get; set; }
}

Below is the code snippet of Author entity holds the author name and Id.

public class Author
{
        public string name { get; set; }
        public int id { get; set; }
}

Every request to CodeProject API must have an Authorization header with the AccessToken. The access token is nothing but a unique OAuth token returned based on the Client Id and Client Secret Keys.

Get Access Token

Let us build a small helper class named "CodeProjectAccessToken" to get the access token so that we can make use of the same at a later point of time when we are actually making a request to get questions, answers etc. Below is the reused code from CodeProject API Sample.

public async Task<string> GetAccessToken()
{
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(_baseUrl);

                // We want the response to be JSON.
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

                // Build up the data to POST.
                List<KeyValuePair<string, string>> postData = new List<KeyValuePair<string, string>>();
                postData.Add(new KeyValuePair<string, string>("grant_type", "client_credentials"));
                postData.Add(new KeyValuePair<string, string>("client_id", _clientId));
                postData.Add(new KeyValuePair<string, string>("client_secret", _clientSecret));

                FormUrlEncodedContent content = new FormUrlEncodedContent(postData);

                // Post to the Server and parse the response.
                HttpResponseMessage response = await client.PostAsync("Token", content);
                string jsonString = await response.Content.ReadAsStringAsync();
                object responseData = JsonConvert.DeserializeObject(jsonString);

                // return the Access Token.
                return ((dynamic)responseData).access_token;
            }
}

Fetch CodeProject Questions

Now we will be having a look into the code snippet to all the questions based on the tags such as C#, ASP.NET etc. Question mode (Default, Unanswered, Active, New), created data and modified date.

public async Task<List<Item>> GetCodeProjectQuestions(string tags, QuestionListMode mode, 
                                                              DateTime createdDate, DateTime modifiedDate)
{
            _allItems.Clear();
            return await GetQuestions(tags, mode, createdDate, modifiedDate);
}

You can see below the private method called with all the required parameters so that it returns the list of "Item" collection by recursively making a call and trying to include the item if it matches the created and modified date criteria if you specify one.

/// <summary>
/// Gets the page of Questions.
/// </summary>
/// <param name="tags">Tags</param>
/// <param name="createdDate">Created Date</param>
/// <param name="modifiedDate">Modified Date</param>
/// <param name="pageCount">Page Count</param>
/// <returns>List of Items</returns>
private async Task<List<Item>> GetQuestions(string tags, QuestionListMode mode, 
                                                   DateTime createdDate, DateTime modifiedDate, 
                                                   int pageCount = 1)
{
            var questionRoot = await codeProjectApiHelper.GetQuestions(pageCount, mode, tags);
            if (questionRoot.items == null)
                return _allItems;

            foreach (var question in questionRoot.items)
            {
                var questionModifiedDate = DateTime.Parse(question.modifiedDate);
                if (questionModifiedDate.Date != modifiedDate.Date && modifiedDate != DateTime.MinValue)
                {
                    if (questionModifiedDate.Date > modifiedDate.Date)
                        continue;

                    if (questionModifiedDate.Date < modifiedDate.Date)
                        return _allItems;
                }
                var questionCreatedDate = DateTime.Parse(question.createdDate);
                if (questionCreatedDate.Date != createdDate.Date && createdDate != DateTime.MinValue)
                {
                    if (questionCreatedDate.Date > createdDate.Date)
                        continue;

                    if (questionCreatedDate.Date < createdDate.Date)
                        return _allItems;
                }
                _allItems.Add(question);
            }

            if (pageCount > questionRoot.pagination.totalPages) return _allItems;
            
            pageCount++;
            await GetQuestions(tags, mode, createdDate, modifiedDate, pageCount);

            return _allItems;
}

In the above code, you can see the actual call to get the questions is being implemented in "CodeProjectAPIHelper" class. Below is the code snippet for the same.

/// <summary>
/// Gets the page of Questions.
/// </summary>
/// <param name="page">The page to get.</param>
/// <param name="tags">The tags to filter the articles with.</param>
/// <returns>The page of articles.</returns>
public async Task<CodeProjectRoot> GetQuestions(int page, QuestionListMode mode, string tags)
{
            using (var client = new HttpClient())
            {
                SetHttpClientProperties(client);

                // create the URL string.
                var url = string.Format("v1/Questions/{0}?page={1}&include={2}", mode, page, HttpUtility.UrlEncode(tags));
     
                // make the request
                var response = await client.GetAsync(url);

                // parse the response and return the data.
                var jsonResponseString = await response.Content.ReadAsStringAsync();
                return JsonConvert.DeserializeObject<CodeProjectRoot>(jsonResponseString);
            }
}

You can see a method call to set the HttpClient properties. There are couple of things such as base address, accept type and authorization needs to be set. Here’s the code snippet for the same.

private void SetHttpClientProperties(HttpClient client)
{
            client.BaseAddress = new Uri(_baseUrl);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // Add the Authorization header with the AccessToken.
            client.DefaultRequestHeaders.Add("Authorization", "Bearer " + _accessToken);
}

Fetch CodeProject Articles

Now let us see how to fetch CodeProject Articles. Below is the code snippet, shows how to make use of the wrapper class.

// Create an instance of CodeProjectApiWrapper
// It has all the juicy methods, helps in accessing the CodeProject API's

var codeProjectApiWrapper = new CodeProjectApiWrapper(baseUrl, _accessToken);
var articles = await codeProjectApiWrapper.GetMyArticles();

// My Articles
foreach (var article in articles)
{
      Console.WriteLine("Title: {0}", article.title);
      foreach (var author in article.authors)
      {

        Console.WriteLine("Authors: {0}", author.name);
      }
}

Below is the code snippet for fetching the articles. It’s in CodeProjectApiWrapper class. You will notice the code looks similar to the one which we did in getting all questions.

public async Task<List<Item>> GetCodeProjectArticles(string tags,
                                                     DateTime createdDate,
                                                     DateTime modifiedDate,
                                                     double? minRating = null,
                                                     double? minRatingGreaterThanEqual = null)
        {
            _allItems.Clear();
            return await GetArticles(tags, createdDate, modifiedDate, minRating, minRatingGreaterThanEqual);
        }

        /// <summary>
        /// Gets the page of Articles.
        /// </summary>
        /// <param name="tags">Tags</param>
        /// <param name="createdDate">Created Date</param>
        /// <param name="modifiedDate">Modified Date</param>
        /// <param name="minRating">Minimum Rating</param>
        /// <param name="minRatingGreaterThanEqual">Minimum rating greater than or equal to</param>
        /// <param name="pageCount">Page Count, Default = 1</param>
        /// <returns>List of Items</returns>
        private async Task<List<Item>> GetArticles(string tags,
                                                   DateTime createdDate, 
                                                   DateTime modifiedDate, 
                                                   double? minRating = null,
                                                   double? minRatingGreaterThanEqual = null, 
                                                   int pageCount = 1)
        {
            var articleRoot = await codeProjectApiHelper.GetArticles(pageCount, tags);
            if (articleRoot.items == null)
                return _allItems;

            foreach (var article in articleRoot.items)
            {
                var articleModifiedDate = DateTime.Parse(article.modifiedDate);
                if (articleModifiedDate.Date != modifiedDate.Date && modifiedDate != DateTime.MinValue)
                {
                    if (articleModifiedDate.Date > modifiedDate.Date)
                        continue;

                    if (articleModifiedDate.Date < modifiedDate.Date)
                        return _allItems;
                }

                var articleCreatedDate = DateTime.Parse(article.createdDate);
                if (articleCreatedDate.Date != modifiedDate.Date && createdDate != DateTime.MinValue)
                {
                    if (articleCreatedDate.Date > createdDate.Date)
                        continue;

                    if (articleCreatedDate.Date < createdDate.Date)
                        return _allItems;
                }

                if (minRating == null || article.rating != minRating)
                {
                    if (minRatingGreaterThanEqual == null)
                    {
                        _allItems.Add(article);
                    }
                    else
                    {
                        if (article.rating >= minRatingGreaterThanEqual)
                            _allItems.Add(article);
                    }
                }
                else
                    _allItems.Add(article);
            }

            if (pageCount > articleRoot.pagination.totalPages) return _allItems;

            pageCount++;
            await GetArticles(tags, modifiedDate, createdDate, minRating, minRatingGreaterThanEqual, pageCount);

            return _allItems;
}

Fetch Forum Messages

Let us see how we can fetch the forum messages. Note – You will have to specify the forum Id and forum display mode (Messages or Threads).

Below you see, we are passing a hardcoded forum Id and forum display mode as ‘Messages’

var createdDate = DateTime.Parse("2012-07-02");
            Console.WriteLine(string.Format("------ Forum Messages for {0}------", createdDate.ToShortDateString()));

var forumMessages = await codeProjectApiWrapper.GetCodeProjectForumMessages(1159, ForumDisplayMode.Messages,
createdDate, DateTime.MinValue);

foreach (var forumMessage in forumMessages)
{
     Console.WriteLine("Title: {0}", forumMessage.title);
     Console.WriteLine("Date: {0}", forumMessage.createdDate);
}

The code within the Wrapper class is as below.

public async Task<List<Item>> GetCodeProjectForumMessages(int forumId,
                                                                  ForumDisplayMode mode,
                                                                  DateTime createdDate,
                                                                  DateTime modifiedDate)
        {
            _allItems.Clear();
            return await GetForumMessages(forumId, mode, createdDate, modifiedDate);
        }

        /// <summary>
        /// Returns all Forum Messages for the Forum ID, Page Count and Created or Modified Date
        /// </summary>
        /// <param name="forumId">Forum ID</param>
        /// <param name="createdDate">Created Date</param>
        /// <param name="modifiedDate">Modified Date</param>
        /// <param name="pageCount">Page Count</param>
        private async Task<List<Item>> GetForumMessages(int forumId,
                                                        ForumDisplayMode mode,
                                                        DateTime createdDate,
                                                        DateTime modifiedDate,
                                                        int pageCount = 1)
        {
            var forumMessageRoot = await codeProjectApiHelper.GetForumMessages(forumId, mode, pageCount);
            if (forumMessageRoot.items == null)
                return _allItems;

            foreach (var forumMessage in forumMessageRoot.items)
            {
                var forumModifiedDate = DateTime.Parse(forumMessage.modifiedDate);
                if (forumModifiedDate.Date != modifiedDate.Date && modifiedDate != DateTime.MinValue)
                {
                    if (forumModifiedDate.Date > modifiedDate.Date)
                        continue;

                    if (forumModifiedDate.Date < modifiedDate.Date)
                        return _allItems;
                }
                var forumMessageCreatedDate = DateTime.Parse(forumMessage.createdDate);
                if (forumMessageCreatedDate.Date != createdDate.Date && createdDate != DateTime.MinValue)
                {
                    if (forumMessageCreatedDate.Date > createdDate.Date)
                        continue;

                    if (forumMessageCreatedDate.Date < createdDate.Date)
                        return _allItems;
                }
                _allItems.Add(forumMessage);
            }

            if (pageCount > forumMessageRoot.pagination.totalPages) return _allItems;

            pageCount++;
            await GetForumMessages(forumId, mode, createdDate, modifiedDate, pageCount);

            return _allItems;
}

My API

The My API’s are those API’s that provides access to CodeProject members information such as profile, reputations, questions, answers, articles, blogs, tips etc. Now let us take a look into the usage of My API with a specific code samples for the same. The My API response is obtained by making use of a specific piece of reused code.

Below is the code snippet shows the usage on how to get all my articles.

var codeProjectApiWrapper = new CodeProjectApiWrapper(baseUrl, _accessToken);
var articles = await codeProjectApiWrapper.GetMyArticles();

// My Articles
foreach (var article in articles)
{
     Console.WriteLine("Title: {0}", article.title);
     foreach (var author in article.authors)
     {

          Console.WriteLine("Authors: {0}", author.name);
     }
}

Here’s how it internally works. The GetMyArticles method is called with a default page count as one. Further it makes a call to CodeProjectApiHelper method GetMyContent which makes a HTTP GET request to get the article contents.

/// <summary>
/// Returns my articles
/// </summary>
/// <param name="pageCount">Page Count</param>
/// <returns>List of Item</returns>
public async Task<List<Item>> GetMyArticles(int pageCount = 1)
{
     _allItems.Clear();           
     return await GetMyContent(pageCount, MyAPIEnum.Articles);
}

/// <summary>
/// A generic function returns content based on the page count and My API Enum
/// </summary>
/// <param name="pageCount">Page Count</param>
/// <param name="myAPIEnum">My API Enum</param>
/// <returns>List of Item</returns>
private async Task<List<Item>> GetMyContent(int pageCount, MyAPIEnum myAPIEnum)
{
            var rootObject = await codeProjectApiHelper.GetMyContent(pageCount, myAPIEnum);
            if (rootObject.items == null)
                return _allItems;

            foreach (var item in rootObject.items)
            {
                _allItems.Add(item);
            }

            if (pageCount > rootObject.pagination.totalPages) return _allItems;

            pageCount++;
            await GetMyContent(pageCount, myAPIEnum);

            return _allItems;
}

Below is the code snippet of CodeProjectApiHelper method GetMyContent. You can also see below how we are building the URL based on the MyAPIEnum.

Note – The HTTP response for Notification, Profile and Reputation are totally different and we will see next how we are dealing with those.

#region "My API"
        public string GetMyAPIUrl(int page, MyAPIEnum myAPIEnum)
        {
            switch (myAPIEnum)
            {
                case MyAPIEnum.Notifications:
                    return "v1/my/notifications";
                    break;
                case MyAPIEnum.Profile:
                    return "v1/my/profile";
                    break;
                case MyAPIEnum.Reputation:
                    return "v1/my/reputation";
                    break;
            }

            return string.Format("v1/my/{0}?page={1}", myAPIEnum.ToString().ToLower(), page);
        }

        /// <summary>
        /// Returns the response based on the My API Type and page number in most of the cases
        /// </summary>
        /// <param name="page">Page Number</param>
        /// <param name="myAPIEnum">My API Enum</param>
        /// <returns>CodeProjectRoot instance</returns>
        public async Task<CodeProjectRoot> GetMyContent(int page, MyAPIEnum myAPIEnum)
        {
            using (var client = new HttpClient())
            {
                SetHttpClientProperties(client);

                // make the request
                var response = await client.GetAsync(GetMyAPIUrl(page, myAPIEnum));

                // parse the response and return the data.
                var jsonResponseString = await response.Content.ReadAsStringAsync();
                return JsonConvert.DeserializeObject<CodeProjectRoot>(jsonResponseString);
            }
        }
#endregion

My Questions, Answers, Tips, Blogs, Bookmarks all work in a similar way as fetching of My Articles works.
The code for fetching the notifications, profile and reputation information for the My API is all done by using a specific set of classes for de-serializing the HTTP Response.

My Notification

We need to have a specific set of entities for desterilizing the JSON responses for notification. Below is the code snippet for the notification entities.

public class Notification
{
        public int id { get; set; }
        public string objectTypeName { get; set; }
        public int objectId { get; set; }
        public string subject { get; set; }
        public string topic { get; set; }
        public string notificationDate { get; set; }
        public bool unRead { get; set; }
        public string content { get; set; }
        public string link { get; set; }
}

public class NotificationRoot
{
        public List<Notification> notifications { get; set; }
}

Below is the code snippet where you can see how we are making a HTTP GET Request to the URL that is built by reusing the code GetMyAPIUrl, notice below we are passing "-1" for page count as it’s not really being use for notifications.

Once we get the JSON response, we will be desterilizing the same by using NotificationRoot class.

public async Task<NotificationRoot> GetMyNotifications()
{
            using (var client = new HttpClient())
            {
                codeProjectApiHelper.SetHttpClientProperties(client);

                // make the request
                // we don't have a page count for this. Just pass a dummy value
                var response = await client.GetAsync(codeProjectApiHelper.GetMyAPIUrl(-1, MyAPIEnum.Notifications));

                // parse the response and return the data.
                var jsonResponseString = await response.Content.ReadAsStringAsync();
                return JsonConvert.DeserializeObject<NotificationRoot>(jsonResponseString);
            }
}

My Reputation

Let us see how to fetch the reputation points. Below is the code snippet where we do a console print for the Total reputation points.

// My Reputation
Console.WriteLine("Get all my Reputation");
var reputation = await codeProjectApiWrapper.GetMyReputation();

Console.WriteLine("Total Points: "+reputation.totalPoints);

Here are the set to entities required for de-serializing the reputation JSON response.

public class ReputationType
{
      public string name { get; set; }
      public int points { get; set; }
      public string level { get; set; }
      public string designation { get; set; }
}

public class Reputation
{
     public int totalPoints { get; set; }
     public List<ReputationType> reputationTypes { get; set; }
     public string graphUrl { get; set; }
}

The code for making the HTTP GET Request for getting the reputation points looks almost similar to that of notifications. You can see below, how we are de-serializing the JSON response by utilizing the Reputation entity.

/// <summary>
/// Returns my reputation
/// </summary>
/// <returns>Returns a Reputation instance</returns>
public async Task<Reputation> GetMyReputation()
{
            using (var client = new HttpClient())
            {
                codeProjectApiHelper.SetHttpClientProperties(client);

                // make the request
                // we don't have a page count for this. Just pass a dummy value
                var response = await client.GetAsync(codeProjectApiHelper.GetMyAPIUrl(-1, MyAPIEnum.Reputation));

                // parse the response and return the data.
                var jsonResponseString = await response.Content.ReadAsStringAsync();
                return JsonConvert.DeserializeObject<Reputation>(jsonResponseString);
            }
}

My Profile

At last, let us see how to fetch the profile information. Below is the code snippet for the same.

// My Profile
Console.WriteLine("Get all my Profile");
var profileInfo = await codeProjectApiWrapper.GetMyProfile();

Console.WriteLine("Display Name: " + profileInfo.displayName);
Console.WriteLine("Company: " + profileInfo.company);

Here’s the Profile entity that we will be using it to de-serialize the JSON response for profile.

public class Profile
{
        public int id { get; set; }
        public string userName { get; set; }
        public string displayName { get; set; }
        public string avatar { get; set; }
        public string email { get; set; }
        public bool htmlEmails { get; set; }
        public string country { get; set; }
        public string homePage { get; set; }
        public int codeProjectMemberId { get; set; }
        public string memberProfilePageUrl { get; set; }
        public string twitterName { get; set; }
        public string googlePlusProfile { get; set; }
        public string linkedInProfile { get; set; }
        public string biography { get; set; }
        public string company { get; set; }
        public string jobTitle { get; set; }
}

Points of Interest

There’s always something new I’m learning and this time, I was really happy about building a generic extendable wrapper for CodeProject API that one can use it in no time without having to understand much about the internals. Although it’s a simply HTTP GET Requests and we de-serialize the response to appropriate type, few instance where I felt like there could be some improvements in the API for example, the question that I have posted below for considering the created date and modified date as filter parameters while making the request would greatly help a lot of looping that we are doing within the code that you might have already noticed and wondered is it at required? I would say, yes at this point. But I am sure there will be more improvements based on our feedback.

http://www.codeproject.com/Messages/5000498/Regarding-GetArticles.aspx

Currently there is one TODO item – handling unauthorized access exception(occurs when you are using a invalid credentials or access token) and letting the user know about the this exception.

References

Please take a look into https://api.codeproject.com to understand more about CodeProject API.

History

Version 1.0 - Coding the API Wrapper with the code smaple to fetch CodeProject and My API related info - 02/22/2015.

Version 1.1 - Updated solution, created a portable library and referenced the same in console app - 02/22/2015

License

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