From cf89dc1b1fae5b17153dfa0967baa4d9d963cc5e Mon Sep 17 00:00:00 2001 From: Basel Rustum Date: Thu, 11 Jun 2020 02:39:44 -0700 Subject: [PATCH] ADT C# SDK Samples Improvements (#12683) * fix(samples): improve on the snippet comments * Create a relationship samples --- .../ComponentSamples.cs | 10 +- .../CustomRelationship.cs | 33 +++ .../DigitalTwinsLifecycleSamples.cs | 38 +-- .../ModelLifecycleSamples.cs | 23 +- .../DigitalTwinsClientSample/Program.cs | 3 + .../PublishTelemetrySamples.cs | 4 +- .../RelationshipSamples.cs | 226 ++++++++++++++++++ .../SamplesConstants.cs | 47 +++- .../UniqueIdHelper.cs | 5 +- .../Azure.DigitalTwins.Core/samples/Readme.md | 194 +++++++++++---- .../src/DigitalTwinsClient.cs | 108 ++++++--- .../src/Serialization/BasicDigitalTwin.cs | 2 +- .../src/Serialization/BasicRelationship.cs | 39 ++- .../Serialization/UpdateOperationsUtility.cs | 2 +- 14 files changed, 591 insertions(+), 143 deletions(-) create mode 100644 sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/CustomRelationship.cs create mode 100644 sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/RelationshipSamples.cs diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/ComponentSamples.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/ComponentSamples.cs index ba1082e7f79e..13130c80d589 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/ComponentSamples.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/ComponentSamples.cs @@ -42,7 +42,7 @@ public async Task RunSamplesAsync(DigitalTwinsClient client) newComponentModelPayload, newModelPayload }); - Console.WriteLine($"Created models Ids {componentModelId} and {modelId} with response {createModelsResponse.GetRawResponse().Status}."); + Console.WriteLine($"Created models with Ids {componentModelId} and {modelId}. Response status: {createModelsResponse.GetRawResponse().Status}."); #region Snippet:DigitalTwinsSampleCreateBasicTwin @@ -77,7 +77,7 @@ public async Task RunSamplesAsync(DigitalTwinsClient client) string basicDtPayload = JsonSerializer.Serialize(basicTwin); Response createBasicDtResponse = await client.CreateDigitalTwinAsync(basicDtId, basicDtPayload); - Console.WriteLine($"Created digital twin {basicDtId} with response {createBasicDtResponse.GetRawResponse().Status}."); + Console.WriteLine($"Created digital twin with Id {basicDtId}. Response status: {createBasicDtResponse.GetRawResponse().Status}."); #endregion Snippet:DigitalTwinsSampleCreateBasicTwin @@ -129,7 +129,7 @@ public async Task RunSamplesAsync(DigitalTwinsClient client) string dt2Payload = JsonSerializer.Serialize(customTwin); Response createCustomDtResponse = await client.CreateDigitalTwinAsync(customDtId, dt2Payload); - Console.WriteLine($"Created digital twin {customDtId} with response {createCustomDtResponse.GetRawResponse().Status}."); + Console.WriteLine($"Created digital twin with Id {customDtId}. Response status: {createCustomDtResponse.GetRawResponse().Status}."); #endregion Snippet:DigitalTwinsSampleCreateCustomTwin @@ -161,7 +161,7 @@ public async Task RunSamplesAsync(DigitalTwinsClient client) Response response = await client.UpdateComponentAsync(basicDtId, "Component1", updatePayload); - Console.WriteLine($"Updated component for digital twin {basicDtId}. Update response status: {response.GetRawResponse().Status}"); + Console.WriteLine($"Updated component for digital twin with Id {basicDtId}. Response status: {response.GetRawResponse().Status}"); #endregion Snippet:DigitalTwinsSampleUpdateComponent @@ -171,7 +171,7 @@ public async Task RunSamplesAsync(DigitalTwinsClient client) response = await client.GetComponentAsync(basicDtId, SamplesConstants.ComponentPath); - Console.WriteLine($"Get component for digital twin: \n{response.Value}. Get response status: {response.GetRawResponse().Status}"); + Console.WriteLine($"Retrieved component for digital twin with Id {basicDtId}. Response status: {response.GetRawResponse().Status}"); #endregion Snippet:DigitalTwinsSampleGetComponent diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/CustomRelationship.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/CustomRelationship.cs new file mode 100644 index 000000000000..43548e381b8a --- /dev/null +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/CustomRelationship.cs @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Text.Json.Serialization; +using Azure.DigitalTwins.Core.Serialization; + +namespace Azure.DigitalTwins.Core.Samples +{ + /// + /// Custom type for a sample illustrating how someone can create their own class to match a relationship type + /// for serialization, instead of using . + /// + internal class CustomRelationship + { + [JsonPropertyName("$relationshipId")] + public string Id { get; set; } + + [JsonPropertyName("$targetId")] + public string TargetId { get; set; } + + [JsonPropertyName("$sourceId")] + public string SourceId { get; set; } + + [JsonPropertyName("$relationshipName")] + public string Name { get; set; } + + [JsonPropertyName("Prop1")] + public string Prop1 { get; set; } + + [JsonPropertyName("Prop2")] + public int Prop2 { get; set; } + } +} diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/DigitalTwinsLifecycleSamples.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/DigitalTwinsLifecycleSamples.cs index 298d2c398687..74a220bb792f 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/DigitalTwinsLifecycleSamples.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/DigitalTwinsLifecycleSamples.cs @@ -117,8 +117,8 @@ private async Task DeleteAllModelsAsync() foreach (string modelId in models) { - await client.DeleteModelAsync(modelId); - Console.WriteLine($"Deleted model {modelId}"); + Response deleteModelResponse = await client.DeleteModelAsync(modelId); + Console.WriteLine($"Deleted model with Id {modelId}. Response status: {deleteModelResponse.Status}"); } } catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.NotFound) @@ -148,7 +148,7 @@ private async Task AddAllModelsAsync() try { Response> response = await client.CreateModelsAsync(modelsToCreate); - Console.WriteLine($"Created models status: {response.GetRawResponse().Status}"); + Console.WriteLine($"Created models. Response status: {response.GetRawResponse().Status}"); } catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.Conflict) { @@ -175,7 +175,7 @@ public async Task GetAllModelsAsync() AsyncPageable allModels = client.GetModelsAsync(); await foreach (ModelData model in allModels) { - Console.WriteLine($"Model Id: {model.Id}, display name: {model.DisplayName["en"]}, upload time: {model.UploadTime}, is decommissioned: {model.Decommissioned}"); + Console.WriteLine($"Retrieved model with Id {model.Id}, display name {model.DisplayName["en"]}, upload time {model.UploadTime}, and decommissioned: {model.Decommissioned}"); } #endregion Snippet:DigitalTwinsSampleGetModels @@ -199,43 +199,33 @@ public async Task DeleteTwinsAsync() try { // Delete all relationships - - #region Snippet:DigitalTwinsSampleGetRelationships - AsyncPageable relationships = client.GetRelationshipsAsync(twin.Key); - - #endregion Snippet:DigitalTwinsSampleGetRelationships - await foreach (var relationshipJson in relationships) { BasicRelationship relationship = JsonSerializer.Deserialize(relationshipJson); await client.DeleteRelationshipAsync(twin.Key, relationship.Id); - Console.WriteLine($"Found and deleted relationship {relationship.Id}"); + Console.WriteLine($"Found and deleted relationship with Id {relationship.Id}."); } // Delete any incoming relationships - - #region Snippet:DigitalTwinsSampleGetIncomingRelationships - AsyncPageable incomingRelationships = client.GetIncomingRelationshipsAsync(twin.Key); - #endregion Snippet:DigitalTwinsSampleGetIncomingRelationships - await foreach (IncomingRelationship incomingRelationship in incomingRelationships) { await client.DeleteRelationshipAsync(incomingRelationship.SourceId, incomingRelationship.RelationshipId); - Console.WriteLine($"Found and deleted incoming relationship {incomingRelationship.RelationshipId}"); + Console.WriteLine($"Found and deleted incoming relationship with Id {incomingRelationship.RelationshipId}."); } // Now the digital twin should be safe to delete + string digitalTwinId = twin.Key; #region Snippet:DigitalTwinsSampleDeleteTwin - await client.DeleteDigitalTwinAsync(twin.Key); + Response deleteDigitalTwinResponse = await client.DeleteDigitalTwinAsync(digitalTwinId); + Console.WriteLine($"Deleted digital twin with Id {digitalTwinId}. Response Status: {deleteDigitalTwinResponse.Status}"); #endregion Snippet:DigitalTwinsSampleDeleteTwin - Console.WriteLine($"Deleted digital twin {twin.Key}"); } catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.NotFound) { @@ -353,8 +343,6 @@ public async Task ConnectTwinsTogetherAsync() // We deserialize as BasicRelationship to get the entire custom relationship (custom relationship properties). IEnumerable relationships = JsonSerializer.Deserialize>(relationshipSet.Value); - #region Snippet:DigitalTwinsSampleCreateRelationship - // From loaded relationships, get the source Id and Id from each one, // and create it with full relationship payload foreach (BasicRelationship relationship in relationships) @@ -375,8 +363,6 @@ await client.CreateRelationshipAsync( Console.WriteLine($"Relationship {relationship.Id} already exists: {ex.Message}"); } } - - #endregion Snippet:DigitalTwinsSampleCreateRelationship } } @@ -421,10 +407,9 @@ public async Task CreateEventRoute() }; Response createEventRouteResponse = await client.CreateEventRouteAsync(_eventRouteId, eventRoute); + Console.WriteLine($"Created event route with Id {_eventRouteId}. Response status: {createEventRouteResponse.Status}"); #endregion Snippet:DigitalTwinsSampleCreateEventRoute - - Console.WriteLine($"Created event route: {_eventRouteId} Response status: {createEventRouteResponse.Status}"); } catch (Exception ex) { @@ -443,10 +428,9 @@ public async Task DeleteEventRoute() #region Snippet:DigitalTwinsSampleDeleteEventRoute Response response = await client.DeleteEventRouteAsync(_eventRouteId); + Console.WriteLine($"Deleted event route with Id {_eventRouteId}. Response status: {response.Status}"); #endregion Snippet:DigitalTwinsSampleDeleteEventRoute - - Console.WriteLine($"Successfully deleted event route: {_eventRouteId}, status: {response.Status}"); } catch (Exception ex) { diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/ModelLifecycleSamples.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/ModelLifecycleSamples.cs index 785b67bd8ee7..797308aeb35e 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/ModelLifecycleSamples.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/ModelLifecycleSamples.cs @@ -26,15 +26,15 @@ public async Task RunSamplesAsync(DigitalTwinsClient client) // For the purpose of this example we will create temporary models using random model Ids and then decommission a model. // We have to make sure these model Ids are unique within the DT instance. - string newComponentModelId = await GetUniqueModelIdAsync(SamplesConstants.TemporaryComponentModelPrefix, client); + string componentModelId = await GetUniqueModelIdAsync(SamplesConstants.TemporaryComponentModelPrefix, client); string sampleModelId = await GetUniqueModelIdAsync(SamplesConstants.TemporaryModelPrefix, client); string newComponentModelPayload = SamplesConstants.TemporaryComponentModelPayload - .Replace(SamplesConstants.ComponentId, newComponentModelId); + .Replace(SamplesConstants.ComponentId, componentModelId); string newModelPayload = SamplesConstants.TemporaryModelWithComponentPayload .Replace(SamplesConstants.ModelId, sampleModelId) - .Replace(SamplesConstants.ComponentId, newComponentModelId); + .Replace(SamplesConstants.ComponentId, componentModelId); // Then we create the model @@ -43,7 +43,7 @@ public async Task RunSamplesAsync(DigitalTwinsClient client) #region Snippet:DigitalTwinsSampleCreateModels Response> response = await client.CreateModelsAsync(new[] { newComponentModelPayload, newModelPayload }); - Console.WriteLine($"Successfully created a model with Id: {newComponentModelId}, {sampleModelId}, status: {response.GetRawResponse().Status}"); + Console.WriteLine($"Created models with Ids {componentModelId} and {sampleModelId}. Response status: {response.GetRawResponse().Status}"); #endregion Snippet:DigitalTwinsSampleCreateModels } @@ -62,10 +62,10 @@ public async Task RunSamplesAsync(DigitalTwinsClient client) #region Snippet:DigitalTwinsSampleGetModel Response sampleModel = await client.GetModelAsync(sampleModelId); + Console.WriteLine($"Retrieved model with Id {sampleModelId}. Response status: {sampleModel.GetRawResponse().Status}"); #endregion Snippet:DigitalTwinsSampleGetModel - Console.WriteLine($"{sampleModel.Value.Id} has decommission status of {sampleModel.Value.Decommissioned}"); } catch (Exception ex) { @@ -78,12 +78,12 @@ public async Task RunSamplesAsync(DigitalTwinsClient client) try { - await client.DecommissionModelAsync(sampleModelId); - Console.WriteLine($"Successfully decommissioned model {sampleModelId}"); + Response decommissionModelResponse = await client.DecommissionModelAsync(sampleModelId); + Console.WriteLine($"Decommissioned model with Id {sampleModelId}. Response status: {decommissionModelResponse.Status}"); } catch (RequestFailedException ex) { - FatalError($"Failed to decommision model {sampleModelId} due to:\n{ex}"); + FatalError($"Failed to decommision model with Id {sampleModelId} due to:\n{ex}"); } #endregion Snippet:DigitalTwinsSampleDecommisionModel @@ -94,13 +94,12 @@ public async Task RunSamplesAsync(DigitalTwinsClient client) try { - await client.DeleteModelAsync(sampleModelId); - - Console.WriteLine($"Deleted model {sampleModelId}"); + Response deleteModelResponse = await client.DeleteModelAsync(sampleModelId); + Console.WriteLine($"Deleted model with Id {sampleModelId}. Response status: {deleteModelResponse.Status}"); } catch (Exception ex) { - FatalError($"Failed to delete model {sampleModelId} due to:\n{ex}"); + FatalError($"Failed to delete model with Id {sampleModelId} due to:\n{ex}"); } #endregion Snippet:DigitalTwinsSampleDeleteModel diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/Program.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/Program.cs index df0c8fabbe65..54512044943e 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/Program.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/Program.cs @@ -52,6 +52,9 @@ public static async Task Main(string[] args) var publishTelemetrySamples = new PublishTelemetrySamples(); await publishTelemetrySamples.RunSamplesAsync(dtClient); + + var relationshipSamples = new RelationshipSamples(); + await relationshipSamples.RunSamplesAsync(dtClient); } /// diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/PublishTelemetrySamples.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/PublishTelemetrySamples.cs index 07d52b3e3114..bbc137c788b1 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/PublishTelemetrySamples.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/PublishTelemetrySamples.cs @@ -56,7 +56,7 @@ await client // construct your json telemetry payload by hand. Response publishTelemetryResponse = await client.PublishTelemetryAsync(twinId, "{\"Telemetry1\": 5}"); - Console.WriteLine($"Successfully published telemetry message, status: {publishTelemetryResponse.Status}"); + Console.WriteLine($"Published telemetry message to twin with Id {twinId}. Response status: {publishTelemetryResponse.Status}"); #endregion Snippet:DigitalTwinsSamplePublishTelemetry @@ -71,7 +71,7 @@ await client twinId, "Component1", JsonSerializer.Serialize(telemetryPayload)); - Console.WriteLine($"Successfully published component telemetry message, status: {publishTelemetryToComponentResponse.Status}"); + Console.WriteLine($"Published component telemetry message to twin with Id {twinId}. Response status: {publishTelemetryToComponentResponse.Status}"); #endregion Snippet:DigitalTwinsSamplePublishComponentTelemetry } diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/RelationshipSamples.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/RelationshipSamples.cs new file mode 100644 index 000000000000..6a3a4b89cd66 --- /dev/null +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/RelationshipSamples.cs @@ -0,0 +1,226 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Text.Json; +using System.Threading.Tasks; +using Azure.DigitalTwins.Core.Models; +using Azure.DigitalTwins.Core.Serialization; +using static Azure.DigitalTwins.Core.Samples.SampleLogger; + +namespace Azure.DigitalTwins.Core.Samples +{ + internal class RelationshipSamples + { + /// + /// Creates two digital twins, and connect them with relationships. + /// + public async Task RunSamplesAsync(DigitalTwinsClient client) + { + // For the purpose of keeping code snippets readable to the user, hardcoded string literals are used in place of assigned variables, eg Ids. + // Despite not being a good code practice, this prevents code snippets from being out of context for the user when making API calls that accept Ids as parameters. + + PrintHeader("RELATIONSHIP SAMPLE"); + + // Create a building digital twin model. + string buildingModelPayload = SamplesConstants.TemporaryModelWithRelationshipPayload + .Replace(SamplesConstants.ModelId, "dtmi:samples:SampleBuilding;1") + .Replace(SamplesConstants.ModelDisplayName, "Building") + .Replace(SamplesConstants.RelationshipName, "contains") + .Replace(SamplesConstants.RelationshipTargetModelId, "dtmi:samples:SampleFloor;1"); + + Response> createBuildingModelResponse = await client.CreateModelsAsync( + new[] + { + buildingModelPayload + }); + Console.WriteLine($"Created model with Id dtmi:samples:SampleBuilding;1. Response status: {createBuildingModelResponse.GetRawResponse().Status}."); + + // Create a floor digital twin model. + string floorModelPayload = SamplesConstants.TemporaryModelWithRelationshipPayload + .Replace(SamplesConstants.ModelId, "dtmi:samples:SampleFloor;1") + .Replace(SamplesConstants.ModelDisplayName, "Floor") + .Replace(SamplesConstants.RelationshipName, "containedIn") + .Replace(SamplesConstants.RelationshipTargetModelId, "dtmi:samples:SampleBuilding;1"); + + Response> createFloorModelResponse = await client.CreateModelsAsync( + new[] + { + floorModelPayload + }); + Console.WriteLine($"Created model with Id dtmi:samples:SampleFloor;1. Response status: {createFloorModelResponse.GetRawResponse().Status}."); + + // Create a building digital twin. + var buildingDigitalTwin = new BasicDigitalTwin + { + Id = "buildingTwinId", + Metadata = { ModelId = "dtmi:samples:SampleBuilding;1" } + }; + + string buildingDigitalTwinPayload = JsonSerializer.Serialize(buildingDigitalTwin); + Response createBuildingDigitalTwinResponse = await client.CreateDigitalTwinAsync("buildingTwinId", buildingDigitalTwinPayload); + Console.WriteLine($"Created a digital twin with Id buildingTwinId. Response status: {createBuildingDigitalTwinResponse.GetRawResponse().Status}."); + + // Create a floor digital. + var floorDigitalTwin = new BasicDigitalTwin + { + Id = "floorTwinId", + Metadata = { ModelId = "dtmi:samples:SampleFloor;1" } + }; + + string floorDigitalTwinPayload = JsonSerializer.Serialize(floorDigitalTwin); + Response createFloorDigitalTwinResponse = await client.CreateDigitalTwinAsync("floorTwinId", floorDigitalTwinPayload); + Console.WriteLine($"Created a digital twin with Id floorTwinId. Response status: {createFloorDigitalTwinResponse.GetRawResponse().Status}."); + + // Create a relationship between building and floor using the BasicRelationship serialization helper. + #region Snippet:DigitalTwinsSampleCreateBasicRelationship + + var buildingFloorRelationshipPayload = new BasicRelationship + { + Id = "buildingFloorRelationshipId", + SourceId = "buildingTwinId", + TargetId = "floorTwinId", + Name = "contains", + CustomProperties = + { + { "Prop1", "Prop1 value" }, + { "Prop2", 6 } + } + }; + + string serializedRelationship = JsonSerializer.Serialize(buildingFloorRelationshipPayload); + Response createRelationshipResponse = await client.CreateRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId", serializedRelationship); + Console.WriteLine($"Created a digital twin relationship with Id buildingFloorRelationshipId from digital twin with Id buildingTwinId to digital twin with Id floorTwinId. " + + $"Response status: {createRelationshipResponse.GetRawResponse().Status}."); + + #endregion Snippet:DigitalTwinsSampleCreateBasicRelationship + + // You can get a relationship and deserialize it into a BasicRelationship. + #region Snippet:DigitalTwinsSampleGetBasicRelationship + + Response getBasicRelationshipResponse = await client.GetRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId"); + if (getBasicRelationshipResponse.GetRawResponse().Status == (int)HttpStatusCode.OK) + { + BasicRelationship basicRelationship = JsonSerializer.Deserialize(getBasicRelationshipResponse.Value); + Console.WriteLine($"Retrieved relationship with Id {basicRelationship.Id} from digital twin with Id {basicRelationship.SourceId}. " + + $"Response status: {getBasicRelationshipResponse.GetRawResponse().Status}.\n\t" + + $"Prop1: {basicRelationship.CustomProperties["Prop1"]}\n\t" + + $"Prop2: {basicRelationship.CustomProperties["Prop2"]}"); + } + + #endregion Snippet:DigitalTwinsSampleGetBasicRelationship + + // Alternatively, you can create your own custom data types to serialize and deserialize your relationships. + // This requires less code or knowledge of the type for interaction. + + // Create a relationship between floorTwinId and buildingTwinId using a custom data type. + #region Snippet:DigitalTwinsSampleCreateCustomRelationship + + var floorBuildingRelationshipPayload = new CustomRelationship + { + Id = "floorBuildingRelationshipId", + SourceId = "floorTwinId", + TargetId = "buildingTwinId", + Name = "containedIn", + Prop1 = "Prop1 val", + Prop2 = 4 + }; + string serializedCustomRelationship = JsonSerializer.Serialize(floorBuildingRelationshipPayload); + + Response createCustomRelationshipResponse = await client.CreateRelationshipAsync("floorTwinId", "floorBuildingRelationshipId", serializedCustomRelationship); + Console.WriteLine($"Created a digital twin relationship with Id floorBuildingRelationshipId from digital twin with Id floorTwinId to digital twin with Id buildingTwinId. " + + $"Response status: {createCustomRelationshipResponse.GetRawResponse().Status}."); + + #endregion Snippet:DigitalTwinsSampleCreateCustomRelationship + + // Getting and deserializing a relationship into a custom data type is extremely easy. + #region Snippet:DigitalTwinsSampleGetCustomRelationship + + Response getCustomRelationshipResponse = await client.GetRelationshipAsync("floorTwinId", "floorBuildingRelationshipId"); + if (getCustomRelationshipResponse.GetRawResponse().Status == (int)HttpStatusCode.OK) + { + CustomRelationship getCustomRelationship = JsonSerializer.Deserialize(getCustomRelationshipResponse.Value); + Console.WriteLine($"Retrieved and deserialized relationship with Id {getCustomRelationship.Id} from digital twin with Id {getCustomRelationship.SourceId}. " + + $"Response status: {getCustomRelationshipResponse.GetRawResponse().Status}.\n\t" + + $"Prop1: {getCustomRelationship.Prop1}\n\t" + + $"Prop2: {getCustomRelationship.Prop2}"); + } + + #endregion Snippet:DigitalTwinsSampleGetCustomRelationship + + // Get all relationships in the graph where buildingTwinId is the source of the relationship. + #region Snippet:DigitalTwinsSampleGetAllRelationships + + AsyncPageable relationships = client.GetRelationshipsAsync("buildingTwinId"); + + await foreach (var relationshipJson in relationships) + { + BasicRelationship relationship = JsonSerializer.Deserialize(relationshipJson); + Console.WriteLine($"Found relationship with Id {relationship.Id} with a digital twin source Id {relationship.SourceId} and " + + $"a digital twin target Id {relationship.TargetId}. \n\t " + + $"Prop1: {relationship.CustomProperties["Prop1"]}\n\t" + + $"Prop2: {relationship.CustomProperties["Prop2"]}"); + } + + #endregion Snippet:DigitalTwinsSampleGetAllRelationships + + // Get all incoming relationships in the graph where buildingTwinId is the target of the relationship. + #region Snippet:DigitalTwinsSampleGetIncomingRelationships + + AsyncPageable incomingRelationships = client.GetIncomingRelationshipsAsync("buildingTwinId"); + + await foreach (IncomingRelationship incomingRelationship in incomingRelationships) + { + Console.WriteLine($"Found an incoming relationship with Id {incomingRelationship.RelationshipId} coming from a digital twin with Id {incomingRelationship.SourceId}."); + } + + #endregion Snippet:DigitalTwinsSampleGetIncomingRelationships + + #region Snippet:DigitalTwinsSampleDeleteAllRelationships + + // Delete all relationships from building to floor. These relationships were created using the BasicRelationship type. + AsyncPageable buildingRelationshipsToDelete = client.GetRelationshipsAsync("buildingTwinId"); + await foreach (var relationshipToDelete in buildingRelationshipsToDelete) + { + BasicRelationship relationship = JsonSerializer.Deserialize(relationshipToDelete); + Response deleteRelationshipResponse = await client.DeleteRelationshipAsync(relationship.SourceId, relationship.Id); + Console.WriteLine($"Deleted relationship with Id {relationship.Id}. Status response: {deleteRelationshipResponse.Status}."); + } + + // Delete all relationships from floor to building. These relationships were created using the CustomRelationship type. + AsyncPageable floorRelationshipsToDelete = client.GetRelationshipsAsync("floorTwinId"); + await foreach (var relationshipToDelete in floorRelationshipsToDelete) + { + CustomRelationship relationship = JsonSerializer.Deserialize(relationshipToDelete); + Response deleteRelationshipResponse = await client.DeleteRelationshipAsync(relationship.SourceId, relationship.Id); + Console.WriteLine($"Deleted relationship with Id {relationship.Id}. Status response: {deleteRelationshipResponse.Status}."); + } + + #endregion Snippet:DigitalTwinsSampleDeleteAllRelationships + + // Clean up. + try + { + // Delete all twins + await client.DeleteDigitalTwinAsync("buildingTwinId"); + await client.DeleteDigitalTwinAsync("floorTwinId"); + } + catch (RequestFailedException ex) + { + Console.WriteLine($"Failed to delete digital twin due to {ex}."); + } + + try + { + await client.DeleteModelAsync("dtmi:samples:SampleBuilding;1"); + await client.DeleteModelAsync("dtmi:samples:SampleFloor;1"); + } + catch (RequestFailedException ex) + { + Console.WriteLine($"Failed to delete model due to {ex}."); + } + } + } +} diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/SamplesConstants.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/SamplesConstants.cs index 845918cf769d..5c32c1d3a79f 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/SamplesConstants.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/SamplesConstants.cs @@ -51,7 +51,17 @@ public static class SamplesConstants public const string TemporaryComponentModelPrefix = "dtmi:samples:ComponentModel;"; /// - /// The application/json description of a temporary model + /// Placeholder for model display name in the temporary payload. + /// + public const string ModelDisplayName = "MODEL_DISPLAY_NAME"; + + /// + /// Placeholder for model relationship name in the temporary payload. + /// + public const string RelationshipName = "RELATIONSHIP_NAME"; + + /// + /// The application/json description of a temporary model with a component. /// public const string TemporaryModelWithComponentPayload = @" { @@ -138,5 +148,40 @@ public static class SamplesConstants ""ComponentProp2"": 123 } }"; + + /// + /// Placeholder for a relationship target model Id in the temporary model with relationship payload. + /// + public const string RelationshipTargetModelId = "RELATIONSHIP_TARGET_MODEL_ID"; + + /// + /// The application/json description of a temporary model with a relationship + /// + public const string TemporaryModelWithRelationshipPayload = @" + { + ""@id"": ""MODEL_ID"", + ""@type"": ""Interface"", + ""@context"": ""dtmi:dtdl:context;2"", + ""displayName"": ""MODEL_DISPLAY_NAME"", + ""contents"": [ + { + ""@type"": ""Relationship"", + ""name"": ""RELATIONSHIP_NAME"", + ""target"": ""RELATIONSHIP_TARGET_MODEL_ID"", + ""properties"": [ + { + ""@type"": ""Property"", + ""name"": ""Prop1"", + ""schema"": ""string"" + }, + { + ""@type"": ""Property"", + ""name"": ""Prop2"", + ""schema"": ""integer"" + } + ] + } + ] + }"; } } diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/UniqueIdHelper.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/UniqueIdHelper.cs index 7a117ffb0a81..849e997c5b4e 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/UniqueIdHelper.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/UniqueIdHelper.cs @@ -3,7 +3,6 @@ using System; using System.Net; -using System.Runtime.CompilerServices; using System.Threading.Tasks; namespace Azure.DigitalTwins.Core.Samples @@ -12,12 +11,12 @@ internal static class UniqueIdHelper { private static readonly Random s_random = new Random(); - internal static async Task GetUniqueModelIdAsync(string baseName, DigitalTwinsClient client, [CallerMemberName] string caller = "") + internal static async Task GetUniqueModelIdAsync(string baseName, DigitalTwinsClient client) { return await GetUniqueIdAsync(baseName, (modelId) => client.GetModelAsync(modelId)); } - internal static async Task GetUniqueTwinIdAsync(string baseName, DigitalTwinsClient client, [CallerMemberName] string caller = "") + internal static async Task GetUniqueTwinIdAsync(string baseName, DigitalTwinsClient client) { return await GetUniqueIdAsync(baseName, (twinId) => client.GetDigitalTwinAsync(twinId)); } diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/Readme.md b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/Readme.md index 6405d1cca5d7..f776b08e7629 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/Readme.md +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/Readme.md @@ -57,7 +57,7 @@ Check out sample models [here](https://github.com/Azure/azure-sdk-for-net/blob/m ```C# Snippet:DigitalTwinsSampleCreateModels Response> response = await client.CreateModelsAsync(new[] { newComponentModelPayload, newModelPayload }); -Console.WriteLine($"Successfully created a model with Id: {newComponentModelId}, {sampleModelId}, status: {response.GetRawResponse().Status}"); +Console.WriteLine($"Created models with Ids {componentModelId} and {sampleModelId}. Response status: {response.GetRawResponse().Status}"); ``` ### List models @@ -68,7 +68,7 @@ Using `GetModelsAsync`, all created models are returned as `AsyncPageable allModels = client.GetModelsAsync(); await foreach (ModelData model in allModels) { - Console.WriteLine($"Model Id: {model.Id}, display name: {model.DisplayName["en"]}, upload time: {model.UploadTime}, is decommissioned: {model.Decommissioned}"); + Console.WriteLine($"Retrieved model with Id {model.Id}, display name {model.DisplayName["en"]}, upload time {model.UploadTime}, and decommissioned: {model.Decommissioned}"); } ``` @@ -76,6 +76,7 @@ Use `GetModelAsync` with model's unique identifier to get a specific model. ```C# Snippet:DigitalTwinsSampleGetModel Response sampleModel = await client.GetModelAsync(sampleModelId); +Console.WriteLine($"Retrieved model with Id {sampleModelId}. Response status: {sampleModel.GetRawResponse().Status}"); ``` ### Decommission models @@ -85,12 +86,12 @@ To decommision a model, pass in a model Id for the model you want to decommision ```C# Snippet:DigitalTwinsSampleDecommisionModel try { - await client.DecommissionModelAsync(sampleModelId); - Console.WriteLine($"Successfully decommissioned model {sampleModelId}"); + Response decommissionModelResponse = await client.DecommissionModelAsync(sampleModelId); + Console.WriteLine($"Decommissioned model with Id {sampleModelId}. Response status: {decommissionModelResponse.Status}"); } catch (RequestFailedException ex) { - FatalError($"Failed to decommision model {sampleModelId} due to:\n{ex}"); + FatalError($"Failed to decommision model with Id {sampleModelId} due to:\n{ex}"); } ``` @@ -101,13 +102,12 @@ To delete a model, pass in a model Id for the model you want to delete. ```C# Snippet:DigitalTwinsSampleDeleteModel try { - await client.DeleteModelAsync(sampleModelId); - - Console.WriteLine($"Deleted model {sampleModelId}"); + Response deleteModelResponse = await client.DeleteModelAsync(sampleModelId); + Console.WriteLine($"Deleted model with Id {sampleModelId}. Response status: {deleteModelResponse.Status}"); } catch (Exception ex) { - FatalError($"Failed to delete model {sampleModelId} due to:\n{ex}"); + FatalError($"Failed to delete model with Id {sampleModelId} due to:\n{ex}"); } ``` @@ -152,7 +152,7 @@ var basicTwin = new BasicDigitalTwin string basicDtPayload = JsonSerializer.Serialize(basicTwin); Response createBasicDtResponse = await client.CreateDigitalTwinAsync(basicDtId, basicDtPayload); -Console.WriteLine($"Created digital twin {basicDtId} with response {createBasicDtResponse.GetRawResponse().Status}."); +Console.WriteLine($"Created digital twin with Id {basicDtId}. Response status: {createBasicDtResponse.GetRawResponse().Status}."); ``` Alternatively, you can create your own custom data types to serialize and deserialize your digital twins. @@ -175,7 +175,7 @@ var customTwin = new CustomDigitalTwin string dt2Payload = JsonSerializer.Serialize(customTwin); Response createCustomDtResponse = await client.CreateDigitalTwinAsync(customDtId, dt2Payload); -Console.WriteLine($"Created digital twin {customDtId} with response {createCustomDtResponse.GetRawResponse().Status}."); +Console.WriteLine($"Created digital twin with Id {customDtId}. Response status: {createCustomDtResponse.GetRawResponse().Status}."); ``` ### Get and deserialize a digital twin @@ -206,23 +206,18 @@ if (getBasicDtResponse.GetRawResponse().Status == (int)HttpStatusCode.OK) Getting and deserializing a digital twin into a custom data type is extremely easy. Custom types provide the best possible experience. -```C# Snippet:DigitalTwinsSampleCreateCustomTwin -var customTwin = new CustomDigitalTwin +```C# Snippet:DigitalTwinsSampleGetCustomDigitalTwin +Response getCustomDtResponse = await client.GetDigitalTwinAsync(customDtId); +if (getCustomDtResponse.GetRawResponse().Status == (int)HttpStatusCode.OK) { - Id = customDtId, - Metadata = { ModelId = modelId }, - Prop1 = "Prop1 val", - Prop2 = 987, - Component1 = new Component1 - { - ComponentProp1 = "Component prop1 val", - ComponentProp2 = 123, - } -}; -string dt2Payload = JsonSerializer.Serialize(customTwin); - -Response createCustomDtResponse = await client.CreateDigitalTwinAsync(customDtId, dt2Payload); -Console.WriteLine($"Created digital twin {customDtId} with response {createCustomDtResponse.GetRawResponse().Status}."); + CustomDigitalTwin customDt = JsonSerializer.Deserialize(getCustomDtResponse.Value); + Console.WriteLine($"Retrieved and deserialized digital twin {customDt.Id}:\n\t" + + $"ETag: {customDt.ETag}\n\t" + + $"Prop1: {customDt.Prop1}\n\t" + + $"Prop2: {customDt.Prop2}\n\t" + + $"ComponentProp1: {customDt.Component1.ComponentProp1}\n\t" + + $"ComponentProp2: {customDt.Component1.ComponentProp2}"); +} ``` ### Query digital twins @@ -280,7 +275,8 @@ await foreach (Page page in asyncPageableResponseWithCharge.AsPages()) Delete a digital twin simply by providing Id of a digital twin as below. ```C# Snippet:DigitalTwinsSampleDeleteTwin -await client.DeleteDigitalTwinAsync(twin.Key); +Response deleteDigitalTwinResponse = await client.DeleteDigitalTwinAsync(digitalTwinId); +Console.WriteLine($"Deleted digital twin with Id {digitalTwinId}. Response Status: {deleteDigitalTwinResponse.Status}"); ``` ## Get and update digital twin components @@ -297,7 +293,7 @@ string updatePayload = componentUpdateUtility.Serialize(); Response response = await client.UpdateComponentAsync(basicDtId, "Component1", updatePayload); -Console.WriteLine($"Updated component for digital twin {basicDtId}. Update response status: {response.GetRawResponse().Status}"); +Console.WriteLine($"Updated component for digital twin with Id {basicDtId}. Response status: {response.GetRawResponse().Status}"); ``` ### Get digital twin components @@ -307,47 +303,137 @@ Get a component by providing name of a component and Id of digital twin to which ```C# Snippet:DigitalTwinsSampleGetComponent response = await client.GetComponentAsync(basicDtId, SamplesConstants.ComponentPath); -Console.WriteLine($"Get component for digital twin: \n{response.Value}. Get response status: {response.GetRawResponse().Status}"); +Console.WriteLine($"Retrieved component for digital twin with Id {basicDtId}. Response status: {response.GetRawResponse().Status}"); ``` -## Create and list digital twin relationships +## Create, get, list and delete digital twin relationships ### Create digital twin relationships `CreateRelationshipAsync` creates a relationship on a digital twin provided with Id of a digital twin, name of relationship such as "contains", Id of an relationship such as "FloorContainsRoom" and an application/json relationship to be created. Must contain property with key "$targetId" to specify the target of the relationship. Sample payloads for relationships can be found [here](https://github.com/Azure/azure-sdk-for-net-pr/blob/feature/IoT-ADT/sdk/iot/Azure.Iot.DigitalTwins/samples/DigitalTwinServiceClientSample/DTDL/Relationships/HospitalRelationships.json "RelationshipExamples"). -```C# Snippet:DigitalTwinsSampleCreateRelationship -// From loaded relationships, get the source Id and Id from each one, -// and create it with full relationship payload -foreach (BasicRelationship relationship in relationships) +One option is to use the provided class BasicRelationship for serialization and deserialization. +It uses functionality from the `System.Text.Json` library to maintain any unmapped json properties to a dictionary. + +```C# Snippet:DigitalTwinsSampleCreateBasicRelationship +var buildingFloorRelationshipPayload = new BasicRelationship { - try + Id = "buildingFloorRelationshipId", + SourceId = "buildingTwinId", + TargetId = "floorTwinId", + Name = "contains", + CustomProperties = { - string serializedRelationship = JsonSerializer.Serialize(relationship); + { "Prop1", "Prop1 value" }, + { "Prop2", 6 } + } +}; - await client.CreateRelationshipAsync( - relationship.SourceId, - relationship.Id, - serializedRelationship); +string serializedRelationship = JsonSerializer.Serialize(buildingFloorRelationshipPayload); +Response createRelationshipResponse = await client.CreateRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId", serializedRelationship); +Console.WriteLine($"Created a digital twin relationship with Id buildingFloorRelationshipId from digital twin with Id buildingTwinId to digital twin with Id floorTwinId. " + + $"Response status: {createRelationshipResponse.GetRawResponse().Status}."); +``` - Console.WriteLine($"Linked twin {relationship.SourceId} to twin {relationship.TargetId} as '{relationship.Name}'"); - } - catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.Conflict) - { - Console.WriteLine($"Relationship {relationship.Id} already exists: {ex.Message}"); - } +Alternatively, you can create your own custom data types to serialize and deserialize your relationships. +By specifying your properties and types directly, it requires less code or knowledge of the type for interaction. +You can review the [CustomRelationship definition](https://github.com/Azure/azure-sdk-for-net/blob/master/sdk/digitaltwins/Azure.DigitalTwins.Core/samples/DigitalTwinsClientSample/CustomRelationship.cs). + +```C# Snippet:DigitalTwinsSampleCreateCustomRelationship +var floorBuildingRelationshipPayload = new CustomRelationship +{ + Id = "floorBuildingRelationshipId", + SourceId = "floorTwinId", + TargetId = "buildingTwinId", + Name = "containedIn", + Prop1 = "Prop1 val", + Prop2 = 4 +}; +string serializedCustomRelationship = JsonSerializer.Serialize(floorBuildingRelationshipPayload); + +Response createCustomRelationshipResponse = await client.CreateRelationshipAsync("floorTwinId", "floorBuildingRelationshipId", serializedCustomRelationship); +Console.WriteLine($"Created a digital twin relationship with Id floorBuildingRelationshipId from digital twin with Id floorTwinId to digital twin with Id buildingTwinId. " + + $"Response status: {createCustomRelationshipResponse.GetRawResponse().Status}."); +``` + +### Get and deserialize a digital twin relationship +You can get a digital twin relationship and deserialize it into a BasicRelationship. + +```C# Snippet:DigitalTwinsSampleGetBasicRelationship +Response getBasicRelationshipResponse = await client.GetRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId"); +if (getBasicRelationshipResponse.GetRawResponse().Status == (int)HttpStatusCode.OK) +{ + BasicRelationship basicRelationship = JsonSerializer.Deserialize(getBasicRelationshipResponse.Value); + Console.WriteLine($"Retrieved relationship with Id {basicRelationship.Id} from digital twin with Id {basicRelationship.SourceId}. " + + $"Response status: {getBasicRelationshipResponse.GetRawResponse().Status}.\n\t" + + $"Prop1: {basicRelationship.CustomProperties["Prop1"]}\n\t" + + $"Prop2: {basicRelationship.CustomProperties["Prop2"]}"); } ``` + +Getting and deserializing a digital twin relationship into a custom data type is as easy. +```C# Snippet:DigitalTwinsSampleGetCustomRelationship +Response getCustomRelationshipResponse = await client.GetRelationshipAsync("floorTwinId", "floorBuildingRelationshipId"); +if (getCustomRelationshipResponse.GetRawResponse().Status == (int)HttpStatusCode.OK) +{ + CustomRelationship getCustomRelationship = JsonSerializer.Deserialize(getCustomRelationshipResponse.Value); + Console.WriteLine($"Retrieved and deserialized relationship with Id {getCustomRelationship.Id} from digital twin with Id {getCustomRelationship.SourceId}. " + + $"Response status: {getCustomRelationshipResponse.GetRawResponse().Status}.\n\t" + + $"Prop1: {getCustomRelationship.Prop1}\n\t" + + $"Prop2: {getCustomRelationship.Prop2}"); +} +``` + ### List digital twin relationships -`GetrelationshipsAsync` and `GetIncomingRelationshipsAsync` lists all the relationships and all incoming relationships respectively of a digital twin. +`GetRelationshipsAsync` lists all the relationships of a digital twin. You can get digital twin relationships and deserialize them into `BasicRelationship`. + +```C# Snippet:DigitalTwinsSampleGetAllRelationships +AsyncPageable relationships = client.GetRelationshipsAsync("buildingTwinId"); -```C# Snippet:DigitalTwinsSampleGetRelationships -AsyncPageable relationships = client.GetRelationshipsAsync(twin.Key); +await foreach (var relationshipJson in relationships) +{ + BasicRelationship relationship = JsonSerializer.Deserialize(relationshipJson); + Console.WriteLine($"Found relationship with Id {relationship.Id} with a digital twin source Id {relationship.SourceId} and " + + $"a digital twin target Id {relationship.TargetId}. \n\t " + + $"Prop1: {relationship.CustomProperties["Prop1"]}\n\t" + + $"Prop2: {relationship.CustomProperties["Prop2"]}"); +} ``` +`GetIncomingRelationshipsAsync` lists all incoming relationships of digital twin. + ```C# Snippet:DigitalTwinsSampleGetIncomingRelationships -AsyncPageable incomingRelationships = client.GetIncomingRelationshipsAsync(twin.Key); +AsyncPageable incomingRelationships = client.GetIncomingRelationshipsAsync("buildingTwinId"); + +await foreach (IncomingRelationship incomingRelationship in incomingRelationships) +{ + Console.WriteLine($"Found an incoming relationship with Id {incomingRelationship.RelationshipId} coming from a digital twin with Id {incomingRelationship.SourceId}."); +} +``` + +### Delete a digital twin relationship + +To delete all outgoing relationships for a digital twin, simply iterate over the relationships and delete them iteratively. + +```C# Snippet:DigitalTwinsSampleDeleteAllRelationships +// Delete all relationships from building to floor. These relationships were created using the BasicRelationship type. +AsyncPageable buildingRelationshipsToDelete = client.GetRelationshipsAsync("buildingTwinId"); +await foreach (var relationshipToDelete in buildingRelationshipsToDelete) +{ + BasicRelationship relationship = JsonSerializer.Deserialize(relationshipToDelete); + Response deleteRelationshipResponse = await client.DeleteRelationshipAsync(relationship.SourceId, relationship.Id); + Console.WriteLine($"Deleted relationship with Id {relationship.Id}. Status response: {deleteRelationshipResponse.Status}."); +} + +// Delete all relationships from floor to building. These relationships were created using the CustomRelationship type. +AsyncPageable floorRelationshipsToDelete = client.GetRelationshipsAsync("floorTwinId"); +await foreach (var relationshipToDelete in floorRelationshipsToDelete) +{ + CustomRelationship relationship = JsonSerializer.Deserialize(relationshipToDelete); + Response deleteRelationshipResponse = await client.DeleteRelationshipAsync(relationship.SourceId, relationship.Id); + Console.WriteLine($"Deleted relationship with Id {relationship.Id}. Status response: {deleteRelationshipResponse.Status}."); +} ``` ## Create, list, and delete event routes of digital twins @@ -364,6 +450,7 @@ var eventRoute = new EventRoute(eventhubEndpointName) }; Response createEventRouteResponse = await client.CreateEventRouteAsync(_eventRouteId, eventRoute); +Console.WriteLine($"Created event route with Id {_eventRouteId}. Response status: {createEventRouteResponse.Status}"); ``` For more information on the event route filter language, see the "how to manage routes" [filter events documentation](https://github.com/Azure/azure-digital-twins/blob/private-preview/Documentation/how-to-manage-routes.md#filter-events). @@ -386,6 +473,7 @@ Delete an event route given event route Id. ```C# Snippet:DigitalTwinsSampleDeleteEventRoute Response response = await client.DeleteEventRouteAsync(_eventRouteId); +Console.WriteLine($"Deleted event route with Id {_eventRouteId}. Response status: {response.Status}"); ``` ### Publish telemetry messages for a digital twin @@ -395,7 +483,7 @@ To publish a telemetry message for a digital twin, you need to provide the digit ```C# Snippet:DigitalTwinsSamplePublishTelemetry // construct your json telemetry payload by hand. Response publishTelemetryResponse = await client.PublishTelemetryAsync(twinId, "{\"Telemetry1\": 5}"); -Console.WriteLine($"Successfully published telemetry message, status: {publishTelemetryResponse.Status}"); +Console.WriteLine($"Published telemetry message to twin with Id {twinId}. Response status: {publishTelemetryResponse.Status}"); ``` You can also publish a telemetry message for a specific component in a digital twin. In addition to the digital twin Id and payload, you need to specify the target component Id. @@ -410,5 +498,5 @@ Response publishTelemetryToComponentResponse = await client.PublishComponentTele twinId, "Component1", JsonSerializer.Serialize(telemetryPayload)); -Console.WriteLine($"Successfully published component telemetry message, status: {publishTelemetryToComponentResponse.Status}"); +Console.WriteLine($"Published component telemetry message to twin with Id {twinId}. Response status: {publishTelemetryToComponentResponse.Status}"); ``` diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/DigitalTwinsClient.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/DigitalTwinsClient.cs index c9f31ba076d9..912e0bed274c 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/DigitalTwinsClient.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/DigitalTwinsClient.cs @@ -201,7 +201,7 @@ public virtual Response GetDigitalTwin(string digitalTwinId, Cancellatio /// string dt2Payload = JsonSerializer.Serialize(customTwin); /// /// Response<string> createCustomDtResponse = await client.CreateDigitalTwinAsync(customDtId, dt2Payload); - /// Console.WriteLine($"Created digital twin {customDtId} with response {createCustomDtResponse.GetRawResponse().Status}."); + /// Console.WriteLine($"Created digital twin with Id {customDtId}. Response status: {createCustomDtResponse.GetRawResponse().Status}."); /// /// public virtual Task> CreateDigitalTwinAsync(string digitalTwinId, string digitalTwin, CancellationToken cancellationToken = default) @@ -251,7 +251,8 @@ public virtual Response CreateDigitalTwin(string digitalTwinId, string d /// /// /// - /// await client.DeleteDigitalTwinAsync(twin.Key); + /// Response deleteDigitalTwinResponse = await client.DeleteDigitalTwinAsync(digitalTwinId); + /// Console.WriteLine($"Deleted digital twin with Id {digitalTwinId}. Response Status: {deleteDigitalTwinResponse.Status}"); /// /// public virtual Task DeleteDigitalTwinAsync(string digitalTwinId, RequestOptions requestOptions = default, CancellationToken cancellationToken = default) @@ -352,7 +353,7 @@ public virtual Response UpdateDigitalTwin(string digitalTwinId, string d /// /// response = await client.GetComponentAsync(basicDtId, SamplesConstants.ComponentPath); /// - /// Console.WriteLine($"Get component for digital twin: \n{response.Value}. Get response status: {response.GetRawResponse().Status}"); + /// Console.WriteLine($"Retrieved component for digital twin with Id {basicDtId}. Response status: {response.GetRawResponse().Status}"); /// /// public virtual Task> GetComponentAsync(string digitalTwinId, string componentPath, CancellationToken cancellationToken = default) @@ -411,7 +412,7 @@ public virtual Response GetComponent(string digitalTwinId, string compon /// /// Response<string> response = await client.UpdateComponentAsync(basicDtId, "Component1", updatePayload); /// - /// Console.WriteLine($"Updated component for digital twin {basicDtId}. Update response status: {response.GetRawResponse().Status}"); + /// Console.WriteLine($"Updated component for digital twin with Id {basicDtId}. Response status: {response.GetRawResponse().Status}"); /// /// public virtual Task> UpdateComponentAsync(string digitalTwinId, string componentPath, string componentUpdateOperations, RequestOptions requestOptions = default, CancellationToken cancellationToken = default) @@ -454,7 +455,13 @@ public virtual Response UpdateComponent(string digitalTwinId, string com /// The cancellation token. /// The pageable list of application/json relationships belonging to the specified digital twin and the http response. /// + /// + /// String relationships that are returned as part of the pageable list can always be deserialized into an instnace of . + /// You may also deserialize the relationship into custom type that extend the . + /// + /// /// For more samples, see our repo samples. + /// /// /// /// The exception that captures the errors from the service. Check the and properties for more details. @@ -463,8 +470,18 @@ public virtual Response UpdateComponent(string digitalTwinId, string com /// The exception is thrown when is null. /// /// - /// - /// AsyncPageable<string> relationships = client.GetRelationshipsAsync(twin.Key); + /// This sample demonstrates iterating over outgoing relationships and deserializing relationship strings into BasicRelationship objects. + /// + /// AsyncPageable<string> relationships = client.GetRelationshipsAsync("buildingTwinId"); + /// + /// await foreach (var relationshipJson in relationships) + /// { + /// BasicRelationship relationship = JsonSerializer.Deserialize<BasicRelationship>(relationshipJson); + /// Console.WriteLine($"Found relationship with Id {relationship.Id} with a digital twin source Id {relationship.SourceId} and " + + /// $"a digital twin target Id {relationship.TargetId}. \n\t " + + /// $"Prop1: {relationship.CustomProperties["Prop1"]}\n\t" + + /// $"Prop2: {relationship.CustomProperties["Prop2"]}"); + /// } /// /// public virtual AsyncPageable GetRelationshipsAsync(string digitalTwinId, string relationshipName = null, CancellationToken cancellationToken = default) @@ -587,7 +604,12 @@ Page NextPageFunc(string nextLink, int? pageSizeHint) /// /// /// - /// AsyncPageable<IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync(twin.Key); + /// AsyncPageable<IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync("buildingTwinId"); + /// + /// await foreach (IncomingRelationship incomingRelationship in incomingRelationships) + /// { + /// Console.WriteLine($"Found an incoming relationship with Id {incomingRelationship.RelationshipId} coming from a digital twin with Id {incomingRelationship.SourceId}."); + /// } /// /// public virtual AsyncPageable GetIncomingRelationshipsAsync(string digitalTwinId, CancellationToken cancellationToken = default) @@ -690,7 +712,9 @@ Page NextPageFunc(string nextLink, int? pageSizeHint) /// The cancellation token. /// The application/json relationship corresponding to the provided relationshipId and the http response . /// + /// /// For more samples, see our repo samples. + /// /// /// /// The exception that captures the errors from the service. Check the and properties for more details. @@ -698,6 +722,20 @@ Page NextPageFunc(string nextLink, int? pageSizeHint) /// /// The exception is thrown when or is null. /// + /// + /// This sample demonstrates getting and deserializing a digital twin relationship into a custom data type. + /// + /// Response<string> getCustomRelationshipResponse = await client.GetRelationshipAsync("floorTwinId", "floorBuildingRelationshipId"); + /// if (getCustomRelationshipResponse.GetRawResponse().Status == (int)HttpStatusCode.OK) + /// { + /// CustomRelationship getCustomRelationship = JsonSerializer.Deserialize<CustomRelationship>(getCustomRelationshipResponse.Value); + /// Console.WriteLine($"Retrieved and deserialized relationship with Id {getCustomRelationship.Id} from digital twin with Id {getCustomRelationship.SourceId}. " + + /// $"Response status: {getCustomRelationshipResponse.GetRawResponse().Status}.\n\t" + + /// $"Prop1: {getCustomRelationship.Prop1}\n\t" + + /// $"Prop2: {getCustomRelationship.Prop2}"); + /// } + /// + /// public virtual Task> GetRelationshipAsync(string digitalTwinId, string relationshipId, CancellationToken cancellationToken = default) { return _dtRestClient.GetRelationshipByIdAsync(digitalTwinId, relationshipId, cancellationToken); @@ -797,27 +835,21 @@ public virtual Response DeleteRelationship(string digitalTwinId, string relation /// The exception is thrown when or is null. /// /// - /// - /// // From loaded relationships, get the source Id and Id from each one, - /// // and create it with full relationship payload - /// foreach (BasicRelationship relationship in relationships) + /// + /// var floorBuildingRelationshipPayload = new CustomRelationship /// { - /// try - /// { - /// string serializedRelationship = JsonSerializer.Serialize(relationship); - /// - /// await client.CreateRelationshipAsync( - /// relationship.SourceId, - /// relationship.Id, - /// serializedRelationship); + /// Id = "floorBuildingRelationshipId", + /// SourceId = "floorTwinId", + /// TargetId = "buildingTwinId", + /// Name = "containedIn", + /// Prop1 = "Prop1 val", + /// Prop2 = 4 + /// }; + /// string serializedCustomRelationship = JsonSerializer.Serialize(floorBuildingRelationshipPayload); /// - /// Console.WriteLine($"Linked twin {relationship.SourceId} to twin {relationship.TargetId} as '{relationship.Name}'"); - /// } - /// catch (RequestFailedException ex) when (ex.Status == (int)HttpStatusCode.Conflict) - /// { - /// Console.WriteLine($"Relationship {relationship.Id} already exists: {ex.Message}"); - /// } - /// } + /// Response<string> createCustomRelationshipResponse = await client.CreateRelationshipAsync("floorTwinId", "floorBuildingRelationshipId", serializedCustomRelationship); + /// Console.WriteLine($"Created a digital twin relationship with Id floorBuildingRelationshipId from digital twin with Id floorTwinId to digital twin with Id buildingTwinId. " + + /// $"Response status: {createCustomRelationshipResponse.GetRawResponse().Status}."); /// /// public virtual Task> CreateRelationshipAsync(string digitalTwinId, string relationshipId, string relationship, CancellationToken cancellationToken = default) @@ -924,7 +956,7 @@ public virtual Response UpdateRelationship(string digitalTwinId, string relation /// AsyncPageable<ModelData> allModels = client.GetModelsAsync(); /// await foreach (ModelData model in allModels) /// { - /// Console.WriteLine($"Model Id: {model.Id}, display name: {model.DisplayName["en"]}, upload time: {model.UploadTime}, is decommissioned: {model.Decommissioned}"); + /// Console.WriteLine($"Retrieved model with Id {model.Id}, display name {model.DisplayName["en"]}, upload time {model.UploadTime}, and decommissioned: {model.Decommissioned}"); /// } /// /// @@ -1037,6 +1069,7 @@ Page NextPageFunc(string nextLink, int? pageSizeHint) /// /// /// Response<ModelData> sampleModel = await client.GetModelAsync(sampleModelId); + /// Console.WriteLine($"Retrieved model with Id {sampleModelId}. Response status: {sampleModel.GetRawResponse().Status}"); /// /// public virtual Task> GetModelAsync(string modelId, CancellationToken cancellationToken = default) @@ -1095,12 +1128,12 @@ public virtual Response GetModel(string modelId, CancellationToken ca /// /// try /// { - /// await client.DecommissionModelAsync(sampleModelId); - /// Console.WriteLine($"Successfully decommissioned model {sampleModelId}"); + /// Response decommissionModelResponse = await client.DecommissionModelAsync(sampleModelId); + /// Console.WriteLine($"Decommissioned model with Id {sampleModelId}. Response status: {decommissionModelResponse.Status}"); /// } /// catch (RequestFailedException ex) /// { - /// FatalError($"Failed to decommision model {sampleModelId} due to:\n{ex}"); + /// FatalError($"Failed to decommision model with Id {sampleModelId} due to:\n{ex}"); /// } /// /// @@ -1162,7 +1195,7 @@ public virtual Response DecommissionModel(string modelId, CancellationToken canc /// /// /// Response<IReadOnlyList<ModelData>> response = await client.CreateModelsAsync(new[] { newComponentModelPayload, newModelPayload }); - /// Console.WriteLine($"Successfully created a model with Id: {newComponentModelId}, {sampleModelId}, status: {response.GetRawResponse().Status}"); + /// Console.WriteLine($"Created models with Ids {componentModelId} and {sampleModelId}. Response status: {response.GetRawResponse().Status}"); /// /// public virtual Task>> CreateModelsAsync(IEnumerable models, CancellationToken cancellationToken = default) @@ -1227,13 +1260,12 @@ public virtual Response> CreateModels(IEnumerable /// try /// { - /// await client.DeleteModelAsync(sampleModelId); - /// - /// Console.WriteLine($"Deleted model {sampleModelId}"); + /// Response deleteModelResponse = await client.DeleteModelAsync(sampleModelId); + /// Console.WriteLine($"Deleted model with Id {sampleModelId}. Response status: {deleteModelResponse.Status}"); /// } /// catch (Exception ex) /// { - /// FatalError($"Failed to delete model {sampleModelId} due to:\n{ex}"); + /// FatalError($"Failed to delete model with Id {sampleModelId} due to:\n{ex}"); /// } /// /// @@ -1593,6 +1625,7 @@ public virtual Response GetEventRoute(string eventRouteId, Cancellat /// }; /// /// Response createEventRouteResponse = await client.CreateEventRouteAsync(_eventRouteId, eventRoute); + /// Console.WriteLine($"Created event route with Id {_eventRouteId}. Response status: {createEventRouteResponse.Status}"); /// /// public virtual Task CreateEventRouteAsync(string eventRouteId, EventRoute eventRoute, CancellationToken cancellationToken = default) @@ -1642,6 +1675,7 @@ public virtual Response CreateEventRoute(string eventRouteId, EventRoute eventRo /// /// /// Response response = await client.DeleteEventRouteAsync(_eventRouteId); + /// Console.WriteLine($"Deleted event route with Id {_eventRouteId}. Response status: {response.Status}"); /// /// public virtual Task DeleteEventRouteAsync(string eventRouteId, CancellationToken cancellationToken = default) @@ -1695,7 +1729,7 @@ public virtual Response DeleteEventRoute(string eventRouteId, CancellationToken /// /// // construct your json telemetry payload by hand. /// Response publishTelemetryResponse = await client.PublishTelemetryAsync(twinId, "{\"Telemetry1\": 5}"); - /// Console.WriteLine($"Successfully published telemetry message, status: {publishTelemetryResponse.Status}"); + /// Console.WriteLine($"Published telemetry message to twin with Id {twinId}. Response status: {publishTelemetryResponse.Status}"); /// /// public virtual Task PublishTelemetryAsync(string digitalTwinId, string payload, TelemetryOptions options = default, CancellationToken cancellationToken = default) @@ -1768,7 +1802,7 @@ public virtual Response PublishTelemetry(string digitalTwinId, string payload, T /// twinId, /// "Component1", /// JsonSerializer.Serialize(telemetryPayload)); - /// Console.WriteLine($"Successfully published component telemetry message, status: {publishTelemetryToComponentResponse.Status}"); + /// Console.WriteLine($"Published component telemetry message to twin with Id {twinId}. Response status: {publishTelemetryToComponentResponse.Status}"); /// /// public virtual Task PublishComponentTelemetryAsync(string digitalTwinId, string componentName, string payload, TelemetryOptions options = default, CancellationToken cancellationToken = default) diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/BasicDigitalTwin.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/BasicDigitalTwin.cs index 2cbc6e95135c..5c7167581fd4 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/BasicDigitalTwin.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/BasicDigitalTwin.cs @@ -47,7 +47,7 @@ namespace Azure.DigitalTwins.Core.Serialization /// string basicDtPayload = JsonSerializer.Serialize(basicTwin); /// /// Response<string> createBasicDtResponse = await client.CreateDigitalTwinAsync(basicDtId, basicDtPayload); - /// Console.WriteLine($"Created digital twin {basicDtId} with response {createBasicDtResponse.GetRawResponse().Status}."); + /// Console.WriteLine($"Created digital twin with Id {basicDtId}. Response status: {createBasicDtResponse.GetRawResponse().Status}."); /// /// /// Here's an example of how to use the BasicDigitalTwin helper class to get and deserialize a digital twin. diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/BasicRelationship.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/BasicRelationship.cs index bd30907351d6..c37265f54e70 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/BasicRelationship.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/BasicRelationship.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using System.Collections.Generic; @@ -13,6 +13,43 @@ namespace Azure.DigitalTwins.Core.Serialization /// /// For more samples, see our repo samples. /// + /// + /// Here's an example of how to use the BasicRelationship helper class to serialize and create a relationship from a building digital twin to a floor digital twin. + /// + /// + /// var buildingFloorRelationshipPayload = new BasicRelationship + /// { + /// Id = "buildingFloorRelationshipId", + /// SourceId = "buildingTwinId", + /// TargetId = "floorTwinId", + /// Name = "contains", + /// CustomProperties = + /// { + /// { "Prop1", "Prop1 value" }, + /// { "Prop2", 6 } + /// } + /// }; + /// + /// string serializedRelationship = JsonSerializer.Serialize(buildingFloorRelationshipPayload); + /// Response<string> createRelationshipResponse = await client.CreateRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId", serializedRelationship); + /// Console.WriteLine($"Created a digital twin relationship with Id buildingFloorRelationshipId from digital twin with Id buildingTwinId to digital twin with Id floorTwinId. " + + /// $"Response status: {createRelationshipResponse.GetRawResponse().Status}."); + /// + /// + /// Here's an example of how to use the BasicRelationship helper class to get and deserialize a relationship. + /// + /// + /// Response<string> getBasicRelationshipResponse = await client.GetRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId"); + /// if (getBasicRelationshipResponse.GetRawResponse().Status == (int)HttpStatusCode.OK) + /// { + /// BasicRelationship basicRelationship = JsonSerializer.Deserialize<BasicRelationship>(getBasicRelationshipResponse.Value); + /// Console.WriteLine($"Retrieved relationship with Id {basicRelationship.Id} from digital twin with Id {basicRelationship.SourceId}. " + + /// $"Response status: {getBasicRelationshipResponse.GetRawResponse().Status}.\n\t" + + /// $"Prop1: {basicRelationship.CustomProperties["Prop1"]}\n\t" + + /// $"Prop2: {basicRelationship.CustomProperties["Prop2"]}"); + /// } + /// + /// public class BasicRelationship { /// diff --git a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/UpdateOperationsUtility.cs b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/UpdateOperationsUtility.cs index 7a268d75862e..e388e3ec425e 100644 --- a/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/UpdateOperationsUtility.cs +++ b/sdk/digitaltwins/Azure.DigitalTwins.Core/src/Serialization/UpdateOperationsUtility.cs @@ -21,7 +21,7 @@ namespace Azure.DigitalTwins.Core.Serialization /// /// Response<string> response = await client.UpdateComponentAsync(basicDtId, "Component1", updatePayload); /// - /// Console.WriteLine($"Updated component for digital twin {basicDtId}. Update response status: {response.GetRawResponse().Status}"); + /// Console.WriteLine($"Updated component for digital twin with Id {basicDtId}. Response status: {response.GetRawResponse().Status}"); /// /// public class UpdateOperationsUtility