The .NET SDK is suitable for all .NET languages. You can include it to your project either by
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.
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.
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.
//The Uri of your PiWeb server
var uri = new Uri( "http://piwebserver:8080" );
//Creating the client
var DataServiceClient = new DataServiceRestClient( uri );
//The Uri of your PiWeb server
var uri = new Uri( "http://piwebserver:8080" );
//Creating the client
var RawDataServiceClient = new RawDataServiceRestClient( uri );
Each method runs asynchronously and returns an awaitable
Task
. The result will be available once theTask
has completed.
All methods accept a
CancellationToken
which you can use to cancel a request.
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: Use
WellKnownKeys
classOur .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
:
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. |
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 . |
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.
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.
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: 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.
//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 );
}
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:
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. |
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.
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.
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
:
Property | Description |
---|---|
InspectionPlanBase[] History |
The version history for this inspection plan entity. |
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:
Property | Description |
---|---|
InspectionPlanEntityDto Type |
Type of the path element (Part or Characteristic) |
String Value |
Path elments’ name |
Best practice: To create a
PathInformationDto
object you might use thePathHelper
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 |
![]()
DataServiceClient
in examples refers to an actual instance ofDataServiceRestClient
pointing to a server.
PathHelper
classvar 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 /
.
Please note that a part or characteristic must not contain a backslash
\
.
//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: 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.
//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} );
//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: 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.
//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 });
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.
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
A
SimpleMeasurementDto
is a measurement without values, whilst aDataMeasurementDto
describes a measurement with measured values.
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.
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.
The
LastModified
property is only relevant for fetching measurements. On creating or updating a measurement it is set by server automatically.
//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.
The method
CreateMeasurementValues
is used to create measurements with measured values, not only measured values alone.
The
Value
property inDataCharacteristicDto
is a shortcut to the attribute with key K1, which is the measured value.
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:
//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:
//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: Use the property
Time
This property is a shortcut to the attribute Time with key K4.
Next to creating or updating measurements, another important functionality is fetching those measurements and measured values according to different criteria.
To improve performance path information of characteristics in
DataMeasurementDto
s are always blank when fetching data.
//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
:
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.
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.
//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.
//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.
The measured value attribute
K1
is always returned in theValue
property of aDataCharacteristicDto
, even when not explicitly requested inAttributeSelector
.
//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: 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.
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:
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: Use the helper methods of
RawDataTargetEntityDto
This class offers several helper methods to create the link to the associated entity.
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. |
//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);
When using -1 the server will generate a new unique key.
//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:
//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:
//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.
//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: 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.
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:
//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: A helper for Uuids
The API offers the class
StringUuidTools
containing different useful methods for working with Uuids.
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 |
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. |
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.
Archive lookup currently works with archives of filetype
.zip
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. |
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.
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.
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.
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. |
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 Selectors
contains the same information as the ArchiveEntryQuery
above, with an additional list of requested files per raw data archive.
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. |
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.
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.
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!
Examples in this section:
Access to PiWeb server service might require authentication. Our .NET client supports all authentication modes:
Authentication mode and credentials (if necessary) can be set via the client’s property AuthenticationContainer
.
//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.
//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.
Version
6.0.0
of our .NET SDK NuGet introduced major architectural changes. This migration guide will help you migrate from version5.X.X
to a 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.
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!
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.
The following steps have to be done to migrate from version 5.0 to version 6.0.
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.
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.
As part of the breakdown into three Nuget-packages, the namespaces has been changed.
Zeiss.IMT.PiWeb.Api
to Zeiss.PiWeb.Api
.Zeiss.PiWeb.Api.Rest.Contracts
.Zeiss.PiWeb.Api.Rest.HttpClient
and sub-namespaces.Zeiss.PiWeb.Api.Definitions
.Zeiss.PiWeb.Api.Rest.Dtos
and sub-namespaces.