Marcos Vainer Loeff

Generating the WebID on the client with the PI Web API client library for .NET Standard

Blog Post created by Marcos Vainer Loeff Employee on Feb 28, 2018

Introduction

 

PI Web API 2017 R2 comes with WebID 2.0, which allows you to generate the Web IDs on the client-side, without having to make an HTTP request.

 

This is actually an important performance improvement since if you wanted to get the current value of a PI Point using older versions of PI Web API, two requests were needed:

  • One request to get the WebID using the path as input.
  • One request to get the current value using the WebID.

 

By generating the Web ID on the client, only the second request is needed.

 

Reducing the number of HTTP request is an important step to improve the performance of you application.

 

The goals of this blog post are:

 

  • Teach you how to use the PI Web API client library for .NET Standard to generate Web IDs on the client.
  • View the information of a given Web ID.
  • Show you the source code and the unit tests so you can understand the logic and create something similar on other platforms.

 

Materials

 

I've referred to the articles and videos written and recorded by Christopher Sawyer below to get started.

 

 

I strongly suggest taking a look at them before continuing to read.

 

The WebIdHelper class

 

A new property called WebIdHelper was added to the PIWebApiClient, the top level object of the client library. This property is a WebIdHelper object with two methods:

 

  • GetWebIdInfo(string webId)
  • GenerateWebIdByPath(string webId, Type objectType, Type ownerType)

 

 

Getting the Web ID information

 

Getting the information of a given Web ID is very simple. Let's take a look at the AF attribute example:

 

PIAttribute piAttribute = client.Attribute.GetByPath(Constants.AF_ATTRIBUTE_PATH);
WebIdInfo piAttributeWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttribute.WebId);

 

The first line retrieves the Web ID of an AF attribute through a HTTP request (old method). The second line gets the WebID information by returning an object from the WebIdInfo class. This class has the following properties:

 

  • Marker (string) --> The marker of the object.
  • ObjectID (Guid) --> The ID of the object.
  • ObjectType (Type) --> The type of the object.
  • OwnerID (Guid) --> The ID of the owner of the object.
  • OwnerType (Type) --> The type of the owner of the object.
  • Path (string) --> The path of the object.
  • PointID (int) --> The PointID of the PI Point. In this case the objectID won't be used.
  • ServerID (Guid) --> The ID of the AF Server or the PI Data Archive.
  • Version (int) --> The version of the Web ID. If it is using the Web ID 2.0, the version should be 1.
  • WebIdType (WebIdType) --> The WebIDType which could be Full, PathOnly, IDOnly, LocalDOnly, DefaultIDOnly.

 

If you take a look at the Specification Tables, you would realize that some objects have ObjectID, some objects don't. Some have OwnerID, some don't. PIDataServer and PIAssetServer don't have OwnerID and ObjectID but only the ServerID. After taking a look at all object types, we have realized that there are 5 categories of WebIDs according to the table below:

 

CategoryServer IDObject IDOwner IDOwner MarkerExamples
AXPIDataArchive and PI AssetServer
BXX

PIAnalysis, PIAnalysisCategory, PIAnalysisTemplate, PIAnalysisRulePlugIn, PIAttributeCategory, PIEventFrame, PITimeRulePlugIn, PISecurityIdentity,

PISecurityMapping, PITable, PITableCategory, PIUnit, PIUnitClass, PIAssetDatabase, PIElement, PIElementCategory, PIElementTemplate

CXXXPIEnumerationSet
DXXXXPIAnalysisRule, PIAttribute, PIAttributeTemplate, PIEnumerationValue, PITimeRule
EXX (PointID instead of ObjectID)PIPoint

 

NOTE: Since the client library doesn't have Notification objects, they were removed from the table above.

 

In all types of objects, the 4 first characters are processed the same way:

- First character: Web ID type

- Second character: Web ID version

- Third and fourth characters: Marker of the object type

 

