× Updating to version 6.0.0? Please follow our migration guide to adapt your application to recent changes!

General Information

The .NET SDK is suitable for all .NET languages. You can include it to your project either by

Best practice Best practice: Use our sample application for better understanding

On GitHub you can find a C# sample application which provides several lessons about the use of the most .NET SDK methods.

Basics

This section represents a brief overview of the most frequent use cases of the .NET API and the available methods. Each section contains a table of content, where the performed examples in this section are listed.

Creating the client

Examples in this section:


Creating a .NET REST client is quite simple. You choose the required client based on the use case (DataService for parts, measurements, values and configuration, or RawDataService for additional data), and pass in the Uri object pointing to your PiWeb server.

Example Example: Creating a DataServiceClient
//The Uri of your PiWeb server
var uri = new Uri( "http://piwebserver:8080" );

//Creating the client
var DataServiceClient = new DataServiceRestClient( uri );


Example Example: Creating a RawDataServiceClient
//The Uri of your PiWeb server
var uri = new Uri( "http://piwebserver:8080" );

//Creating the client
var RawDataServiceClient = new RawDataServiceRestClient( uri );


Info Each method runs asynchronously and returns an awaitable Task. The result will be available once the Task has completed.

Info All methods accept a CancellationToken which you can use to cancel a request.

Configuration

Examples in this section:


All types of entities can be described by several attributes. Every attribute is identified by a unique key. PiWeb’s standard configuration consists of well known attributes, changing standardized keys is not advised. When customizing the configuration a best practice is to use keys outside of PiWeb’s standard range, for example between 14000 and 20000 should be enough space for your personal keys.
To mark an attribute as a key in PiWeb it is often displayed with the letter K prior to the actual value, e.g. K1234. In code you only use the value, so 1234. Leading zeros are ignored, a key 0024 is the same as 24.

Best practice Best practice: Use WellKnownKeys class

Our .NET SDK provides the WellKnownKeys class including important standardized attribute keys.

//Get standardized key for the part description
var partDescriptionKey = WellKnownKeys.Part.Description;

ConfigurationDto class includes all possible attributes for each entity in a particular property:

Property Description
AbstractAttributeDefinitionDto[] AllAttributes Returns a list of all attribute definitions in this configuration.
AttributeDefinitionDto[] CatalogAttributes Returns a list of all attribute definitions for a catalog entry in this configuration.
AbstractAttributeDefinitionDto[] CharacteristicAttributes Returns a list of all characteristic attribute definitions in this configuration.
AbstractAttributeDefinitionDto[] MeasurementAttributes Returns a list of all measurement attribute definitions in this configuration.
AbstractAttributeDefinitionDto[] PartAttributes Returns a list of all part attribute definitions in this configuration.
AbstractAttributeDefinitionDto[] ValueAttributes Returns a list of all value attribute definitions in this configuration.
VersioningType VersioningTypeDto Specifies how the server is performing inspection plan versioning.

As you can see in above class diagram ConfigurationDto consists of several methods to easily handle entities’ attribute definitions.

AbstractAttributeDefinitionDto has two implementations: AttributeDefinitionDto and CatalogAttributeDefinitionDto:

AbstractAttributeDefinitionDto

Property Description
string Description The description of the attribute.
ushort Key The unique key/identifier.
bool QueryEfficient Indicates if the attribute is efficient for filtering operations. This flag is currently unused but may be used in future web service versions.

AttributeDefinitionDto

Property Description
ushort? Length The maximal lenght of an attribute. Only valid if the type is AlphaNumeric.
AttributeTypeDto Type The attribute type, i.e. AlphaNumeric, Float, Integer or DateTime.

CatalogAttributeDefinitionDto

Property Description
Guid Catalog The Guid of the catalog that should be usable as an attribute value.

While AttributeDefinitionDto describes an attribute for parts, characteristics, measurements, measured values and catalogs, CatalogAttributeDefinitionDto is an attribute definition based on an existing catalog. This means that a CatalogAttributeDefinitionDto doesn’t define an attribute that can be used in a catalog, but the definition of an attributes value as a catalog entry. The following example will help to understand the difference.

Example Example: Creating a new AttributeDefinitionDto
//Create an AttributeDefinitionDto;
var attributeDef = new AttributeDefinitionDto( 11001, "Description", AttributeTypeDto.AlphaNumeric, 255 );

Here we create an attribute Description, which can be used in parts, catalogs etc.

Example Example: Creating a new CatalogAttributeDefinitionDto
//Create a catalog using recently created attributeDef as catalog's valid attribute
var testCatalog = new CatalogDto
{
  Name = "TestCatalog",
  Uuid = Guid.NewGuid(),
  ValidAttributes = new[]{ attributeDef.Key, ... },
  CatalogEntries = new[]{ ... }
};

//Create a CatalogAttributeDefinition
var catalogAttributeDefinition = new CatalogAttributeDefinitionDto
{
  Catalog = testCatalog.Uuid,
  Description = "Catalog Based Attribute",
  Key = 11002
};

Here we create a new catalog TestCatalog. The next step is to create a CatalogAttributeDefinitionDto that defines a new attribute Catalog Based Attribute, and to assign the TestCatalog to this attribute. Now the catalog entries can be used as values for this attribute.

