Showing posts with label REST. Show all posts
Showing posts with label REST. Show all posts

2019-12-19

Retrieve SharePoint Online List by Graph API

Last day in 2019, and last post in 2019.

Actually pulling SharePoint List from Power Query/Power BI is much faster and straightforward. But in some cases, you may still need to follow the traditional ETL way. Hope below script can help you if you need it.

Firstly we need to register the app in Azure AD (Check Here). After create your app, write down 3 ids: tenant id, client id, and the secret. Note the secret will only be exposed to you when you create the app, then it will be masked (though you can always create a new one.)


To allow your app to access the SharePoint list, you need to enable the "Site.Read.All" permission under the Application Permission category, as demonstrated below. Keep in mind though, the "Site.Read.All" permission needs AAD admin's consent.




That is all about the app setup. To query the list, we need to exchange the token by our tenant id, client id, and the secret:

 public class RespToken
        {
            public string token_type { get; set; }
            public int expires_in { get; set; }
            public int ext_expires_in { get; set; }
            public string access_token { get; set; }
        }

        public static string GetAccessToken(string tenentId, string clientId, string clientSecret)
        {
            string token = string.Empty;

            string uriToken = string.Format("https://login.microsoftonline.com/{0}/oauth2/v2.0/token", tenentId);
            string contentType = "application/x-www-form-urlencoded";
            string grantType = "client_credentials";
            string scope = "https://graph.microsoft.com/.default";

            string tmpBody = "grant_type={0}&client_id={1}&client_secret={2}&scope={3}";
            string body = string.Format(tmpBody, grantType, clientId, clientSecret, scope);
            byte[] contents = Encoding.UTF8.GetBytes(body);

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uriToken);
            request.ContentType = contentType;
            request.Method = "POST";

            request.ContentLength = contents.Length;
            using (Stream writeStream = request.GetRequestStream())
            {
                writeStream.Write(contents, 0, contents.Length);
            }

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                using (Stream readStream = response.GetResponseStream())
                {
                    string responseBody = string.Empty;
                    using (StreamReader sr = new StreamReader(readStream))
                    {
                        responseBody = sr.ReadToEnd();
                    }
                    JavaScriptSerializer Serializer = new JavaScriptSerializer();
                    var r = Serializer.Deserialize(responseBody);
                    token = r.access_token;
                }
            }
            
            return token;
        }




Then we follow the routine "search site => search list => get list item":

static void Main(string[] args)
        {

            string tenentId = string.Empty;
            string clientId = string.Empty;
            string clientSecret = string.Empty;
            string siteName = string.Empty;

            string siteId = string.Empty;
            string listId = string.Empty;


            Console.WriteLine("Tenent Id: ");
            tenentId = Console.ReadLine();

            Console.WriteLine("Client Id: ");
            clientId = Console.ReadLine();

            Console.WriteLine("Client Secret: ");
            clientSecret = Console.ReadLine();

            if (string.IsNullOrEmpty(tenentId) || string.IsNullOrEmpty(clientId) || string.IsNullOrEmpty(clientSecret))
            {
                Console.WriteLine("empty value found in mandatory parameters");
                return;
            }

            string token = GetAccessToken(tenentId, clientId, clientSecret);
            Console.WriteLine("Token acquired, query site");
            

            Console.WriteLine("Input site name for search: ");
            siteName = Console.ReadLine();

            string uriSite = $"https://graph.microsoft.com/v1.0/sites?search={siteName}";

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uriSite);
            request.Method = "GET";
            request.Headers.Add(HttpRequestHeader.Authorization, token);

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                using (Stream s = response.GetResponseStream())
                {
                    using (StreamReader r = new StreamReader(s))
                    {
                        string contents = r.ReadToEnd();
                        var j = JObject.Parse(contents);
                        siteId = j["value"]
                            .FirstOrDefault(x => x["name"].ToString().ToLower() == "msgraph")["id"]
                            .ToString();

                    }
                }
            }

            if (string.IsNullOrEmpty(siteId))
            {
                Console.WriteLine("error happens, cannot obtain site id");
                return;
            }


            Console.WriteLine($"site id acquired, {siteId}");
            string uriList = $"https://graph.microsoft.com/v1.0/sites/{siteId}/lists";
            request = (HttpWebRequest)WebRequest.Create(uriList);
            request.Method = "GET";
            request.Headers.Add(HttpRequestHeader.Authorization, token);

            using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
            {
                using (Stream s = response.GetResponseStream())
                {
                    using (StreamReader sr = new StreamReader(s))
                    {
                        string content = sr.ReadToEnd();
                        var jObject = JObject.Parse(content);

                        listId = jObject["value"]
                            .FirstOrDefault(x => x["name"].ToString().ToLower() == "events")["id"]
                            .ToString();
                    }
                }
            }

            if (string.IsNullOrEmpty(listId))
            {
                Console.WriteLine("error occurred, cannot obtains list id");
                return;
            }

            Console.WriteLine($"list id: {listId}");

            Console.ReadLine();

        } 