The other characters are processed by the ProcessGuidAndPaths() method below:

 

        private void ProcessGuidsAndPaths(string webId, bool hasMarkerOwner, WebIdStringType webIdStringType, bool usePIPoint = false)
        {
            string restWebId = webId.Substring(4);


            if (hasMarkerOwner == true)
            {
                string markerOwner = restWebId.Substring(0, 1);
                ProcessOwnerType(markerOwner);
                restWebId = restWebId.Substring(1);
            }


            if ((WebIdType == WebIdType.Full) || (WebIdType == WebIdType.IDOnly))
            {
                string encodedServerID = restWebId.Substring(0, 22);
                ServerID = DecodeGUID(encodedServerID);
                restWebId = restWebId.Substring(22);


                if (webIdStringType == WebIdStringType.ThreeGuids)
                {
                    string encodedOwnerID = restWebId.Substring(0, 22);
                    OwnerID = DecodeGUID(encodedOwnerID);
                    restWebId = restWebId.Substring(22);
                }
                if ((webIdStringType == WebIdStringType.ThreeGuids) ||
                    (webIdStringType == WebIdStringType.TwoGuids))
                {


                    if (usePIPoint == false)
                    {
                        string encodedObjectID = restWebId.Substring(0, 22);
                        ObjectID = DecodeGUID(encodedObjectID);
                        restWebId = restWebId.Substring(22);
                    }
                    else
                    {
                        string encodedObjectID = restWebId.Substring(0, 6);
                        PointID = DecodeInt(encodedObjectID);
                        restWebId = restWebId.Substring(6);
                    }


                }
            }


            if ((WebIdType == WebIdType.Full) || (WebIdType == WebIdType.PathOnly))
            {
                string encodedPath = restWebId;
                Path = DecodeString(encodedPath);
            }
        }

 

Each type of object belongs to a different category. Each category calls the ProcessGuidsAndPaths() function according to the table below:

 

CategoryHow to call ProcessGuidsAndPaths
AProcessGuidsAndPaths(webId, false, WebIdStringType.OneGuid, false)
BProcessGuidsAndPaths(webId, false, WebIdStringType.TwoGuids, false)
CProcessGuidsAndPaths(webId, true, WebIdStringType.ThreeGuids, false)
DProcessGuidsAndPaths(webId, true, WebIdStringType.TwoGuids, false)
EProcessGuidsAndPaths(webId, false, WebIdStringType.TwoGuids, true)

 