Best practice Best practice: Check if a key already exists in the configuration

Keys are unique, so creating an attribute with the same key will result in an exception. You should always check if an attribute already exists, see example below.

Example Example: Creating a new attribute for a part
//Create the client and fetch the configuration
var configuration = await DataServiceClient.GetConfiguration();

//Create a new AttributeDefinitionDto with key 11001
var attributeDefinition = new AttributeDefinitionDto( 11001, "Description", AttributeTypeDto.AlphaNumeric, 255 );

//Check if attribute does already exist
var attributeDoesAlreadyExist = configuration.GetDefinition( 11001 );

//Create new attribute if not existing
if(attributeDoesAlreadyExist == null)
{
  await DataServiceClient.CreateAttributeDefinition( EntityDto.Part, attributeDefinition );
}

Inspection plan

Examples in this section:


An inspection plan object contains entities of two different types - parts and characteristics. Parts are hold in class SimplePartDto, characteristics are hold in class InspectionPlanCharacteristicDto. Both are derived from the abstract base class InspectionPlanDtoBase and consist of the following properties:

InspectionPlanDtoBase

Property Description
AttributeDto[] Attributes A set of attributes which describes the entity.
string Comment A comment which describes the last inspection plan change. The comment is only returned if versioning is enabled in the server settings.
PathInformationDto Path The path of this entity which describes the entity’s hierarchical structure.
string this[ushort key] Indexer for accessing entity’s attribute value directly with the specified key
DateTime TimeStamp Contains the date and time of when the entity was last updated.
Guid Uuid Identifies this inspection plan entity uniquely.
uint Version Contains the entity´s revision number. The revision number starts with 0 and is globally incremented by 1 each time changes are applied to the inspection plan.

Info The version is only updated if versioning is enabled in server settings. Every update creates a new version entry. You should add a value for Comment to explain why it was changed and by whom. The optimal format is “[Username]:Reason of change”, e.g. “[John Doe]: Renamed ‘Part1’ to ‘Part2’”. This way it will be correctly displayed in PiWeb Planner.

Info The version/revision of a part or characteristic is global. This means that the version counter is the same for every entity in the inspection plan. A part with a version of 34 did not necessarily change 34 times but the version indicates that the 34th change in the whole inspection plan was done to this entity.

SimplePartDto

Property Description
DateTime CharChangeDate The timestamp for the most recent characteristic change on any characteristic below that part (but not below sub parts). This timestamp is updated by the server backend.

Parts as well as characteristic may contain a version history if versioning is enabled in server settings. If enabled, parts are represented by class InspectionPlanPartDto which is derived from SimplePartDto:

InspectionPlanCharacteristicDto, InspectionPlanPartDto

Property Description
InspectionPlanBase[] History The version history for this inspection plan entity.

PathInformationDto

The inspection plan is organized in a tree structure but saved to the database in a flat representation. Path information helps to convert from flat to tree structure. A PathInformationDto object includes an array of entity’s path elements. These path elements contain the following properties:

PathElementDto

Property Description
InspectionPlanEntityDto Type Type of the path element (Part or Characteristic)
String Value Path elments’ name

Best practice Best practice: To create a PathInformationDto object you might use the PathHelper class which includes several helper methods:

Method Description
PathInformationDto RoundtripString2PathInformation( string path ) Creates a path information object based on path parameter in roundtrip format (“structure:database path”)
PathInformationDto String2PartPathInformation( string path ) Creates a path information object based on path parameter including plain part structure
PathInformationDto String2CharacteristicPathInformation( string path ) Creates a path information object based on path parameter including plain characteristic structure
PathInformationDto DatabaseString2PathInformation( string path, string structure) Creates a path information object based on path and structure parameter

Examples

Info DataServiceClient in examples refers to an actual instance of DataServiceRestClient pointing to a server.

Example Example: Using the PathHelper class
var characteristicPath = PathHelper.RoundtripString2PathInformation( "PPC:/MetalPart/SubPart/Char1/" );

In the above example a simple path is created using our PathHelper.RoundtripString2PathInformation method. The method needs the path in roundtrip format, consisting of structure and path.

The structure is a list of the letters P and C, which are the short form of part and characteristic. The list is ordered according to the occurring types of entities in the following path string. The example structure is PPC, standing for /Part/Part/Characteristic/, which matches the types of /MetalPart/SubPart/Char1/ in the exact order. A path needs to end with /.

Info Please note that a part or characteristic must not contain a backslash \.

Example Example: Creating a part with a subpart
//Create a new part
var parentPart = new InspectionPlanPartDto
{
	Uuid = Guid.NewGuid(),
	Path = PathHelper.RoundtripString2PathInformation("P:/MetalPart/"),
};
//Create a new part that represents a child of the parentPart
var childPart = new InspectionPlanPartDto
{
	Uuid = Guid.NewGuid(),
	Path = PathHelper.RoundtripString2PathInformation("PP:/MetalPart/SubPart/"),
};

//Create parts on the server
await DataServiceClient.CreateParts( new[] { parentPart, childPart } );

The name of the part is specified within its path. Nesting is easy as you just create the path and structure according to your desired hierarchy. Remember again that characteristics cannot contain parts.

