csawyer

PI Web API - Using Web ID 2.0 to Optimize your Applications

Blog Post created by csawyer Employee on Jan 26, 2018

(If you're looking for the PI object encodings spec, visit this post)

 

Supported Versions

Web ID 2.0 first became available in PI Web API 2017 R2 (1.10 and later).   You can check your installed version by browsing to your PI Web API System endpoint  {yourwebapiserver}/piwebapi/system .

Video: Everything you wanted to know about WebID 2.0

 

What is Web ID?

Web ID is a fail-safe mechanism for retrieving artifcats from PI and AF servers while remaining safe for use in URLs. WebID URLs are resilient to changes of an object’s name or ID, which means you can store them and use them in your apps.

 

PI Web API uses Web ID as the main access mechanism to explore the objects in the PI system landscape.

 

If you already know the structure of your AF hierarchy, you can compose Web ID addresses yourself, sparing you the expense of consulting the query and search endpoints in PI Web API to determine what those Web IDs are.

 

How can I use Web ID to enhance my application?

With Web ID you can build direct links to storage areas in the PI System and cache those in your own application.   This is helpful for scenarios where you have a large number of objects to trade with PI Web API and you already know how the objects are modeled in the PI System.   They are also critical to efficient operations such as Batch requests where you need to perform a large amount of operations at once.

 

With Web ID you can also save on network bandwidth and transfer rates by composing shorter Web IDs where necessary, if server contexts aren’t expressly needed.

 

The Basics - How a Web ID is composed

A Web ID is a smart code that is comprised of several compound strings. The first four characters of a WebID determine how the remainder of the string is comprised and what object is being referenced.

 

Diagram of how a Web ID is composed

 

In the example here, a full version of a Web ID is being used to encode the ID of an AF Element.

 

The components of the Web ID in this instance are:

AF Element (Web ID Type: Full)

 

Field NameValueEncoded WidthEncoding Method
Type‘F’1None
Version‘1’1None
Marker‘Em’2None
System IDelement.PISystem.ID22Urlencoded Guid
Element IDelement.ID22Urlencoded Guid
Name Payloadelement.GetPath(AFEncodeType. Name, null).Substring(2).ToUpperInvariant()varUrlencoded UTF8 String

(Visit here for the full listing of supported object types and their encodings)

 

Web ID Types

The following Web ID types can be used to reduce the length of ID that you use to cache. You can request WebID types by adding ?webIDType={type} to any URL request in PI Web API, or posting any type of Web ID you wish to the PI Web API server. PI Web API will automatically figure out what type of Web ID you sent based on the formatting markers inside the ID.

 

Here are some examples:

Web ID TypeSampleElements Omitted
Full (default)F1EmDqD5loBNH0erqeqJodtALAh_fRZ2eH5xGWZgAVXQKgBgUkVTVFVOSVRcU0FXWUVSXEVMRU1FTlQx
?webIDType=IDOnlyI1EmDqD5loBNH0erqeqJodtALAh_fRZ2eH5xGWZgAVXQKgBgPath
?webIDType=PathOnlyP1EmUkVTVFVOSVRcU0FXWUVSXEVMRU1FTlQxServer ID, Object ID
?webIDType=LocalIDOnlyL1Emh_fRZ2eH5xGWZgAVXQKgBgServer ID, Path
?webIDType=DefaultIDOnlyD1Emh_fRZ2eH5xGWZgAVXQKgBgServer ID, Path

Let’s decompose in code

The following code sample demonstrates how a full Web ID is parsed.

Example C# - Decomposing Web ID values

  using System;
  using System.Text;

  class WebIDExample
  {

  internal static string Encode(byte[] value)
  {
 string encoded = System.Convert.ToBase64String(value);
 return encoded.TrimEnd(new char[] { '=' }).Replace('+', '-').Replace('/', '_');
  }

  internal static string Encode(Guid value)
  {
 byte[] bytes = value.ToByteArray();
 return Encode(bytes);
  }

  internal static byte[] Decode(string value)
  {
 //Base 64 strings are in multiples of 4 bytes long.
 //This restores the = sign padding and changes the Uri-safe chars with the Base64 requirement
 StringBuilder decodestring = new StringBuilder(value.Replace('-','+').Replace('_','/'));
 int padneeded = value.Length % 4;
 for (int i = 0; i < padneeded; i++)
 {
 decodestring.Append('=');
 }

 byte[] bytes = System.Convert.FromBase64String(decodestring.ToString());
 return bytes;
  }

  internal static string DecodeString(string value)
  {
 return Encoding.UTF8.GetString(Decode(value));
  }

  internal static Guid DecodeGUID(string value)
  {
 byte[] guid = Decode(value);
 return new Guid(guid);
  }

  public static void Main()
  {

 // Given a Web ID of F1EmDqD5loBNH0erqeqJodtALAh_fRZ2eH5xGWZgAVXQKgBgUkVTVFVOSVRcU0FXWUVSXEVMRU1FTlQx,
 // let's decode the component parts.

 // F Full 1 Version Em AF Element RXhhbXBsZVN0cmluZw== Server
 // ID (GUID), string must be padded to decode
 // h/fRZ2eH5xGWZgAVXQKgBg== AF Object ID (GUID), change the _
 // to a / before decoding UkVTVFVOSVRcU0FXWUVSXEVMRU1FTlQx
 // Name (RESTUNIT\SAWYER\ELEMENT1)

 // Note: The decoded GUIDs are returned as a series of UTF8 bytechars, which are then
 // convertible into a GUID by printing the hex value of each byte.

 // Encode/decode basic string parts used in WebID
 Console.WriteLine("The F value of the WebID = Full");
 Console.WriteLine($"The value of RXhhbXBsZVN0cmluZw is {DecodeString("RXhhbXBsZVN0cmluZw")}");
 Console.WriteLine($"The value of DqD5loBNH0erqeqJodtALA is {DecodeGUID("DqD5loBNH0erqeqJodtALA").ToString()}");
 Console.WriteLine($"The value of h_fRZ2eH5xGWZgAVXQKgBg is {DecodeGUID("h_fRZ2eH5xGWZgAVXQKgBg").ToString()}");
 Console.WriteLine($"The value of UkVTVFVOSVRcU0FXWUVSXEVMRU1FTlQx is {DecodeString("UkVTVFVOSVRcU0FXWUVSXEVMRU1FTlQx").ToString()}");

  }

  }

 

Example JS - Decomposing Web ID values

//var btoa = require('btoa');
//var atob = require('atob');

function DecodeString(strDecode) {
  var decodestring = strDecode.replace('-', '+').replace('_', '/');
  var padneeded = decodestring.length % 4;
  for (var i = 0; i < padneeded; i++) {
  decodestring += '=';
  }

  return (atob(decodestring)).toString('utf8');;
}

function Base64ToArrayBuffer(base64) {
  var binary_string = atob(base64);
  var len = binary_string.length;
  var bytes = new Uint8Array(len);
  for (var i = 0; i < len; i++) {
  bytes[i] = binary_string.charCodeAt(i);
  }
  return bytes.buffer;
}

function DecodeGUID(strDecode) {
  var bytes = Base64ToArrayBuffer(strDecode);
  var uncodedbytes = new Uint8Array(bytes);

  var guidstr = "";

  for (var i = 3; i >= 0; i--) {
  if (uncodedbytes[i] < 17) {
  guidstr += "0" + uncodedbytes[i].toString(16);
  } else {
  guidstr += uncodedbytes[i].toString(16);
  }
  }
  guidstr += "-";
  if (uncodedbytes[5] < 17) {
  guidstr += "0" + uncodedbytes[5].toString(16);
  } else {
  guidstr += uncodedbytes[5].toString(16);
  }
  if (uncodedbytes[4] < 17) {
  guidstr += "0" + uncodedbytes[4].toString(16);
  } else {
  guidstr += uncodedbytes[4].toString(16);
  }
  guidstr += "-";
  if (uncodedbytes[7] < 17) {
  guidstr += "0" + uncodedbytes[7].toString(16);
  } else {
  guidstr += uncodedbytes[7].toString(16);
  }
  if (uncodedbytes[6] < 17) {
  guidstr += "0" + uncodedbytes[6].toString(16);
  } else {
  guidstr += uncodedbytes[6].toString(16);
  }
  guidstr += "-";
  if (uncodedbytes[8] < 17) {
  guidstr += "0" + uncodedbytes[8].toString(16);
  } else {
  guidstr += uncodedbytes[8].toString(16);
  }
  if (uncodedbytes[9] < 17) {
  guidstr += "0" + uncodedbytes[9].toString(16);
  } else {
  guidstr += uncodedbytes[9].toString(16);
  }
  guidstr += "-";
  for (i = 10; i < 16; i++) {
  if (uncodedbytes[i] < 17) {
  guidstr += "0" + uncodedbytes[i].toString(16);
  } else {
  guidstr += uncodedbytes[i].toString(16);
  }
  }

  return guidstr;
}


function ParseExample() {
  console.log("Taking the Full Web ID of " +
  "F1EmDqD5loBNH0erqeqJodtALAh_fRZ2eH5xGWZgAVXQKgBgUkVTVFVOSVRcU0FXWUVSXEVMRU1FTlQx\n");
  console.log("Let's break this into its constituent parts: ");
  console.log("F1 Full Web ID, Version 1");
  console.log("Em This is an AF Element");
  console.log("DqD5loBNH0erqeqJodtALA -> " + DecodeGUID("DqD5loBNH0erqeqJodtALA") + " Server ID");
  console.log("h_fRZ2eH5xGWZgAVXQKgBg -> " + DecodeGUID("h_fRZ2eH5xGWZgAVXQKgBg") + " AF Object ID");
  console.log("UkVTVFVOSVRcU0FXWUVSXEVMRU1FTlQx -> " + DecodeString("UkVTVFVOSVRcU0FXWUVSXEVMRU1FTlQx"));
}


ParseExample();

 

How the encoding works

 

Encoded fields are constructed mostly in two ways:

Encoded Strings (Base64)

UTF-8 strings designating names are encoded in Base64 . However, they do not include the padding characters required to decode them. This means you will need to follow this pattern:

  • Determine length of string
  • If the modulo of the string’s length is not evenly divisible by 4, then pad the string with equal signs ‘=’ until it is evenly divisible by four.
  • Once decoded into a binary array, convert the binary array to UTF-8 characters.

 

Encoded GUID/UUID (Base64)

How a GUID is composed

Universally Unique Identifiers (UUIDs) are 128-bit numbers that are used to uniquely identify any type of object as well as guarantee with an extremely high level of probability that randomly-generated numbers will not collide.

 

Web ID contains unqiue object identifiers that point to servers and resources in PI Asset Framework. Usually this will be a Server ID that identifies the AF Server where an Asset Framework object is based then GUID/UUID of an AF object itself; like an element, attribute, batch frame or template, etc.

 

Note about byte order - PI Web API uses Microsoft-formatted GUID numbers. Because they are expressed in Base64 strings, they will be 22 characters in length. In other environments such as Javascript or Java, UUIDs may be expressed with different byte orders.

 

Given a GUID expressed as DqD5loBNH0erqeqJodtALA, you must expand this string to DqD5loBNH0erqeqJodtALA== so that it is 24 characters long. Converting the individual bytes to hexadecimal notation and placing the bytes in the correct order should yield 96f9a00e-4d80-471f-aba9-ea89a1db402c as the server GUID.

 

 

 

 

For the full listing of supported PI object types and their Web ID encodings, visit here.

Outcomes