We have executed a Unit test to make sure that no exception is thrown in this process and that the results are correct:

 

            string webIdType = "Full";
            PIAnalysis piAnalysis = client.Analysis.GetByPath(Constants.AF_ANALYSIS_PATH, null, webIdType);
            WebIdInfo piAnalysisWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysis.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_PATH.ToUpper().Substring(2), piAnalysisWebIdInfo.Path);
            Assert.AreEqual(piAnalysis.Id, piAnalysisWebIdInfo.ObjectID.ToString());


            PIAnalysisCategory piAnalysisCategory = client.AnalysisCategory.GetByPath(Constants.AF_ANALYSIS_CATEGORY_PATH, null, webIdType);
            WebIdInfo piAnalysisCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisCategory.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_CATEGORY_PATH.ToUpper().Substring(2), piAnalysisCategoryWebIdInfo.Path);
            Assert.AreEqual(piAnalysisCategory.Id, piAnalysisCategoryWebIdInfo.ObjectID.ToString());


            PIAnalysisRule piAnalysisRule = client.AnalysisRule.GetByPath(Constants.AF_ANALYSIS_RULE_PATH, null, webIdType);
            WebIdInfo piAnalysisRuleWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisRule.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_RULE_PATH.ToUpper().Substring(2), piAnalysisRuleWebIdInfo.Path);
            Assert.AreEqual(piAnalysisRule.Id, piAnalysisRuleWebIdInfo.ObjectID.ToString());




            PIAnalysisRulePlugIn piAnalysisRulePlugIn = client.AnalysisRulePlugIn.GetByPath(Constants.AF_ANALYSIS_RULE_PLUGIN_PATH, null, webIdType);
            WebIdInfo piAnalysisRulePlugInWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisRulePlugIn.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_RULE_PLUGIN_PATH.ToUpper().Substring(2), piAnalysisRulePlugInWebIdInfo.Path);
            Assert.AreEqual(piAnalysisRulePlugIn.Id, piAnalysisRulePlugInWebIdInfo.ObjectID.ToString());


            PIAnalysisTemplate piAnalysisTemplate = client.AnalysisTemplate.GetByPath(Constants.AF_ANALYSIS_TEMPLATE_PATH, null, webIdType);
            WebIdInfo piAnalysisTemplateWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAnalysisTemplate.WebId);
            Assert.AreEqual(Constants.AF_ANALYSIS_TEMPLATE_PATH.ToUpper().Substring(2), piAnalysisTemplateWebIdInfo.Path);
            Assert.AreEqual(piAnalysisTemplate.Id, piAnalysisTemplateWebIdInfo.ObjectID.ToString());




            PIAssetDatabase piAssetDatabase = client.AssetDatabase.GetByPath(Constants.AF_DATABASE_PATH, null, webIdType);
            WebIdInfo piAssetDatabaseWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAssetDatabase.WebId);
            Assert.AreEqual(Constants.AF_DATABASE_PATH.ToUpper().Substring(2), piAssetDatabaseWebIdInfo.Path);
            Assert.AreEqual(piAssetDatabase.Id, piAssetDatabaseWebIdInfo.ObjectID.ToString());




            PIAssetServer piAssetServer = client.AssetServer.GetByPath(Constants.AF_SERVER_PATH, null, webIdType);
            WebIdInfo piAssetServerWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAssetServer.WebId);
            Assert.AreEqual(Constants.AF_SERVER_PATH.ToUpper().Substring(2), piAssetServerWebIdInfo.Path);
            Assert.AreEqual(piAssetServer.Id, piAssetServerWebIdInfo.ServerID.ToString());




            PIAttribute piAttribute = client.Attribute.GetByPath(Constants.AF_ATTRIBUTE_PATH, null, webIdType);
            WebIdInfo piAttributeWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttribute.WebId);
            Assert.AreEqual(Constants.AF_ATTRIBUTE_PATH.ToUpper().Substring(2), piAttributeWebIdInfo.Path);
            Assert.AreEqual(piAttribute.Id, piAttributeWebIdInfo.ObjectID.ToString());




            PIAttributeCategory piAttributeCategory = client.AttributeCategory.GetByPath(Constants.AF_ATTRIBUTE_CATEGORY_PATH, null, webIdType);
            WebIdInfo piAttributeCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttributeCategory.WebId);
            Assert.AreEqual(Constants.AF_ATTRIBUTE_CATEGORY_PATH.ToUpper().Substring(2), piAttributeCategoryWebIdInfo.Path);
            Assert.AreEqual(piAttributeCategory.Id, piAttributeCategoryWebIdInfo.ObjectID.ToString());


            PIAttributeTemplate piAttributeTemplate = client.AttributeTemplate.GetByPath(Constants.AF_ATTRIBUTE_TEMPLATE_PATH, null, webIdType);
            WebIdInfo piAttributeTemplateWebIdInfo = client.WebIdHelper.GetWebIdInfo(piAttributeTemplate.WebId);
            Assert.AreEqual(Constants.AF_ATTRIBUTE_TEMPLATE_PATH.ToUpper().Substring(2), piAttributeTemplateWebIdInfo.Path);
            Assert.AreEqual(piAttributeTemplate.Id, piAttributeTemplateWebIdInfo.ObjectID.ToString());




            PIDataServer piDataServer = client.DataServer.GetByPath(Constants.PI_DATA_SERVER_PATH, null, webIdType);
            WebIdInfo piDataServerWebIdInfo = client.WebIdHelper.GetWebIdInfo(piDataServer.WebId);
            Assert.AreEqual(Constants.PI_DATA_SERVER_PATH.ToUpper().Substring(2), piDataServerWebIdInfo.Path);
            Assert.AreEqual(piDataServer.Id, piDataServerWebIdInfo.ServerID.ToString());




            PIElement piElement = client.Element.GetByPath(Constants.AF_ELEMENT_PATH, null, webIdType);
            WebIdInfo piElementWebIdInfo = client.WebIdHelper.GetWebIdInfo(piElement.WebId);
            Assert.AreEqual(Constants.AF_ELEMENT_PATH.ToUpper().Substring(2), piElementWebIdInfo.Path);
            Assert.AreEqual(piElement.Id, piElementWebIdInfo.ObjectID.ToString());


            PIElementCategory piElementCategory = client.ElementCategory.GetByPath(Constants.AF_ELEMENT_CATEGORY_PATH, null, webIdType);
            WebIdInfo piElementCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piElementCategory.WebId);
            Assert.AreEqual(Constants.AF_ELEMENT_CATEGORY_PATH.ToUpper().Substring(2), piElementCategoryWebIdInfo.Path);
            Assert.AreEqual(piElementCategory.Id, piElementCategoryWebIdInfo.ObjectID.ToString());




            PIElementTemplate piElementTemplate = client.ElementTemplate.GetByPath(Constants.AF_ELEMENT_TEMPLATE_PATH, null, webIdType);
            WebIdInfo piElementTemplateWebIdInfo = client.WebIdHelper.GetWebIdInfo(piElementTemplate.WebId);
            Assert.AreEqual(Constants.AF_ELEMENT_TEMPLATE_PATH.ToUpper().Substring(2), piElementTemplateWebIdInfo.Path);
            Assert.AreEqual(piElementTemplate.Id, piElementTemplateWebIdInfo.ObjectID.ToString());




            PIEnumerationSet piEnumerationSet = client.EnumerationSet.GetByPath(Constants.AF_ENUMERATION_SET_PATH, null, webIdType);
            WebIdInfo piEnumerationSetWebIdInfo = client.WebIdHelper.GetWebIdInfo(piEnumerationSet.WebId);
            Assert.AreEqual(Constants.AF_ENUMERATION_SET_PATH.ToUpper().Substring(2), piEnumerationSetWebIdInfo.Path);
            Assert.AreEqual(piEnumerationSet.Id, piEnumerationSetWebIdInfo.ObjectID.ToString());




            PIEnumerationValue piEnumerationValue = client.EnumerationValue.GetByPath(Constants.AF_ENUMERATION_VALUE_PATH, null, webIdType);
            WebIdInfo piEnumerationValueWebIdInfo = client.WebIdHelper.GetWebIdInfo(piEnumerationValue.WebId);
            Assert.AreEqual(Constants.AF_ENUMERATION_VALUE_PATH.ToUpper().Substring(2), piEnumerationValueWebIdInfo.Path);
            Assert.AreEqual(piEnumerationValue.Id, piEnumerationValueWebIdInfo.ObjectID.ToString());




            PIEventFrame piEventFrame = client.EventFrame.GetByPath(Constants.AF_EVENT_FRAME_PATH, null, webIdType);
            WebIdInfo piEventFrameWebIdInfo = client.WebIdHelper.GetWebIdInfo(piEventFrame.WebId);
            Assert.AreEqual(Constants.AF_EVENT_FRAME_PATH.ToUpper().Substring(2), piEventFrameWebIdInfo.Path);
            Assert.AreEqual(piEventFrame.Id, piEventFrameWebIdInfo.ObjectID.ToString());




            PIPoint piPoint = client.Point.GetByPath(Constants.PI_POINT_PATH, null, webIdType);
            WebIdInfo piPointWebIdInfo = client.WebIdHelper.GetWebIdInfo(piPoint.WebId);
            Assert.AreEqual(Constants.PI_POINT_PATH.ToUpper().Substring(2), piPointWebIdInfo.Path);
            Assert.AreEqual(piPoint.Id.ToString(), piPointWebIdInfo.PointID.ToString());




            PISecurityIdentity piSecurityIdentity = client.SecurityIdentity.GetByPath(Constants.AF_SECURITY_IDENTITY_PATH, null, webIdType);
            WebIdInfo piSecurityIdentityWebIdInfo = client.WebIdHelper.GetWebIdInfo(piSecurityIdentity.WebId);
            Assert.AreEqual(Constants.AF_SECURITY_IDENTITY_PATH.ToUpper().Substring(2), piSecurityIdentityWebIdInfo.Path);
            Assert.AreEqual(piSecurityIdentity.Id, piSecurityIdentityWebIdInfo.ObjectID.ToString());




            PISecurityMapping piSecurityMapping = client.SecurityMapping.GetByPath(Constants.AF_SECURITY_MAPPING_PATH, null, webIdType);
            WebIdInfo piSecurityMappingWebIdInfo = client.WebIdHelper.GetWebIdInfo(piSecurityMapping.WebId);
            Assert.AreEqual(Constants.AF_SECURITY_MAPPING_PATH.ToUpper().Substring(2), piSecurityMappingWebIdInfo.Path);
            Assert.AreEqual(piSecurityMapping.Id, piSecurityMappingWebIdInfo.ObjectID.ToString());




            PITable piTable = client.Table.GetByPath(Constants.AF_TABLE_PATH, null, webIdType);
            WebIdInfo piTableWebIdInfo = client.WebIdHelper.GetWebIdInfo(piTable.WebId);
            Assert.AreEqual(Constants.AF_TABLE_PATH.ToUpper().Substring(2), piTableWebIdInfo.Path);
            Assert.AreEqual(piTable.Id, piTableWebIdInfo.ObjectID.ToString());


            PITableCategory piTableCategory = client.TableCategory.GetByPath(Constants.AF_TABLE_CATEGORY_PATH, null, webIdType);
            WebIdInfo piTableCategoryWebIdInfo = client.WebIdHelper.GetWebIdInfo(piTableCategory.WebId);
            Assert.AreEqual(Constants.AF_TABLE_CATEGORY_PATH.ToUpper().Substring(2), piTableCategoryWebIdInfo.Path);
            Assert.AreEqual(piTableCategory.Id, piTableCategoryWebIdInfo.ObjectID.ToString());




            PIUnit piUnit = client.Unit.GetByPath(Constants.AF_UOM_PATH, null, webIdType);
            WebIdInfo piUnitWebIdInfo = client.WebIdHelper.GetWebIdInfo(piUnit.WebId);
            Assert.AreEqual(Constants.AF_UOM_PATH.ToUpper().Substring(2), piUnitWebIdInfo.Path);
            Assert.AreEqual(piUnit.Id, piUnitWebIdInfo.ObjectID.ToString());




            PIUnitClass piUnitClass = client.UnitClass.GetByPath(Constants.AF_UOM_CLASS_PATH, null, webIdType);
            WebIdInfo piUnitClassWebIdInfo = client.WebIdHelper.GetWebIdInfo(piUnitClass.WebId);
            Assert.AreEqual(Constants.AF_UOM_CLASS_PATH.ToUpper().Substring(2), piUnitClassWebIdInfo.Path);
            Assert.AreEqual(piUnitClass.Id, piUnitClassWebIdInfo.ObjectID.ToString());

 

 