Best practice Best practice: Create or update multiple entities in a single call

To achieve a good performance, it is highly recommended to create or update items in a single call. That is why all create and update methods expect an array parameter.

Example Example: Creating characteristics for the part “MetalPart”
//Create characteristics for MetalPart
var char1Path = PathHelper.RoundtripString2PathInformation( "PC:/MetalPart/Char1/" );
var char2Path = PathHelper.RoundtripString2PathInformation( "PC:/MetalPart/Char2/" );
//Create characteristic for SubPart
var char3Path = PathHelper.RoundtripString2PathInformation( "PPC:/MetalPart/SubPart/Char3/" );

//Create InspectionPlanCharacteristicDto objects
var char1 = new InspectionPlanCharacteristicDto { Path = char1Path, Uuid = Guid.NewGuid() };
var char2 = new InspectionPlanCharacteristicDto { Path = char2Path, Uuid = Guid.NewGuid() };
var char3 = new InspectionPlanCharacteristicDto { Path = char3Path, Uuid = Guid.NewGuid() };

//Use Client to create characteristics on server
await DataServiceClient.CreateCharacteristics( new[] {char1, char2, char3} );
Example Example: Fetching different entities
//Create PathInformationDto of "MetalPart"
var partPath = PathHelper.String2PartPathInformation( "/MetalPart/" );

//Fetch all parts below "MetalPart"
var parts = await DataServiceClient.GetParts( partPath );

//Fetch all characteristics below "MetalPart"
var characteristics = await DataServiceClient.GetCharacteristics( partPath );

You can fetch different entities with the corresponding method. The path specifies the entry point from where you want to fetch data. Setting the search depth is also possible, the default value is null, which is equal to an unlimited search and returns all characteristics of the part with their child characteristics. Please note that this does not include characteristics of subparts, to fetch these you have to use the according subpart-path.

If versioning is enabled on server side, you can specify the parameter withHistory when fetching entities. If set to true each fetched entity will contain a history, where all previous changes of this entity are stored. Each entry of the history contains the old values of the entity at this time, and a comment with information about why and by whom the change was made.

Known limitation Known limitation: Missing filter possibilities

Not all endpoints provide extensive filter possibilities, as it is much more performant to filter on client side. This reduces additional workload for the server.

Example Example: Deleting “MetalPart”
//Create PathInformationDto of "MetalPart"
var partPath = PathHelper.String2PartPathInformation("/MetalPart/");

//Get the part from server, depth 0 to only get the exact part and no children
var parts = await DataServiceClient.GetParts(partPath, depth:0);
//Get the part from the returned array (first entry in our case)
var metalPart = parts.First();

//Delete the part by its Uuid
await DataServiceClient.DeleteParts(new[] { metalPart.Uuid });

Info Deleting a part also deletes its subparts, characteristics and measurements. This is only possible if the server setting “Parts with measurement data can be deleted” is activated.

Measurements and values

Examples in this section:


Measurements contain measured values for specific characteristics, and belong to exactly one part. A measurement is represented by the class SimpleMeasurementDto and DataMeasurementDto, measured values are hold in class DataValueDto

Info A SimpleMeasurementDto is a measurement without values, whilst a DataMeasurementDto describes a measurement with measured values.

SimpleMeasurementDto

Property Description
AttributeDto[] Attributes The attributes that belong to that measurement
DateTime Created The time of creation, set by server
DateTime LastModified The time of the last modification, set by server
Guid PartUuid The Uuid of the part to which the measurement belongs
SimpleMeasurementStatusDto Status A status containing information about characteristics in/out tolerance
DateTime? Time The actual time of measuring
DateTime TimeOrMinDate Returns the measurement time and in case of no time specified, the minimum time allowed for server backend
DateTime TimeOrCreationDate Returns the measurement time and in case of no time specified, the creation date of the measurement.
Guid Uuid The Uuid of the measurement

A SimpleMeasurementDto contains important information about the measurement itself like the linked part or the measurement attributes. You can use this class to create a measurement without measured values.

DataMeasurementDto

Property Description
DataCharacteristicDto[] Characteristics The affected characteristics and their measured values

In most cases however you want to create a measurement with actual measured values. The DataMeasurementDto class contains an additional list of DataCharacteristicDto objects, which store information about a characteristic and the corresponding value.


Creating measurements and measured values

Info The LastModified property is only relevant for fetching measurements. On creating or updating a measurement it is set by server automatically.

Example Example: Creating a measurement with values
//Create an attribute that stores the measured value (if it does not exist already)
var valueAttributeDefinition = new AttributeDefinitionDto( WellKnownKeys.Value.MeasuredValue,
"Value", AttributeTypeDto.Float, 0 );

//Create the parts and characteristics you want to measure (details see other examples)
var part = new InspectionPlanPartDto {...};
var characteristic = new InspectionPlanCharacteristicDto {...};
await DataServiceClient.CreateParts( new[] { part } );
await DataServiceClient.CreateCharacteristics( new[] { characteristic } );

//Create value with characteristic
var valueAndCharacteristic = new DataCharacteristicDto
{
  //Always specify both, path and uuid of the characteristic. All other properties are obsolete
  Path = characteristic.Path,
  Uuid = characteristic.Uuid,
  Value = new DataValueDto( 0.5 ) //This is your measured value!
};

