Marcos Vainer Loeff

PI Web API 2015 R2  - New features demo - C#

Blog Post created by Marcos Vainer Loeff Employee on Jul 31, 2015

Introduction

 

Yesterday, I have published a blog post about a sample application developed using HTML5 and Javascript/jQuery, showing the new features of PI Web API 2015 R2. One of the new enhancements is bulk calls (read and write) for multiple streams (PI Points and Attribute) through only one unique HTTP request. Nevertheless, a prerequisite for using this cool feature is to know the WebId from each stream in advance.

 

On the sample application, we were able to overcome this issue using the $.when() method from jQuery:

 

   var ajaxPt1 = getAttributeWebId(attributePaths[0], null, errorCallBack);
        var ajaxPt2 = getAttributeWebId(attributePaths[1], null, errorCallBack);
        var ajaxPt3 = getAttributeWebId(attributePaths[2], null, errorCallBack);
        var data = [];
        $.when(ajaxPt1, ajaxPt2, ajaxPt3).done(function (r1, r2, r3) {
            var results = new Array(3);
            results[0] = r1[0];
            results[1] = r2[0];
            results[2] = r3[0];
               ......
          });

 

 

The variables ajaxPt1, ajaxPt3, ajaxPt3 are ajax functions that will get the webId from the PI Points of the array. The method when() will execute them and will continue with the done() method as soon as all the requests have their execution finished. The results of those ajax requests are available on the r1, r2 and r3 variables which let you get the webIds and proceed with the bulk calls.

 

The natural question for a .NET developer is: how to do that in C#? The sample application from this blog post answers this question. Click here to download the source code package.

 

Calling RESTful web services in .NET

 

Let's remember how to call actions from PI Web API in C#. The code snippet below was extracted from our blog post:

 

internal static dynamic MakeRequest(string url)
        {
            WebRequest request = WebRequest.Create(url);
            request.Credentials = new NetworkCredential("marc.adm", "xxxxxx");  
            request.ContentType = "application/json";
            WebResponse response = request.GetResponse();
            using (StreamReader sw = new StreamReader(response.GetResponseStream()))
            {
                using (JsonTextReader reader = new JsonTextReader(sw))
                {
                    return JObject.ReadFrom(reader);
                }
            }
        }

 

I am still using basic authentication on my environment. Setting up Credentials property from the WebRequest will add the Basic Authentication header of the HTTP request. In order to convert the response into a dynamic object, I am using Newtonsoft.Json which can be downloaded using NuGet.

 

For more information, please refer to this blog post.

 

Creating an array of tasks

 

First, we define a List<string> with all the PI Point names and a dynamic object which will store the final response containing all the compressed values, Then, we create an array of tasks responsible for getting the webIds from the streams. Each task will get the webId from each stream. The code snippet is below:

 

            
            List<string> piPointNames = new List<string>() { "sinusoid", "sinusoidu", "cdt160" };


            //this variable will store the final response with the compressed values from all the PI Points from the array
            dynamic finalResponse = null;


            // create an array of tasks
            Task<string>[] tasks = new Task<string>[piPointNames.Count];


            for (int i = 0; i < piPointNames.Count; i++)
            {


                // create a new task
                tasks[i] = new Task<string>((piPointName) =>
                    {


                        //get the webId from the selected PI Point
                        string url = @"https://marc-web-sql.marc.net/piwebapi/points?path=\\marc-pi2014\" + piPointName;
                        dynamic response = MakeRequest(url);
                        string webId = response.WebId.Value.ToString();


                        //the webIds will be available by accessing the property Task.Result
                        return webId;
                    }, piPointNames[i]);
            }

 

Note that although we have created and defined the tasks, they have not started yet.

 

Set up multitask continuation

 

When all tasks have finished storing the WebIds from the streams, the url for getting the compressed values needs to be generated. We create another task called continuation which will be executed when all of the antecedents task have their execution finished. The continuation task will access the Result property from each antecedent Task for geting the webIds. Adding the start time and end time, all the inputs are available to complete this task by calling the MakeRequest(url) method again. Finally, the antecedent tasks are started and the main thread waits for the continuation task to complete. The final code snippet is below:

 

            Task continuation = Task.Factory.ContinueWhenAll(tasks, antecedents =>
            {
                //in order to generate an url with all webids to get compressed values from all PI Points within the array, the antecents tasks need be accessed
                string webIdsSection = string.Empty;
                foreach (Task<string> t in antecedents)
                {
                    webIdsSection += "&webid=" + t.Result;
                }
                //then, we can generate the url with the start time and end time
                string url = @"https://marc-web-sql.marc.net/piwebapi/streamsets/recorded?" + webIdsSection.Substring(1) + "&startTime=*-200d&endTime=*";
                finalResponse = MakeRequest(url);
            });


            // start the antecedent tasks
            foreach (Task t in tasks)
            {
                t.Start();
            }


            continuation.Wait();


            Console.WriteLine(finalResponse);
            Console.WriteLine("Press enter to finish");
            Console.ReadLine();

 

 

Conclusions

 

Although the PI Web API 2015 R2 provides bulk calls, you need to know the webIds in order to use this feature. If you don't have them available, you must get them prior to calling bulk methods. This blog posts provides a C# example of how to overcome this challenge by creating antecedent tasks for getting the webIds and a continuation task for the bulk call. Using tasks increases the performance of your application as we work on a multi-threaded environment making parallel calls to the PI Web API.

 

I hope you find this blog post useful and I would like to thank you for reading!

Attachments

Outcomes