Generating a Web ID given a path

 

Generating a Web ID on the client is a very simple task. Just call the client.WebIdHelper.GenerateWebIdByPath() method. Here are some examples:

 

            string webId = client.WebIdHelper.GenerateWebIdByPath(Constants.AF_DATABASE_PATH, typeof(PIAssetDatabase));
            string webId2 = client.WebIdHelper.GenerateWebIdByPath(Constants.AF_ATTRIBUTE_PATH, typeof(PIAttribute), typeof(PIElement));

 

The inputs of this function are:

  • The path of the object (required)
  • The type of the object (required)
  • The type of the owner of the object (optional)

 

The third input should only be used for objects that belong to the Category C and D. There is a method on the client library that validates this parameter which was developed according to the table specification:

 

      private void ValidateTypeAndOwnerType(Type type, Type ownerType)
        {
            if (type == typeof(PIAttribute))
            {
                if ((ownerType != typeof(PIElement)) && (ownerType != typeof(PIEventFrame)))
                {
                    throw new WebIdException("PIAttribte owner type must be a PIElement or a PIEventFrame.");
                }
            }
            else if (type == typeof(PIAttributeTemplate))
            {
                if ((ownerType != typeof(PIElementTemplate)))
                {
                    throw new WebIdException("PIElementTemplate owner type must be a PIElementTemplate.");
                }
            }
            else if ((type == typeof(PIEnumerationSet)) && (type == typeof(PIEnumerationValue)))
            {
                if ((ownerType != typeof(PIDataServer)) && (ownerType != typeof(PIAssetServer)))
                {
                    throw new WebIdException("PIEnumerationSet and  PIEnumerationValue owner type must be a PIDataServer or PIAssetServer.");
                }
            }
            else if (type == typeof(PITimeRule))
            {
                if ((ownerType != typeof(PIAnalysis)) && (ownerType != typeof(PIAnalysisTemplate)))
                {
                    throw new WebIdException("PITimeRule owner type must be a PIAnalysis and PIAnalysisTemplate.");
                }
            }
        }

 

 

The returned WebID is generated as follows:

 

  • First two charcaters: "P1" (PathOnly WebIDType and Version 1)
  • Two characters for the marker's object
  • One character for the marker's owner object (optional)
  • Encoded path (after deleting the first two backslashes)

 

     public string GenerateWebIdByPath(string path, Type type, Type ownerType = null)
        {
            ValidateTypeAndOwnerType(type, ownerType);
            string marker = GetMarker(type);
            string ownerMarker = GetOwnerMarker(ownerType);


            if (path.Substring(0, 2) == "\\\\")
            {
                path = path.Substring(2);
            }
            string encodedPath = Encode(path.ToUpperInvariant());
            return string.Format("P1{0}{1}{2}", marker, ownerMarker, encodedPath);
        }

 

Conclusions

 

I hope that this blog post helped you:

  • Generate the Web ID using this client library faster
  • Understand the main concepts of Web ID 2.0
  • Write code in other platforms to generate the Web ID client side.

 

If you have questions, please post them below!

Outcomes