//Create a measurement and assign the value-characteristic combination
var measurement = new DataMeasurementDto
{
  Uuid = Guid.NewGuid(),
  PartUuid = part.Uuid,
  Time = DateTime.Now,
  Characteristics = new[] { valueAndCharacteristic }
};

//Create measurement on the server
await DataServiceClient.CreateMeasurementValues( new[] { measurement } );

The DataCharacteristicDto represents a connection between an actual measured value and a characteristic. It is linked to the characteristic via its path and uuid, and contains the value in form of a DataValueDto object.

Info The method CreateMeasurementValues is used to create measurements with measured values, not only measured values alone.

Info The Value property in DataCharacteristicDto is a shortcut to the attribute with key K1, which is the measured value.

Warning K1 is always associated with the measured value, changing this key is absolutely not advised as it would result in unexpected behavior!

Instead of using the Value property you can access the measured value attribute like any other attribute:

Example Example: Writing values using the attribute
//Surrounding code is skipped here, see above example for details

//Create the DataValueDto and add the attribute with value
valueAndCharacteristic.Value = new DataValueDto
{
  Attributes = new[] { new AttributeDto( WellKnownKeys.Value.MeasuredValue, 0.5 ) }
  //You can set other attributes for the entity Value (if defined) here too
};

In this example we fill the Attributes property of our DataValueDto object directly with the attribute and its value. You still use the key 1 (K1) because it is associated with the measured value. Other attributes that you may have defined for the entity of type Value can be assigned here, too. To assign values to attributes of a measurement, you add it to the measurement definition:

Example Example: Assigning values to measurement attributes
//Surrounding code is skipped here, see above example for details

//Create the attribute (remember to check if it already exists)
var measurementAttributeDefinition = new AttributeDefinitionDto( WellKnownKeys.Measurement.InspectorName,
"Inspector", AttributeTypeDto.AlphaNumeric, 30 );
await DataServiceClient.CreateAttributeDefinition( EntityDto.Measurement, measurementAttributeDefinition );

//Create the measurement
var measurement = new DataMeasurementDto
{
  Uuid = Guid.NewGuid(),
  PartUuid = part.Uuid,
  Time = DateTime.Now,
  Attributes = new[]
    {
      new AttributeDto( measurementAttributeDefinition.Key, "Ryan, Andrew" )
      //You can again set other attributes for the entity Measurement (if defined) here
    },
  Characteristics = new[] {...}
};

//Create measurement on the server
await DataServiceClient.CreateMeasurementValues( new[] { measurement } );

Best practice Best practice: Use the property Time

This property is a shortcut to the attribute Time with key K4.


Fetching measurements and measured values

Next to creating or updating measurements, another important functionality is fetching those measurements and measured values according to different criteria.

Info To improve performance path information of characteristics in DataMeasurementDtos are always blank when fetching data.

Example Example: Fetching measurements of a part
//Create PathInformation of the desired part that contains measurements
var partPath = PathHelper.RoundtripString2PathInformation( "P:/Measured part/" );

//Fetch all measurements of this part without measured values
var fetchedMeasurements = await DataServiceClient.GetMeasurements( partPath );

//Or fetch all measurements of this part with measured values
var fetchedMeasurementsWithValues = await DataServiceClient.GetMeasurementValues( partPath );

This is the simplest way to fetch measurements and the associated measured values as the unfiltered method returns all measurements of the specified part. Each measurement then contains a DataCharacteristicDto objects linking values to characteristics. Since this can result in a large collection of results you have the possibility to create a filter based on different criteria. This can be done by using MeasurementFilterAttributesDto which is derived from AbstractMeasurementFilterAttributesDto:

AbstractMeasurementFilterAttributes

Property Description
AggregationMeasurementSelectionDto AggregationMeasurements Specifies what types of measurements will be returned (normal/aggregated measurements or both).
bool Deep false if measurements for only the given part should be searched, true if measurements for the given part and contained subparts should be searched.
DateTime? FromModificationDate Specifies a date to select all measurements that where modified after that date.
DateTime? ToModificationDate Specifies a date to select all measurements that where modified before that date.
int LimitResult The maximum number of measurements that should be returned, unlimited if set to -1 (default).
Guid[] MeasurementUuids List of uuids of measurements that should be returned.
OrderDto[] OrderBy The sort order of the resulting measurements.
Guid[] PartUuids The list of parts that should be used to restrict the measurement search.
GenericSearchConditionDto SearchCondition The search condition that should be used.
bool IsUnrestricted Convenience property to check if restrictions are empty (LimitResult, SearchCondition, PartUuids all empty)

The class MeasurementValueFilterAttributesDto contains further possibilities.

MeasurementValueFilterAttributesDto

Property Description
Guid[] CharacteristicsUuidList The list of characteristics including its measured values that should be returned.
ushort[] MergeAttributes The list of primary measurement keys to be used for joining measurements across multiple parts on the server side.
MeasurementMergeConditionDto MergeCondition Specifies the condition that must be met when merging measurements across multiple parts using a primary key. Default value is MeasurementMergeConditionDto.MeasurementsInAllParts.
Guid MergeMasterPart Specifies the part to be used as master part when merging measurements across multiple parts using a primary key.
AttributeSelector RequestedMeasurementAttributes The selector for the measurement attributes. Default: all
AttributeSelector RequestedValueAttributes The selector for the measurement value attributes. Default: all