2019-12-16

Power Query for Twitter API

I am lazy, so let's make it quick. Looping twitter could be a bit trouble so I put power query script (for searching a specific account by timeline API) below. Hope it helps

1. A function to get token

let
    fnGetToken = () =>
        let 
            key = "Basic " & Binary.ToText(Text.ToBinary("{you should know what it is}"),0),
            url = "https://api.twitter.com/oauth2/token",
            getJson = Web.Contents(url,
                [
                    Headers = [#"Authorization"=key,#"Content-Type"="application/x-www-form-urlencoded;charset=UTF-8"],
                    Content = Text.ToBinary("grant_type=client_credentials") 
                ]
            ),
            j = Json.Document(getJson),
            token = j[access_token],
            tokenHeader = "bearer " & token
        in tokenHeader
in
    fnGetToken


2. Another function to get the max twitter id regarding the topic

let
 fnGetMaxId = (token) =>
 let 
  GetJsonQuery = Web.Contents("https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name={this is the account}&count=1",
   [
    Headers = [#"Authorization"=token]
   ]
  ),
  FormatAsJsonQuery = Json.Document(GetJsonQuery),
  t = Table.FromList(FormatAsJsonQuery, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
  maxId = Table.ExpandRecordColumn(t, "Column1", {"id_str"}, {"maxid"})
 in 
  maxId
in
 fnGetMaxId


3. the 3rd function to get result by token and endpoint. (technically you don't need it, just to make the main function easy to read)

let
 fnGetList = (token, endpoint) =>
  let 
   req = Web.Contents(endpoint,
    [
     Headers = [#"Authorization"=token]
    ]
   ),
   json = Json.Document(req)
  in 
   json
in
 fnGetList


4. finally the main query (note suggest to have a initial Id to limit the size of the result)

let
 fn = (initId) =>
  let
   token = fnGetTokenHeader(),
   I = if initId = "" then fnGetMaxId(token){0}[maxid] else initId,
   endPoint = "https://api.twitter.com/1.1/statuses/user_timeline.json?screen_name={this is the account}&max_id=" & I & "&count=200",
   records = fnGetResults(token, endPoint),
   tbl = Table.FromList(records, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
   tblMaxIds = Table.ExpandRecordColumn(tbl, "Column1", {"id_str"}, {"maxid"}),
   minId = try List.Min(tblMaxIds[maxid]) otherwise null,
   recordSet = [records = records, nextId = minId]
  in
   recordSet,
 resultSet = List.Generate(
                    () => fn(""),
                    each Number.FromText(_[nextId]) >= 1000000000000000000, //an max id you want to put to limit result size, you should get a value by calling getMaxId function, otherwise it could return too many lists
     // each _[nextId] <> null,
                    each fn(_[nextId]),
                    each [nextId = _[nextId], records = _[records]]
                    )
in
    resultSet



2018-10-28

Copy Azure Blob via Rest API

Recently I was requested to work on a data procedure. Basically what I need to do is using SSIS to copy files between different Azure blob storage folders (allow me to use this term, I know there is no such concept in Azure blob. It is just for simplicity :P ).

It is a quite simple task if you can reference Microsoft.WindowsAzure.Storage dll in your package. But in my case the SSIS server doesn't have it. So I decided to use Azure Blob Rest API -- independent to the host environment.

The Azure Blob API's office document gives us a very clear explanation. To call the API, we need to identify the resource, create authorization header, and then issue the web request. But the problem is, I couldn't find any resource to tell you how to create the authorization header for copy. So if you are facing the same problem, continue to section below...

The first step is to create a helper method to generate authorization header, very standard implementation:


        public static String SignThis(String stringToSign, string accountKey, string accountName)
        {
            String signature = string.Empty;
            byte[] unicodeKey = Convert.FromBase64String(accountKey);
            using (HMACSHA256 hmacSha256 = new HMACSHA256(unicodeKey))
            {
                Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(stringToSign);
                signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
            }

            String authorizationHeader = String.Format(
                  CultureInfo.InvariantCulture,
                  "{0} {1}:{2}",
                  "SharedKey",
                  accountName,
                  signature);

            return authorizationHeader;
        }


The second step is to declare our variables


            string accountKey = "my acccount key";
            string accountName = "my account name";
   
            string srcBlob = "source blob name";
            string destBlob = "destination blob name";
   
            string containerName = "my blob container name";
   
            string method = "PUT";
            DateTime dt = DateTime.UtcNow;
   
            string reqUrl = @"https://" + accountName + ".blob.core.windows.net/" + containerName + "/" + destBlob;
            string copySource = @"https://" + accountName + ".blob.core.windows.net/" + containerName + "/" + srcBlob;
            string canonicalizedHeaders = string.Format("/{0}/{1}/{2}", accountName, containerName, destBlob);



Here is a very important step: we need to create the string for sign. Each line in this string has different meaning, and you can find detailed explanation here. Fortunately we just need to populate mandatory components, like this:


            string StringToSign = String.Format(
                method + "\n"
                + "\n" // content encoding
                + "\n" // content language
                + "\n" // content length
                + "\n" // content md5
                + "\n" // content type
                + "\n" // date
                + "\n" // if modified since
                + "\n" // if match
                + "\n" // if none match
                + "\n" // if unmodified since
                + "\n" // range
                + "x-ms-copy-source:" + copySource + "\nx-ms-date:" + dt.ToString("R") + "\nx-ms-version:2015-04-05\n"
                + canonicalizedHeaders);


Notice here the version I used is 2015-04-05. Technically you should be able to use any version Microsoft provided, e.g. the latest is 2018-03-28.

Now we have the string, then we only need to sign this string, and issue the web request


            string auth = SignThis(StringToSign, accountKey, accountName);

            Uri reqUri = new Uri(reqUrl);

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(reqUri);
            request.Method = method;
            request.Headers.Add("x-ms-date", dt.ToString("R"));
            request.Headers.Add("x-ms-version", "2015-04-05");
            request.Headers.Add("Authorization", auth);
            request.ContentLength = 0;
            request.Headers.Add("x-ms-copy-source", copySource);

            using (var response = request.GetResponse())
            {
                var status = ((HttpWebResponse)response).StatusCode;

                if (status != HttpStatusCode.Accepted)
                {
                    Dts.TaskResult = (int)ScriptResults.Failure;
                }
                else
                {
                    Dts.TaskResult = (int)ScriptResults.Success;
                }
            }


Enjoy :)

2018-09-25

Power BI/Power Query: Load JIRA Issues in Parallel

For nearly a year I haven't done any posting, as I was doing some works not relevant to SQL things, AR/MR with Unity, Machine Learning/Deep Learning, and Flask development. Now I am back to data related works so hopefully I will restart blogging my data experience. Let's see what we can do today.

Due to performance issue, JIRA made the decision to limit amount of records returned from its Rest API to 100 (here). But what if we are working on a large number of issues? like this



So we need to make the API call for more than 200 times. So depends on business requirement, the Power BI report could be just too long to refresh.

An alternative way to handle large amount of issues is we call the API in parallel. To do that, firstly let's create a basic function ufnGetJson to call the API (replace the Organization place holder with correct value):

 (nStartAt) =>  
     let   
       query = Json.Document(Web.Contents("https://{organization}.atlassian.net/rest/api/2/search?maxResults=100&startAt=" & Number.ToText(nStartAt)))  
     in  
       query  


Then based on this ufnGetJson function, we can get total amount of issues and split the stream to multiple batches (5 batches in below example)

 ufnGetRange  
 (n) =>  
     let  
       tmpResult = ufnGetJson(0),  
       totalItem = tmpResult[total],  
       cnt = totalItem / 5,  
       returnRange = {(n-1) * Number.RoundDown(cnt)..n * Number.RoundUp(cnt)}    
     in  
       returnRange  


Code listed above provides us with a boundary of the issue numbers we need to call. Now we just need to create 5 datasets and call the function separately, but keep in mind, we need to provide different n value. e.g. to get the first batch we can have the query like below

 let  
   rng = ufnGetRange(1),  
   startAt = List.Min(rng),  
   boundary = List.Max(rng),  
   pages = {Number.RoundDown(startAt/100)..Number.RoundDown(boundary/100)},  
   pageRange = List.Transform(pages, each _ * 100),  
   paginated = List.Transform(pageRange, each ufnGetJson(_)),  
   #"Converted to Table" = Table.FromList(paginated, Splitter.SplitByNothing(), null, null, ExtraValues.Error)  
 in  
   #"Converted to Table"  

The last step is simply creating a new dataset by combining all 5 datasets into one.


 let  
   combined = Table.Combine({#"Issue Batch 1", #"Issue Batch 2", #"Issue Batch 3", #"Issue Batch 4", #"Issue Batch 5"}),  
      ....  
      ....  
      ....  




At my end this approach improves loading speed a lot, but obviously it depends on connection capacity. Let me know if there is any problem :)

2017-11-28

API Response Pagination - SSIS

In my previous posts, I demonstrated how to paginate API response in Power Query. But what if we need to do a pagination via SSIS? As far as I know, at this stage there is no generic Rest connector available in SSIS. Therefore, we have to use script component/task to read response from the Rest API.

To show you the approach in a quick way, let's write some codes to replicate the scenario I demonstrated in this post. Firstly I created below function:

private static string callAPI(HttpClient client, string key, string endPoint)
    {
        string jsonBody;

        var header = new MediaTypeWithQualityHeaderValue("application/json");
        client.DefaultRequestHeaders.Accept.Add(header);

        client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Key", key);

        HttpResponseMessage response = client.GetAsync(endPoint).Result;

        jsonBody = response.Content.ReadAsStringAsync().Result;

        return jsonBody;
    }


Clearly, this function will extract response body from the Rest API (here we assume the API uses HTTP header "Key" for authorization purpose).

So now we have the base method to call the API, what we need to do is creating a recursive routine, so that we can read the next page until nothing returns:

            bool tryNextPage = true;

            using (HttpClient client = new HttpClient())
            {
                while (tryNextPage)
                {
                    string jBody = callAPI(client, key, endPoint);

                    if (jBody.Length > 50)
                    {
                        ResponseBuffer.AddRow();
                        ResponseBuffer.JsonBody.AddBlobData(System.Text.Encoding.UTF8.GetBytes(jBody));

                        int page = int.Parse(endPoint.Substring(endPoint.Length - 1, 1));
                        
                        endPoint = endPoint.Substring(0, endPoint.Length - 1) + (page + incremental).ToString();
                    }
                    else
                    {
                        tryNextPage = false;
                    }
                    
                }

                
            }


As you can see, I defined a Boolean variable to control the While loop. And in each loop, I read length of the Json response to determine if we should read next page.

Simple approach, isn't it? Though it looks like we have to write more lines of code when compare to power query, SSIS does have its own advantages, such as data quality control. Until my next post, enjoy the pagination.

2017-10-01

Power BI/Power Query: API Response Pagination Part 3 - Missing Indicator

This is the 3rd post about pagination in Power BI/Power Query.

In part 1 and part 2 we have discussed how to paginate Restful response if the response contains a total item Count or a next page Token. So what if the response only returns us records, without any indicator about the next page?

Let's take a look on Basecamp todo API. If we call the API at the end point /api/v1/projects/{project id}/todos.json?page=1, the begin of the response and the end of the response look like below screenshots:

begin of the response
end of the response


Sorry for the messy screenshots, but the point is, this API doesn't give us a token for next page. We have to query the next page to ensure our API call pulls all records we need. You might have hundreds of ways to do the job, but my first impression is let's create a recursive function. Let's think about the problem:

1. We receive the first page from the response and merge the records into a table "T";
2. Then we move on to the next page
2.1 If the next page contains records then we repeat step 1 (merge records into "T") and step 2
2.2 If there is no records in the next page, then we stop and return the table "T"

It is simply a classic recursive scenario. In Power Query, the recursive function is all about the "@". Let's look at the codes


let
    ufnQuery = (n) =>
        let
            jsonDoc= Json.Document(
                        Web.Contents(
                            "https://basecamp.com/{domain}/api/v1/projects/{project id}/todos.json?page=" & Number.ToText(n), 
                            [Headers=[Authorization="Basic {the key}"]]
                        )
        ),
        tmpTbl = Table.FromList(jsonDoc, Splitter.SplitByNothing(), null, null, ExtraValues.Error)
    in
        tmpTbl,

    fnRecursive = (tbl, n) =>
        if Table.RowCount(ufnQuery(n)) > 0 then @fnRecursive(Table.Combine({tbl, ufnQuery(n)}), n+1) else tbl,

    tbl = ufnQuery(1),
    result = fnRecursive(tbl, 2)  
in
   result


As you can see, the key function in codes above is the 2nd function, in the function body, we check amount of records returned from the call, and if the amount is greater than 0, we recursively (@) call the function by passing the merged table and next page number. Simple enough, right?










2017-09-17

Power BI/Power Query: API Response Pagination Part 2 - Paginate by Page Cursor

This is the 2nd post of the topic API Response Pagination in Power BI/Power Query. You can find the first post here.

In the first post we have seen an API pagination example in Power Query, in which the API response provides us with the total record count, then we can calculate amount of pages we need to query. So today let's see another typical scenario in API response: the response contains an cursor pointing to the next page. (Here the cursor could be a direct link brings you to the next page, or it could be a page token you need to apply to your next API call).

The example I demonstrated today is Youtube Search API, and the channel I am going to search is VicRoad (as this post is focusing on result pagination, I am not going to explain how to prepare the API call :P). Below is a well-formatted Json response returned from the call. As you can see, the key field (the cursor) we are going to use is "nextPageToken".



Similar to the last post, the first step is preparing a base call function, as shown below. However, this time we return a list, which contains the result set of the current call, as well as the nextPageToken field we can use for drilling calls.



Now here is the core function of today's topic: List.Generate(). Basically you can treat this function as the FOR loop: we initialize a variable, give it a stop condition, specify the incremental action to the variable, and finally populate the return list result. Below is what I wrote:

So what we did is quite clear now: we create a helper function to call the API; Inside the List.Generate() function, we call the function to repeatedly populate records on different pages by using nextPageToken value. So what we need to do is just turning the result to table? Hold on for one second!

Let's review this piece of codes. So initially we call the API without page token, and the return result set contains records from page 1 and token for page 2, hence the second call gives us records from page 2 and token for page 3...


Initial TokenResultsetNext Token
""page 1page 2
page 2page 2page 3
page 3page 3page 4
page 4page 4page 5
.........
last pagelast pagenull

Now you see the problem: our condition for generating the list is "for each NOT NULL nextPageToken". We haven't gone through the last page!

Fixing the problem is quite simple, we can just manually query the last page, and then combine the result sets:

Put all together:

let    
    ufnGetList = (pgToken) =>
        let 
            result = Json.Document(
                        Web.Contents("https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=UCkT7-rjW_Foojt0ZRtbXMdA&maxResults=50&type=video&key=[put your api key here]&pageToken=" & pgToken)
                        ),
            nextPageToken = try result[nextPageToken] otherwise null,
            items = result[items],
            record = [items = items, nextPageToken = nextPageToken]
        in 
            record,

    resultSet = List.Generate(
                    () => ufnGetList(""),
                    each _[nextPageToken] <> null,
                    each ufnGetList(_[nextPageToken]),
                    each [nextPageToken = _[nextPageToken], items = _[items]]
                    ),

    lastPageToken = List.Last(Table.FromRecords(resultSet)[nextPageToken]),
    lastResultSet = ufnGetList(lastPageToken)[items],

    fullResultSet = Table.Combine({Table.FromRecords(List.Combine(Table.FromRecords(resultSet)[items])), Table.FromRecords(lastResultSet)})
    
    
in
    fullResultSet


Now Run the query we should have all the records we need.



2017-09-03

Power BI/Power Query: API Response Pagination Part 1 - Paginate by Item Count

It has been a while since my last post, lots of things happened in these few month, changed my career path, obtained my certification... Finally I got a chance to sit down and restart the blog. So today's topic is pagination in REST API response.

As you can see in the past I had done a few posts of how to consume API results in SSIS via script components, such as this post. But in Power BI, it might not be as easy as what we did in the script component, because there is no direct way to do a loop when reading paginated response.

Generally there are two types of paginated result set:

-- Good: A Json response contains an indicator to provide you with the information of the next page, it could be a link for the next page, or it could be a token for you to issue the next call, or it could be a total item count so you need to do a calculation of how many pages you need to go through. This is today's topic.

-- Cursed: A Json result set does not have pagination information, so you need to query next page every time until the the response gives us nothing.

So, let's prepare today's post here. To demonstrate the logic, I am using the v3 SET (/api/v3/lego/sets) API from rebrickable. The API itself is quite simple, but you need to create an account, then generate a API key from your account profile to call the API.  It is a simple process so I won't cover it here.

After we obtain the API key, it is time to open the Power Query window in Power BI



From opened query window, select Web as the data source



In the pop up window, select Advanced option, and then input API endpoint and key value, as you can see below, I set page size to 100, and the initial page is 1.


Click the OK button and the query should run automatically and give you the result. As you can see the response is quite clear: it provides you total items in the result set, as well as the link you should use to visit the next page. In this example, I am going to use the "count" indicator to do the loop.


Now Power Query has built the basic routine for us. We need to manually modify the query
to return the result page by page. So firstly we open the query edit window by click Advanced Editor


Our query looks like screenshot captured below


In the query editor window, I transform the query into a function, nothing special, it simply accepts a page number and then return the Json response.


Here is the key part:
  • We pull the total item count from the initial call 
  • Then we generate a list for page numbers 
  • For each list item, we call the ufnCallAPI function

And the result returned from the query:

(note: 100/page might be too small to issue the call, if it is too slow, you can try to change the page_size parameter to 1000 in the function definition, as well as the pageRange list boundary to {1..Number.RoundUp(totalItems/1000)} )


As you can see, though there is no loop function available in Power Query (at this stage), we can still retrieve paginated API responses by using provided paging indicator. In my next post, I will cover the topic of how to handle no indicator response in Power Query. Enjoy.


2015-08-27

Salesforce - Retrieve Salesforce Entity Data by REST API

In my previous post I have shown how to retrieve Salesforce entity data through SOAP API. The Salesforce platform can also be accessed through REST API.

To get the Salesforce REST API works, the first thing we need to do is to create an app in Salesforce. Go to Setup page, on your left hand side, expand Create node under the Build section, then click Apps link.



Now on the main panel, there is a section named "Connected Apps", click the New button next to it.


On the next screen, firstly we need to fill in some basic information, as shown below


Then go to the API section, tick the checkbox "Enable OAuth Settings". Immediately there will be OAuth settings shown on the screen. To demonstrate the REST API, here I just make the callback URL as a test URL, and select full access as the authentication scope. In a real production environment, the access scope should be limited to specific account/group level.


Now once we save it, we should see the confirmation message as below

The activation of the app should not take too long, but meanwhile, we need to collect some information for the API coding task. Open the App and you should see the App details page like below. The consumer key and consumer secret are what we need for our coding task


Time to look at the coding side. In my Simpro post (part2) and Telstra post (here) I used webclient to post the message. Actually we can also use HttpClient to do the same task.

Assume what I want to do is to get a list of leads in Salesforce. The final output of the work looks like below.



Because we are going to use HttpClient, we need to add System.Net.Http into our reference list of the C# project.


Now the remaining coding is just like other REST API, obtain the access token, and submit the request, then receive the response. One thing could be important to you, is the version of your Salesforce when you build the query string. In a normal scenario, the version should not affect your query string. But it is always a good practise to check the version/document.

Code to access lead entity is shown below


     private async void btnLogin_Click(object sender, EventArgs e)  
     {  
       HttpClient authClient = new HttpClient();  
       string ConsumerKey = "<ConsumerKey>";  
       string ConsumerSecret = "<ConsumerSecret>";  
       string Url = @"https://login.salesforce.com/services/oauth2/token";  
       string username = "<username>";  
       string password = "<password>";  
       string token = "<token>";  
       string loginpassword = password + token;  
       HttpContent httpContent = new FormUrlEncodedContent(new Dictionary<string, string>  
         {  
           {"grant_type", "password"},  
           {"client_id", ConsumerKey},  
           {"client_secret", ConsumerSecret},  
           {"username", username},  
           {"password",loginpassword}  
         }  
       );  
       HttpRequestMessage Request = new HttpRequestMessage()  
       {  
         Method = HttpMethod.Post,  
         RequestUri = new Uri(Url),  
         Content = httpContent  
       };  
       var responsemessage = await authClient.SendAsync(Request);  
       var response = await responsemessage.Content.ReadAsStringAsync();  
       if (responsemessage.IsSuccessStatusCode)  
       {  
         var authToken = JsonConvert.DeserializeObject<object>(response);  
         string instance_url = ((JObject)authToken)["instance_url"].ToString();  
         string oauToken = ((JObject)authToken)["access_token"].ToString();  
         string sfQuery = instance_url + "/services/data/v34.0/query?q=SELECT+name+from+Lead";  
         HttpRequestMessage QueryRequest = new HttpRequestMessage(HttpMethod.Get, sfQuery);  
         QueryRequest.Headers.Add("Authorization", "Bearer " + oauToken);  
         var QueryResponse = await authClient.SendAsync(QueryRequest);  
         string QueryResponseResult = await QueryResponse.Content.ReadAsStringAsync();  
         JObject sfObject = JObject.Parse(QueryResponseResult);  
         JArray sfArray = (JArray)sfObject["records"];  
         foreach (JObject o in sfArray)  
         {  
           listBox1.Items.Add(o["Name"].ToString());  
         }  
       }  
       else  
       {  
         MessageBox.Show(response.ToString());  
       }  
     }