Please find some usefull examples for usage of the filter in the following section. The part path is the same as in the first example of this section.

Example Example: Fetching measurements in a time range
//Fetch all measurements of the part
var fetchedMeasurements = await DataServiceClient.GetMeasurementValues(
  partPath,
  new MeasurementValueFilterAttributesDto
  {
    AggregationMeasurements = AggregationMeasurementSelectionDto.All,
    Deep = true,
    FromModificationDate = DateTime.Now - TimeSpan.FromDays(2),
    ToModificationDate = DateTime.Now
  });

This returns the measurements of the last 48 hours. You can also use actual dates instead of a time range. It is not required to specify both dates, you could use FromModificationDate and ToModificationDate independently.

Example Example: Fetching specified attributes
//Fetch all measurements of the part
var fetchedMeasurements = await DataServiceClient.GetMeasurementValues(
  partPath,
  new MeasurementValueFilterAttributesDto
  {
    AggregationMeasurements = AggregationMeasurementSelectionDto.All,
    Deep = true,
    FromModificationDate = DateTime.Now - TimeSpan.FromDays(2),
    ToModificationDate = DateTime.Now,
    RequestedMeasurementAttributes = new AttributeSelector(){ Attributes = new ushort[]{ WellKnownKeys.Measurement.Time, WellKnownKeys.Measurement.InspectorName, WellKnownKeys.Measurement.Contract } }
  });

To retrieve only a subset of measurement attributes we set RequestedMeasurementAttributes, which requires an AttributeSelector. Simply add the keys of the desired attributes to the Attributes property, so in this case time, inspector name and contract attributes. The property RequestedValueAttributes works the same way for measured value attributes.

Info The measured value attribute K1 is always returned in the Value property of a DataCharacteristicDto, even when not explicitly requested in AttributeSelector.

Example Example: Using search conditions
//Fetch all measurements of the part
var fetchedMeasurements = await DataServiceClient.GetMeasurementValues(
  partPath,
  new MeasurementValueFilterAttributesDto
  {
    SearchCondition = new GenericSearchAttributeConditionDto
    {
      Attribute = WellKnownKeys.Measurement.Time,
      Operation = OperationDto.GreaterThan,
      Value = XmlConvert.ToString(DateTime.UtcNow - TimeSpan.FromDays(2), XmlDateTimeSerializationMode.Utc)
    }
  });

In this case we use a GenericSearchAttributeConditionDto, a search condition for attributes. You specify the attribute key with the value of interest, an operation and the value you want to check against. This example again returns the measurements of the last 48 hours. To create more complex filters please use GenericSearchAndDto, GenericSearchOrDto and/or GenericSearchNotDto.

Best practice Best practice: Consider filtering on client side

Instead of defining complex queries to retrieve filtered results, you should consider requesting data with less restrictions, and filter it on client side according to your criteria. This reduces workload for the server, as filtering requires more resources.

RawData

Examples in this section:


Additional data are attachments that can be added to any entity in the inspection plan. This can be text, images, log files, CAD models or any binary file. There is no limit to the number of files and you can edit or remove additional data as desired. Every additional data is linked to a RawDataInformationDto object:

RawDataInformationDto

Property Description
DateTime Created The time of creation, set by server
string FileName The filename of the additional data, which does not have to be unique (unlike in a real filesystem).
int? Key A unique key that identifies this specific additional data for a corresponding entity. Entities can have multiple additional data objects that are distinguished by this key.
LastModified The time of the last modification, set by server
Guid MD5 A uuid using the MD5-Hash of the additional data object (the file).
string MimeType The MIME-Type of the additional data. (List of MIME-Types)
int Size The size of the additional data in bytes.
RawDataTargetEntityDto Target The target object this additional data object belongs to.

Best practice Best practice: Use the helper methods of RawDataTargetEntityDto

This class offers several helper methods to create the link to the associated entity.

RawDataTargetEntityDto

Method Description
RawDataTargetEntityDto CreateForCharacteristic(Guid uuid) The target is a characteristic.
RawDataTargetEntityDto CreateForPart(Guid uuid) The target is a part.
RawDataTargetEntityDto CreateForMeasurement(Guid uuid) The target is a measurement.
RawDataTargetEntityDto CreateForValue(Guid measurementUuid, Guid characteristicUuid) The target is a measured value of a specific measurement.
Example Example: Creating additional data
//SamplePart is the part (either new or fetched) where additional data should be added
//Create/choose additional data
var additionalData = Encoding.UTF8.GetBytes( "More important information" );
var target = RawDataTargetEntityDto.CreateForPart( SamplePart.Uuid );

//Create RawDataInformationDto
var information = new RawDataInformationDto
{
	FileName = "SampleFile.txt",
	MimeType = "text/plain",
	Key = -1,
	MD5 = new Guid( MD5.Create().ComputeHash( additionalData ) ),
	Size = additionalData.Length,
	Target = target
};

//Create on server
await RawDataServiceClient.CreateRawData(information, additionalData);

Info When using -1 the server will generate a new unique key.

Example Example: Fetching information about additional data
//SamplePart is the part (either new or fetched) where additional data should be fetched
var target = RawDataTargetEntityDto.CreateForPart( SamplePart.Uuid );

//Fetch a list of additional data of our target (the SamplePart)
var additionalDataInformation = await RawDataServiceClient.ListRawData( new[] { target } );

This will result in an array of all RawDataInformationDto objects linked to the specified part, in this case the SamplePart with only our SampleFile.txt as additional data. This means that we only get information about the files associated with our part but not the files itself.
With the overview of the available additional data we can fetch the files of interest:

Example Example: Fetching additional data
//var additionalDataInformation as above

//Get the RawDataInformationDto object (the first entry in our case)
var informationAboutSampleFile = additionalDataInformation.First();

//Fetch the file using the correct RawDataInformation
var sampleFile = await RawDataServiceClient.GetRawData( informationAboutSampleFile );

With this you will retrieve the actual additional data in its byte representation. Now you can edit the file as desired and later update the additional data:

Example Example: Updating additional data
//var informationAboutSampleFile as above

//Fetch the file using the correct RawDataInformation
var sampleFile = await RawDataServiceClient.GetRawData( informationAboutSampleFile );

//Edit the file
sampleFile = Encoding.UTF8.GetBytes( "Important information changed!" );

//Update RawDataInformation
informationAboutSampleFile.MD5 = new Guid(MD5.Create().ComputeHash(sampleFile)); //recompute hash
informationAboutSampleFile.Size = sampleFile.Length;

//Update file on server
await RawDataServiceClient.UpdateRawData(informationAboutSampleFile, sampleFile);

It is important to update the RawDataInformationDto as well, so it matches the new file. The hash and length need to be updated while key and target stays the same. Changing the filename or MIME-Type is also possible. The server automatically updates the property LastModified, the date of creation is instead not changed because we only updated our data.

Example Example: Deleting additional data
//var informationAboutSampleFile and Part as in above examples

//Delete the specific file of our SamplePart
await RawDataServiceClient.DeleteRawDataForPart( part.Uuid, informationAboutSampleFile.Key );

//Or simply delete all additional data of our SamplePart
await RawDataServiceClient.DeleteRawDataForPart( part.Uuid );

Best practice Best practice: Use the specific delete method per entity

The RawDataServiceRestClient offers a delete method for each type of entity which can contain additional data. They need the entity’s uuid and the additional data key to delete a specific file or only the entity uuid to delete all additional data of the entity.

Info When creating or fetching additional data for measured values, you need a combination of the measurement Uuid and the characteristic Uuid that contains the value with data of interest:

Example Example: Creating or fetching additional data of measured values
//sampleMeasurement is a fetched/created measurement of a part
//sampleChar is the characteristic that contains the value
//Create the measured value as target
var target = RawDataTargetEntityDto.CreateForValue( sampleMeasurement.Uuid, sampleChar.Uuid);

//Create the additional data
var additionalData = Encoding.UTF8.GetBytes("This is additional data of a measured value.");
var information = new RawDataInformationDto{...}; //see first example
await RawDataServiceClient.CreateRawData(information, additionalData);

//Fetching again using the target
var additionalDataInformation = await RawDataServiceClient.ListRawData( new[] { target } );

Best practice Best practice: A helper for Uuids

The API offers the class StringUuidTools containing different useful methods for working with Uuids.

StringUuidTools

Method Description
void CheckUuid( RawDataEntityDto entity, string uuid ) Check if a Uuid has the valid syntax. Throws an ArgumentOutOfRangeException if not.
void CheckUuids( RawDataEntityDto entity, IEnumerable<string> uuids ) Check a list of Uuids for valid syntax. Throws an ArgumentOutOfRangeException if a Uuid is not correct.
string CreateStringUuidPair( Guid measurementGuid, Guid characteristicGuid ) Creates a string containig a measurementUuid and a characteristicUuid in the form measurementUuid|characteristicUuid.
bool IsStringUuidPair( string uuidPair ) Checks if a given string is a unique UUID pair (in the form measurementUuid|characteristicUuid).
ValueRawDataIdentifier SplitStringUuidPair( string uuidPair ) Splits a string containig a measurementUuid and a characteristicUuid in the form measurementUuid|characteristicUuid.
List StringUuidListToGuidList( IEnumerable<string> uuids )</code></nobr> Creates a list of Uuids from a list of Uuid strings.
Guid StringUuidToGuid( string uuid ) Create a Uuid from a Uuid string.
bool TrySplitStringUuidPair( string uuidPair, out ValueRawDataIdentifierDto result ) Try to splits a string containig a measurementUuid and a characteristicUuid in the form measurementUuid|characteristicUuid.

Archive lookup


Examples in this section:


The .NET SDK offers special functionality to work with archives, including the possibility to list contents of an archive which is saved as raw data on any target entity, or retrieving only specified files of an archive instead of requesting the whole archive. This can save time and data if you only need a small portion of an archive.

Info Archive lookup currently works with archives of filetype .zip

RawDataArchiveEntriesDto

Property Description
RawDataInformationDto ArchiveInfo Information about the raw data, which is an archive. Same as RawDataInformationDto.
string[] Entries A list of all files inside the archive, including subfolders.
Example Example: Fetch information about a raw data archive
var target = RawDataTargetEntityDto.CreateForPart( SamplePart.Uuid );

//Raw data with key 1 is an archive
//Fetch information and a list of archive entries
var archiveInformation = await RawDataServiceClient.ListRawDataArchiveEntries( target, 1 );

You will receive a RawDataArchiveEntriesDto containing archive information and a list of filenames. Since folders are also entries from the point of view of the archive, they are part of the result, e.g. an entry like Subfolder/. Please note that these entries do not contain data, so requesting the entry Subfolder/ will result in an empty result. You will not receive the folders content. For this you need to explicitly fetch the files located in the subfolder with their full name, including folder structure, as shown in the next example.

Once you know the contents of specified archive you can request single files similiar to fetching normal raw data. You specify the raw data archive with its RawDataTargetEntityDto and key, the requested file with its filename.

Info The filename is case insensitive. Archive lookup will return the first occurence of specified file if two files with the same name exist in the same folder.

Example Example: Fetch content of a raw data archive
var target = RawDataTargetEntityDto.CreateForPart( SamplePart.Uuid );

//Raw data with key 1 is an archive
//Fetch the file information.txt from the archive (raw data with key 1 of SamplePart)
var file = await RawDataServiceClient.GetRawDataArchiveContent( target, 1, "information.txt" );

//Fetch a file from a subfolder inside the archive
var fileSubfolder = await RawDataServiceClient.GetRawDataArchiveContent( target, 1, "Subfolder/information.txt" );

This will fetch the file “information.txt”, or as in the second call the file “information.txt” from a subfolder.

You can request information and file lists for more than one archive at a time using special query functionality.

RawDataBulkQueryDto

Property Description
RawDataSelectorDto[] Selectors List of selectors. Each selector specifies one raw data (which should be an archive). A selector contains the RawDataTargetEntityDto and the raw data key of the archive.
Example Example: Fetch information about multiple archives
var target = RawDataTargetEntityDto.CreateForPart( SamplePart.Uuid );

//Raw data with keys 0, 1, 7 are archives
//Fetch information and a list of archive entries for each selector
var archiveEntryQuery = await RawDataServiceClient.RawDataArchiveEntryQuery(
  new RawDataBulkQueryDto( new[]
    {
      new RawDataSelectorDto( 0, target ),
      new RawDataSelectorDto( 1, target ), //more raw data from the same target entity
      new RawDataSelectorDto( 7, target_5 ) //raw data from another target entity
    }
));

You will receive a list of RawDataArchiveEntriesDtos for all archives specified by a selector. This method offers some convenience functionality: it also accepts an array of RawDataInformationDtos as parameter to specify targeted raw data archives.

You can fetch multiple files of the same or different archives using the method GetRawDataArchiveContent. The property Selectorscontains the same information as the ArchiveEntryQuery above, with an additional list of requested files per raw data archive.

RawDataArchiveBulkQueryDto

Property Description
RawDataArchiveSelectorDto[] Selectors List of selectors. Each selector specifies one raw data (which should be an archive). A selector contains the RawDataTargetEntityDto, the raw data key of the archive and a list of requested files.
Example Example: Fetch contents of multiple archives
var target = RawDataTargetEntityDto.CreateForPart( SamplePart.Uuid );

//Raw data with key 1 and 7 is an Archive
//Fetch requested archive contents
var archiveContentQuery = await RawDataServiceClient.RawDataArchiveContentQuery(
  new RawDataArchiveBulkQueryDto( new[]
    {
      new RawDataArchiveSelectorDto( 0, target, new[]
        {
          "information.txt",
          "thumbnail.jpg"
        }),
      new RawDataArchiveSelectorDto( 7, target_5, new[]
        {
          "Subfolder/someFile.txt",
          "cadFile.cad"
        })
}));

You will receive a list of RawDataArchiveContentDtos, one for each file, with no separation between files of different raw data archives.

RawDataArchiveContentDto

Property Description
RawDataInformationDto ArchiveInfo Information about the raw data, which is an archive. Same as RawDataInformationDto.
string FileName The filename of the requested file.
int Size Length of data.
byte[] Data Actual data representing the file.
Guid MD5 MD5 checksum of data.

As both methods work like queries, a non existing file or archive simply wont be part of the result. If the file “cadFile.cad” in above example doesnt exist the result will simply contain only 3 files instead of 4. You wont get an exception. This behaves differently if one specified raw data is no archive, in this case an exception is thrown.

Exceptions

Archive lookup only works with supported archives, currently the .zip format. If you try to list or fetch contents of raw data in any other format you will receive the following exception: WrappedServerErrorException: Specified RawData is no archive of supported format!

Security

Examples in this section:


Access to PiWeb server service might require authentication. Our .NET client supports all authentication modes:

  • Basic authentication based on username and password,
  • Windows authentication based on Active Directory integration,
  • Certificate-based authentication,
  • Hardware certificate based authentication,
  • OAuth (PiWeb Cloud only)

Authentication mode and credentials (if necessary) can be set via the client’s property AuthenticationContainer.

Example Example: Authenticate with basic authentication
//Create an AuthenticationContainer
var authContainer = new AuthenticationContainer
(
  AuthenticationMode.NoneOrBasic,
  new NetworkCredential( "API User", "pa55w0rd" )
);

//Set it as the clients AuthenticationContainer
DataServiceClient.AuthenticationContainer = authContainer;
//The client will now use your credentials for requests

The AuthenticationContainer provides information about authentication mode and credentials to use for requests. If authentication is activated accessing the services is only possible with valid credentials. Please note that PiWeb Server has permission management. Users can have different permissions for different functionalities like reading or creating entities.

Trying to fetch data without permission will still work if credentials are correct, but will return an empty result. Actions however, e.g. creating a part will result in an exception (mostly HTTP 401 Unauthorized), so make sure that you have the needed permissions for your requests.

If you don’t know your account, credentials or permissions you should contact your PiWeb Server administrator.

Example Example: Authenticate using ActiveDirectory (Windows)
//Create an AuthenticationContainer with mode Windows (ActiveDirectory)
var authContainer = new AuthenticationContainer
(
  AuthenticationMode.Windows
);

//Set it as the clients AuthenticationContainer
DataServiceClient.AuthenticationContainer = authContainer;

This tells the ServiceClient to use Windows Authentication (ActiveDirectory). From now on the client will use your Windows user account for authentication. You do not have to specify credentials as the client will check and fetch your information from the organization directory.
Remember that your authentication mode has to match the mode set in the server settings.

Migration Guide


Info Version 6.0.0 of our .NET SDK NuGet introduced major architectural changes. This migration guide will help you migrate from version 5.X.X to a NuGet version >6.0.0

.NET SDK NuGet Version 6.0.0

With Version 6.0.0 of our .NET SDK NuGet we introduced major architectural changes. Another important change affects the PackageID of our NuGet: we renamed it according to our new naming scheme. The new PackageID is Zeiss.PiWeb.Api.Rest. This NuGet is the official successor of Zeiss.IMT.PiWebApi.Client, which wont get new feature and is therefore discontinued. Critical bugs may be fixed, but migrating is strongly advised for continuous support.

Info The new NuGet wont show up as an update, as the PackageID changed. To migrate you need to explicitly deinstall Zeiss.IMT.PiWebApi.Client and install Zeiss.PiWeb.Api.Rest!

New structure

The new NuGet structure aims for clearer architecture and different levels of dependencies. The following image gives you a short impression of the new structure:

The .NET SDK API Nuget is now split into the three shown parts, and each part is its own NuGet listed on NuGet.org. The blue arrows describe their dependencies between each other. Zeiss.PiWeb.Api.Rest needs both NuGets to function correctly, whilst Zeiss.Piweb.Api.Rest.Dtos needs Zeiss.PiWeb.Definitions. When installing Zeiss.PiWeb.Api.Rest all dependencies are installed automatically in the right version.

The advantages of this splitting are lesser dependencies to client related NuGets like IdentityModel or System.IdentityModel.Tokens.Jwt when only working with API data in backend code. All PiWeb applications are communicating using the API .NET SDK. The older NuGet required all dependencies everywhere it was installed, even in backend code where only data is processed but not sent, which wasn’t favorable in an architectural view.
The new NuGets now can partly be used independent: You only need a key of our WellKnownKeys? No problem, just install Zeiss.PiWeb.Definitions. Just working with data? Install Zeiss.Piweb.Api.Rest.Dtos. At this point the only dependencies are Newtonsoft.Json and JetBrains.Annotations. You finally want to send or recieve data? Then install the client NuGet Zeiss.PiWeb.Api.Rest. This way your backend projects will stay free of client dependencies.

Migration

The following steps have to be done to migrate from version 5.0 to version 6.0.

Referencing the new packages

The very first step is the replacement of the existing Nuget package with the current version. As described above, this step requires to remove the existing reference to Zeiss.IMT.PiWebApi.Client in your projects and add a new reference to the Zeiss.PiWeb.Api.Rest package. The default steps of updating a Nuget package does not work since version 6.0 has a different package ID than version 5.0.

If the package reference is only required for getting the data-transfer-objects, it can be useful to reference the Nuget package Zeiss.PiWeb.Definitions instead of Zeiss.PiWeb.Api.Rest. This might help reducing dependencies.

Renaming of Data Transfer Object classes

A well-designed application should have a domain model. In order to prevent naming collisions between the objects located in the domain model and those used for data transfer (DTO), we decided to add the Dto-suffix to our data-transfer-objects. After updating to version 6.0, the naming of the referenced data-transfer-objects in your code needs to be corrected.

Adapting namespaces

As part of the breakdown into three Nuget-packages, the namespaces has been changed.

  • First of all, the root namespaces has been changed from Zeiss.IMT.PiWeb.Api to Zeiss.PiWeb.Api.
  • The namespace containing the interface of the REST clients (including supporting types) is Zeiss.PiWeb.Api.Rest.Contracts.
  • The REST client implementations are located in Zeiss.PiWeb.Api.Rest.HttpClient and sub-namespaces.
  • The namespace of the well-known-keys is Zeiss.PiWeb.Api.Definitions.
  • The data transfer objects are located in Zeiss.PiWeb.Api.Rest.Dtos and sub-namespaces.