public override async Task CleanupAsync() { // Individual test-level cleanup code that runs for each instance of the test. await base.CleanupAsync(); // We will delete all twins created by this test instance. foreach (BasicDigitalTwin twin in _createdTwins) { await _digitalTwinsClient.DeleteDigitalTwinAsync(twin.Id).ConfigureAwait(false); } }
public static async Task Run( [EventHubTrigger("lifecycleevents", Connection = "EVENTHUB_CONNECTIONSTRING")] EventData[] events, ILogger log) { var exceptions = new List <Exception>(); foreach (EventData eventData in events) { try { //log.LogDebug($"EventData: {System.Text.Json.JsonSerializer.Serialize(eventData)}"); string opType = eventData.Properties["opType"] as string; if (opType == "deleteDeviceIdentity") { string deviceId = eventData.Properties["deviceId"] as string; // Create Digital Twin client var cred = new ManagedIdentityCredential(adtAppId); var client = new DigitalTwinsClient(new Uri(adtInstanceUrl), cred, new DigitalTwinsClientOptions { Transport = new HttpClientTransport(httpClient) }); // Find twin based on the original Registration ID string regID = deviceId; // simple mapping string dtId = await GetTwinId(client, regID, log); if (dtId != null) { await DeleteRelationships(client, dtId, log); // Delete twin await client.DeleteDigitalTwinAsync(dtId); log.LogInformation($"Twin '{dtId}' deleted in DT"); } } } catch (Exception e) { // We need to keep processing the rest of the batch - capture this exception and continue. exceptions.Add(e); } } if (exceptions.Count > 1) { throw new AggregateException(exceptions); } if (exceptions.Count == 1) { throw exceptions.Single(); } }
/// <summary> /// Delete a twin, and any relationships it might have /// </summary> public async Task DeleteTwinsAsync() { PrintHeader("DELETE DIGITAL TWINS"); Dictionary <string, string> twins = FileHelper.LoadAllFilesInPath(s_twinsPath); foreach (KeyValuePair <string, string> twin in twins) { var digitalTwinId = twin.Key; try { // Delete all relationships AsyncPageable <string> relationships = client.GetRelationshipsAsync(digitalTwinId); await foreach (var relationshipJson in relationships) { BasicRelationship relationship = JsonSerializer.Deserialize <BasicRelationship>(relationshipJson); await client.DeleteRelationshipAsync(digitalTwinId, relationship.Id); Console.WriteLine($"Found and deleted relationship with Id {relationship.Id}."); } // Delete any incoming relationships AsyncPageable <IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync(digitalTwinId); await foreach (IncomingRelationship incomingRelationship in incomingRelationships) { await client.DeleteRelationshipAsync(incomingRelationship.SourceId, incomingRelationship.RelationshipId); Console.WriteLine($"Found and deleted incoming relationship with Id {incomingRelationship.RelationshipId}."); } // Now the digital twin should be safe to delete #region Snippet:DigitalTwinsSampleDeleteTwin Response deleteDigitalTwinResponse = await client.DeleteDigitalTwinAsync(digitalTwinId); Console.WriteLine($"Deleted digital twin with Id {digitalTwinId}. Response Status: {deleteDigitalTwinResponse.Status}"); #endregion Snippet:DigitalTwinsSampleDeleteTwin } catch (RequestFailedException ex) when(ex.Status == (int)HttpStatusCode.NotFound) { // Digital twin or relationship does not exist } catch (RequestFailedException ex) { FatalError($"Failed to delete {digitalTwinId} due to {ex.Message}"); } } }
// <DeleteTwin> private static async Task DeleteTwinAsync(DigitalTwinsClient client, string twinId) { await FindAndDeleteOutgoingRelationshipsAsync(client, twinId); await FindAndDeleteIncomingRelationshipsAsync(client, twinId); try { await client.DeleteDigitalTwinAsync(twinId); Console.WriteLine("Twin deleted successfully"); } catch (RequestFailedException ex) { Console.WriteLine($"*** Error:{ex.Message}"); } }
public async Task PublishTelemetry_Lifecycle() { // Setup // Create a DigitalTwinsClient instance. DigitalTwinsClient client = GetClient(); string wifiComponentName = "wifiAccessPoint"; string wifiModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.WifiModelIdPrefix).ConfigureAwait(false); string roomWithWifiModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.RoomWithWifiModelIdPrefix).ConfigureAwait(false); string roomWithWifiTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.RoomWithWifiTwinIdPrefix).ConfigureAwait(false); string eventRouteId = $"someEventRouteId-{GetRandom()}"; try { // Create an event route for the digital twins client. DigitalTwinsEventRoute eventRoute = await CreateEventRoute(client, eventRouteId).ConfigureAwait(false); // Create the models needed for the digital twin. await CreateModelsAndTwins(client, wifiModelId, roomWithWifiModelId, wifiComponentName, roomWithWifiTwinId).ConfigureAwait(false); // Act - Test publishing telemetry to a digital twin. var telemetryOptions = new PublishTelemetryOptions() { TimeStamp = default }; Response publishTelemetryResponse = await client.PublishTelemetryAsync(roomWithWifiTwinId, Recording.Random.NewGuid().ToString(), "{\"Telemetry1\": 5}", telemetryOptions).ConfigureAwait(false); // Assert publishTelemetryResponse.Status.Should().Be((int)HttpStatusCode.NoContent); // Act - Test publishing telemetry to a component in a digital twin. var componentTelemetryOptions = new PublishComponentTelemetryOptions() { TimeStamp = default }; var telemetryPayload = new Dictionary <string, int> { { "ComponentTelemetry1", 9 } }; Response publishComponentTelemetryResponse = await client .PublishComponentTelemetryAsync(roomWithWifiTwinId, wifiComponentName, Recording.Random.NewGuid().ToString(), JsonSerializer.Serialize(telemetryPayload), componentTelemetryOptions) .ConfigureAwait(false); // Assert publishComponentTelemetryResponse.Status.Should().Be((int)HttpStatusCode.NoContent); } catch (Exception ex) { Assert.Fail($"Failure in executing a step in the test case: {ex.Message}."); } finally { // clean up try { if (!string.IsNullOrWhiteSpace(eventRouteId)) { await client.DeleteEventRouteAsync(eventRouteId).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(roomWithWifiTwinId)) { await client.DeleteDigitalTwinAsync(roomWithWifiTwinId).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(roomWithWifiModelId)) { await client.DeleteModelAsync(roomWithWifiModelId).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(wifiModelId)) { await client.DeleteModelAsync(wifiModelId).ConfigureAwait(false); } } catch (Exception ex) { Assert.Fail($"Test clean up failed: {ex.Message}"); } } }
/// <summary> /// Creates two digital twins, and connect them with relationships. /// </summary> 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 <IReadOnlyList <Models.ModelData> > 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 <IReadOnlyList <Models.ModelData> > 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 <string> 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 <string> 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 <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}."); #endregion Snippet:DigitalTwinsSampleCreateBasicRelationship // You can get a relationship and deserialize it into a BasicRelationship. #region Snippet:DigitalTwinsSampleGetBasicRelationship 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"]}"); } #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 <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}."); #endregion Snippet:DigitalTwinsSampleCreateCustomRelationship // Getting and deserializing a relationship into a custom data type is extremely easy. #region Snippet:DigitalTwinsSampleGetCustomRelationship 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}"); } #endregion Snippet:DigitalTwinsSampleGetCustomRelationship // Get all relationships in the graph where buildingTwinId is the source of the relationship. #region Snippet:DigitalTwinsSampleGetAllRelationships 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"]}"); } #endregion Snippet:DigitalTwinsSampleGetAllRelationships // Get all incoming relationships in the graph where buildingTwinId is the target of the relationship. #region Snippet:DigitalTwinsSampleGetIncomingRelationships 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}."); } #endregion Snippet:DigitalTwinsSampleGetIncomingRelationships // Delete the contains relationship, created earlier in the sample code, from building to floor. #region Snippet:DigitalTwinsSampleDeleteRelationship Response deleteBuildingRelationshipResponse = await client.DeleteRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId"); Console.WriteLine($"Deleted relationship with Id buildingFloorRelationshipId. Status response: {deleteBuildingRelationshipResponse.Status}."); #endregion Snippet:DigitalTwinsSampleDeleteRelationship // Delete the containedIn relationship, created earlier in the sample code, from floor to building. Response deleteFloorRelationshipResponse = await client.DeleteRelationshipAsync("floorTwinId", "floorBuildingRelationshipId"); Console.WriteLine($"Deleted relationship with Id floorBuildingRelationshipId. Status response: {deleteFloorRelationshipResponse.Status}."); // 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}."); } }
public async Task Component_Lifecycle() { // arrange DigitalTwinsClient client = GetClient(); string wifiModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.WifiModelIdPrefix).ConfigureAwait(false); string roomWithWifiModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.RoomWithWifiModelIdPrefix).ConfigureAwait(false); string roomWithWifiTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.RoomWithWifiTwinIdPrefix).ConfigureAwait(false); string wifiComponentName = "wifiAccessPoint"; try { // CREATE // create roomWithWifi model string wifiModel = TestAssetsHelper.GetWifiModelPayload(wifiModelId); // create wifi model string roomWithWifiModel = TestAssetsHelper.GetRoomWithWifiModelPayload(roomWithWifiModelId, wifiModelId, wifiComponentName); await CreateAndListModelsAsync(client, new List <string> { roomWithWifiModel, wifiModel }).ConfigureAwait(false); // create room digital twin BasicDigitalTwin roomWithWifiTwin = TestAssetsHelper.GetRoomWithWifiTwinPayload(roomWithWifiModelId, wifiComponentName); await client.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>(roomWithWifiTwinId, roomWithWifiTwin); // Get the component Response <object> getComponentResponse = await client .GetComponentAsync <object>( roomWithWifiTwinId, wifiComponentName) .ConfigureAwait(false); // The response to the GET request should be 200 (OK) getComponentResponse.GetRawResponse().Status.Should().Be((int)HttpStatusCode.OK); // Patch component JsonPatchDocument componentUpdatePatchDocument = new JsonPatchDocument(); componentUpdatePatchDocument.AppendReplace("/Network", "New Network"); Response updateComponentResponse = await client .UpdateComponentAsync( roomWithWifiTwinId, wifiComponentName, componentUpdatePatchDocument) .ConfigureAwait(false); // The response to the Patch request should be 204 (No content) updateComponentResponse.Status.Should().Be((int)HttpStatusCode.NoContent); } catch (Exception ex) { Assert.Fail($"Failure in executing a step in the test case: {ex.Message}."); } finally { // clean up try { if (!string.IsNullOrWhiteSpace(roomWithWifiTwinId)) { await client.DeleteDigitalTwinAsync(roomWithWifiTwinId).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(roomWithWifiModelId)) { await client.DeleteModelAsync(roomWithWifiModelId).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(wifiModelId)) { await client.DeleteModelAsync(wifiModelId).ConfigureAwait(false); } } catch (Exception ex) { Assert.Fail($"Test clean up failed: {ex.Message}"); } } }
/// <summary> /// Create a temporary component model, twin model and digital twin instance. /// Publish a telemetry message and a component telemetry message to the digital twin instance. /// </summary> public async Task RunSamplesAsync(DigitalTwinsClient client) { PrintHeader("PUBLISH TELEMETRY MESSAGE SAMPLE"); // For the purpose of this example we will create temporary models using a random model Ids. // We will also create temporary twin instances to publish the telemetry to. string componentModelId = await GetUniqueModelIdAsync(SamplesConstants.TemporaryComponentModelPrefix, client); string modelId = await GetUniqueModelIdAsync(SamplesConstants.TemporaryModelPrefix, client); string twinId = await GetUniqueTwinIdAsync(SamplesConstants.TemporaryTwinPrefix, client); string newComponentModelPayload = SamplesConstants.TemporaryComponentModelPayload .Replace(SamplesConstants.ComponentId, componentModelId); string newModelPayload = SamplesConstants.TemporaryModelWithComponentPayload .Replace(SamplesConstants.ModelId, modelId) .Replace(SamplesConstants.ComponentId, componentModelId); // Then we create the models. await client .CreateModelsAsync(new[] { newComponentModelPayload, newModelPayload }); // Get the models we just created AsyncPageable <DigitalTwinsModelData> models = client.GetModelsAsync(); await foreach (DigitalTwinsModelData model in models) { Console.WriteLine($"Successfully created model '{model.Id}'"); } // Create digital twin with Component payload. string twinPayload = SamplesConstants.TemporaryTwinPayload .Replace(SamplesConstants.ModelId, modelId); BasicDigitalTwin basicDigitalTwin = JsonSerializer.Deserialize <BasicDigitalTwin>(twinPayload); Response <BasicDigitalTwin> createDigitalTwinResponse = await client.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>(twinId, basicDigitalTwin); Console.WriteLine($"Created digital twin '{createDigitalTwinResponse.Value.Id}'."); try { #region Snippet:DigitalTwinsSamplePublishTelemetry // construct your json telemetry payload by hand. await client.PublishTelemetryAsync(twinId, Guid.NewGuid().ToString(), "{\"Telemetry1\": 5}"); Console.WriteLine($"Published telemetry message to twin '{twinId}'."); #endregion Snippet:DigitalTwinsSamplePublishTelemetry #region Snippet:DigitalTwinsSamplePublishComponentTelemetry // construct your json telemetry payload by serializing a dictionary. var telemetryPayload = new Dictionary <string, int> { { "ComponentTelemetry1", 9 } }; await client.PublishComponentTelemetryAsync( twinId, "Component1", Guid.NewGuid().ToString(), JsonSerializer.Serialize(telemetryPayload)); Console.WriteLine($"Published component telemetry message to twin '{twinId}'."); #endregion Snippet:DigitalTwinsSamplePublishComponentTelemetry } catch (Exception ex) { FatalError($"Failed to publish a telemetry message due to: {ex.Message}"); } try { // Delete the twin. await client.DeleteDigitalTwinAsync(twinId); // Delete the models. await client.DeleteModelAsync(modelId); await client.DeleteModelAsync(componentModelId); } catch (RequestFailedException ex) when(ex.Status == (int)HttpStatusCode.NotFound) { // Digital twin or models do not exist. } catch (RequestFailedException ex) { FatalError($"Failed to delete due to: {ex.Message}"); } }
public static async Task Run( [EventHubTrigger("lifecycleevents", Connection = "EVENTHUB_CONNECTIONSTRING")] EventData[] events, ILogger log) { var exceptions = new List <Exception>(events.Length); // Create Digital Twin client var cred = new ManagedIdentityCredential(adtAppId); var client = new DigitalTwinsClient( new Uri(adtInstanceUrl), cred, new DigitalTwinsClientOptions { Transport = new HttpClientTransport(singletonHttpClientInstance) }); foreach (EventData eventData in events) { try { //log.LogDebug($"EventData: {System.Text.Json.JsonSerializer.Serialize(eventData)}"); string opType = eventData.Properties["opType"] as string; if (opType == "deleteDeviceIdentity") { string deviceId = eventData.Properties["deviceId"] as string; try { // Find twin based on the original Registration ID BasicDigitalTwin digitalTwin = await client.GetDigitalTwinAsync <BasicDigitalTwin>(deviceId); // In order to delete the twin, all relationships must first be removed await DeleteAllRelationshipsAsync(client, digitalTwin.Id, log); // Delete the twin await client.DeleteDigitalTwinAsync(digitalTwin.Id, digitalTwin.ETag); log.LogInformation($"Twin {digitalTwin.Id} deleted in DT"); } catch (RequestFailedException e) when(e.Status == (int)HttpStatusCode.NotFound) { log.LogWarning($"Twin {deviceId} not found in DT"); } } } catch (Exception e) { // We need to keep processing the rest of the batch - capture this exception and continue. exceptions.Add(e); } } if (exceptions.Count > 1) { throw new AggregateException(exceptions); } if (exceptions.Count == 1) { throw exceptions.Single(); } }
/// <summary> /// Create a temporary component model, twin model and digital twin instance. /// Publish a telemetry message and a component telemetry message to the digital twin instance. /// </summary> public async Task RunSamplesAsync(DigitalTwinsClient client) { PrintHeader("PUBLISH TELEMETRY MESSAGE SAMPLE"); // For the purpose of this example we will create temporary models using a random model Ids. // We will also create temporary twin instances to publish the telemetry to. string componentModelId = await GetUniqueModelIdAsync(SamplesConstants.TemporaryComponentModelPrefix, client); string modelId = await GetUniqueModelIdAsync(SamplesConstants.TemporaryModelPrefix, client); string twinId = await GetUniqueTwinIdAsync(SamplesConstants.TemporaryTwinPrefix, client); string newComponentModelPayload = SamplesConstants.TemporaryComponentModelPayload .Replace(SamplesConstants.ComponentId, componentModelId); string newModelPayload = SamplesConstants.TemporaryModelWithComponentPayload .Replace(SamplesConstants.ModelId, modelId) .Replace(SamplesConstants.ComponentId, componentModelId); // Then we create the models. await client .CreateModelsAsync(new[] { newComponentModelPayload, newModelPayload }); Console.WriteLine($"Successfully created models with Ids: {componentModelId}, {modelId}"); // Create digital twin with Component payload. string twinPayload = SamplesConstants.TemporaryTwinPayload .Replace(SamplesConstants.ModelId, modelId); await client.CreateDigitalTwinAsync(twinId, twinPayload); Console.WriteLine($"Created digital twin {twinId}."); try { #region Snippet:DigitalTwinsSamplePublishTelemetry // construct your json telemetry payload by hand. Response publishTelemetryResponse = await client.PublishTelemetryAsync(twinId, "{\"Telemetry1\": 5}"); Console.WriteLine($"Published telemetry message to twin with Id {twinId}. Response status: {publishTelemetryResponse.Status}"); #endregion Snippet:DigitalTwinsSamplePublishTelemetry #region Snippet:DigitalTwinsSamplePublishComponentTelemetry // construct your json telemetry payload by serializing a dictionary. var telemetryPayload = new Dictionary <string, int> { { "ComponentTelemetry1", 9 } }; Response publishTelemetryToComponentResponse = await client.PublishComponentTelemetryAsync( twinId, "Component1", JsonSerializer.Serialize(telemetryPayload)); Console.WriteLine($"Published component telemetry message to twin with Id {twinId}. Response status: {publishTelemetryToComponentResponse.Status}"); #endregion Snippet:DigitalTwinsSamplePublishComponentTelemetry } catch (Exception ex) { FatalError($"Failed to publish a telemetry message due to {ex.Message}"); } try { // Delete the twin. await client.DeleteDigitalTwinAsync(twinId); // Delete the models. await client.DeleteModelAsync(modelId); await client.DeleteModelAsync(componentModelId); } catch (RequestFailedException ex) when(ex.Status == (int)HttpStatusCode.NotFound) { // Digital twin or models do not exist. } catch (RequestFailedException ex) { FatalError($"Failed to delete due to {ex.Message}"); } }
public static async Task Run( [EventHubTrigger("lifecycleevents", Connection = "EVENTHUB_CONNECTIONSTRING")] EventData[] events, ILogger log) { // After this is deployed, you need to turn the Managed Identity Status to "On", // Grab Object Id of the function and assigned "Azure Digital Twins Owner (Preview)" role // to this function identity in order for this function to be authorized on ADT APIs. if (adtInstanceUrl == null) { log.LogError("Application setting \"ADT_SERVICE_URL\" not set"); } var exceptions = new List <Exception>(); foreach (EventData eventData in events) { try { //log.LogDebug($"EventData: {System.Text.Json.JsonSerializer.Serialize(eventData)}"); string opType = eventData.Properties["opType"] as string; if (opType == "deleteDeviceIdentity") { string deviceId = eventData.Properties["deviceId"] as string; //string dtId = eventData.Properties["dtId"] as string; // enriched property not available in 'delete' event // Create Digital Twin client var cred = new ManagedIdentityCredential("https://digitaltwins.azure.net"); var client = new DigitalTwinsClient(new Uri(adtInstanceUrl), cred, new DigitalTwinsClientOptions { Transport = new HttpClientTransport(httpClient) }); // Find twin based on the original Regitration ID string regID = deviceId; // simple mapping string dtId = await GetTwinId(client, regID, log); if (dtId != null) { await DeleteRelationships(client, dtId, log); // Delete twin await client.DeleteDigitalTwinAsync(dtId); log.LogInformation($"Twin '{dtId}' deleted in DT"); } } } catch (Exception e) { // We need to keep processing the rest of the batch - capture this exception and continue. exceptions.Add(e); } } if (exceptions.Count > 1) { throw new AggregateException(exceptions); } if (exceptions.Count == 1) { throw exceptions.Single(); } }
public async Task Relationships_Lifecycle() { // arrange DigitalTwinsClient client = GetClient(); var floorContainsRoomRelationshipId = "FloorToRoomRelationship"; var floorCooledByHvacRelationshipId = "FloorToHvacRelationship"; var hvacCoolsFloorRelationshipId = "HvacToFloorRelationship"; var roomContainedInFloorRelationshipId = "RoomToFloorRelationship"; string floorModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.FloorModelIdPrefix).ConfigureAwait(false); string roomModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.RoomModelIdPrefix).ConfigureAwait(false); string hvacModelId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.HvacModelIdPrefix).ConfigureAwait(false); string floorTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.FloorTwinIdPrefix).ConfigureAwait(false); string roomTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.RoomTwinIdPrefix).ConfigureAwait(false); string hvacTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.HvacTwinIdPrefix).ConfigureAwait(false); try { // create floor, room and hvac model string floorModel = TestAssetsHelper.GetFloorModelPayload(floorModelId, roomModelId, hvacModelId); string roomModel = TestAssetsHelper.GetRoomModelPayload(roomModelId, floorModelId); string hvacModel = TestAssetsHelper.GetHvacModelPayload(hvacModelId, floorModelId); await client.CreateModelsAsync(new List <string> { floorModel, roomModel, hvacModel }).ConfigureAwait(false); // create floor twin BasicDigitalTwin floorTwin = TestAssetsHelper.GetFloorTwinPayload(floorModelId); await client.CreateDigitalTwinAsync <BasicDigitalTwin>(floorTwinId, floorTwin).ConfigureAwait(false); // Create room twin BasicDigitalTwin roomTwin = TestAssetsHelper.GetRoomTwinPayload(roomModelId); await client.CreateDigitalTwinAsync <BasicDigitalTwin>(roomTwinId, roomTwin).ConfigureAwait(false); // create hvac twin BasicDigitalTwin hvacTwin = TestAssetsHelper.GetHvacTwinPayload(hvacModelId); await client.CreateDigitalTwinAsync <BasicDigitalTwin>(hvacTwinId, hvacTwin).ConfigureAwait(false); BasicRelationship floorContainsRoomPayload = TestAssetsHelper.GetRelationshipWithPropertyPayload(roomTwinId, ContainsRelationship, "isAccessRestricted", true); BasicRelationship floorTwinCoolsRelationshipPayload = TestAssetsHelper.GetRelationshipPayload(floorTwinId, CoolsRelationship); BasicRelationship floorTwinContainedInRelationshipPayload = TestAssetsHelper.GetRelationshipPayload(floorTwinId, ContainedInRelationship); BasicRelationship floorCooledByHvacPayload = TestAssetsHelper.GetRelationshipPayload(hvacTwinId, CooledByRelationship); JsonPatchDocument floorContainsRoomUpdatePayload = new JsonPatchDocument(); floorContainsRoomUpdatePayload.AppendReplace("/isAccessRestricted", false); // CREATE relationships // create Relationship from Floor -> Room await client .CreateRelationshipAsync <BasicRelationship>( floorTwinId, floorContainsRoomRelationshipId, floorContainsRoomPayload) .ConfigureAwait(false); // create Relationship from Floor -> Hvac await client .CreateRelationshipAsync <BasicRelationship>( floorTwinId, floorCooledByHvacRelationshipId, floorCooledByHvacPayload) .ConfigureAwait(false); // create Relationship from Hvac -> Floor await client .CreateRelationshipAsync <BasicRelationship>( hvacTwinId, hvacCoolsFloorRelationshipId, floorTwinCoolsRelationshipPayload) .ConfigureAwait(false); // create Relationship from Room -> Floor await client .CreateRelationshipAsync <BasicRelationship>( roomTwinId, roomContainedInFloorRelationshipId, floorTwinContainedInRelationshipPayload) .ConfigureAwait(false); // UPDATE relationships // create Relationship from Floor -> Room await client .UpdateRelationshipAsync( floorTwinId, floorContainsRoomRelationshipId, floorContainsRoomUpdatePayload) .ConfigureAwait(false); // GET relationship Response <BasicRelationship> containsRelationshipId = await client .GetRelationshipAsync <BasicRelationship>( floorTwinId, floorContainsRoomRelationshipId) .ConfigureAwait(false); // LIST incoming relationships AsyncPageable <IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync(floorTwinId); int numberOfIncomingRelationshipsToFloor = 0; await foreach (IncomingRelationship relationship in incomingRelationships) { ++numberOfIncomingRelationshipsToFloor; } numberOfIncomingRelationshipsToFloor.Should().Be(2, "floor has incoming relationships from room and hvac"); // LIST relationships AsyncPageable <string> floorRelationships = client.GetRelationshipsAsync(floorTwinId); int numberOfFloorRelationships = 0; await foreach (var relationship in floorRelationships) { ++numberOfFloorRelationships; } numberOfFloorRelationships.Should().Be(2, "floor has an relationship to room and hvac"); // LIST relationships by name AsyncPageable <string> roomTwinRelationships = client .GetRelationshipsAsync( roomTwinId, ContainedInRelationship); containsRelationshipId.Value.Id.Should().Be(floorContainsRoomRelationshipId); int numberOfRelationships = 0; await foreach (var relationship in roomTwinRelationships) { ++numberOfRelationships; } numberOfRelationships.Should().Be(1, "room has only one containedIn relationship to floor"); await client .DeleteRelationshipAsync( floorTwinId, floorContainsRoomRelationshipId) .ConfigureAwait(false); await client .DeleteRelationshipAsync( roomTwinId, roomContainedInFloorRelationshipId) .ConfigureAwait(false); await client .DeleteRelationshipAsync( floorTwinId, floorCooledByHvacRelationshipId) .ConfigureAwait(false); await client .DeleteRelationshipAsync( hvacTwinId, hvacCoolsFloorRelationshipId) .ConfigureAwait(false); Func <Task> act = async() => { await client .GetRelationshipAsync <BasicRelationship>( floorTwinId, floorContainsRoomRelationshipId) .ConfigureAwait(false); }; act.Should().Throw <RequestFailedException>() .And.Status.Should().Be((int)HttpStatusCode.NotFound); act = async() => { await client .GetRelationshipAsync <BasicRelationship>( roomTwinId, roomContainedInFloorRelationshipId) .ConfigureAwait(false); }; act.Should().Throw <RequestFailedException>(). And.Status.Should().Be((int)HttpStatusCode.NotFound); act = async() => { await client .GetRelationshipAsync <BasicRelationship>( floorTwinId, floorCooledByHvacRelationshipId) .ConfigureAwait(false); }; act.Should().Throw <RequestFailedException>(). And.Status.Should().Be((int)HttpStatusCode.NotFound); act = async() => { await client .GetRelationshipAsync <BasicRelationship>( hvacTwinId, hvacCoolsFloorRelationshipId) .ConfigureAwait(false); }; act.Should().Throw <RequestFailedException>() .And.Status.Should().Be((int)HttpStatusCode.NotFound); } finally { // clean up try { await Task .WhenAll( client.DeleteDigitalTwinAsync(floorTwinId), client.DeleteDigitalTwinAsync(roomTwinId), client.DeleteDigitalTwinAsync(hvacTwinId), client.DeleteModelAsync(hvacModelId), client.DeleteModelAsync(floorModelId), client.DeleteModelAsync(roomModelId)) .ConfigureAwait(false); } catch (Exception ex) { Assert.Fail($"Test clean up failed: {ex.Message}"); } } }
public async Task Relationships_PaginationWorks() { DigitalTwinsClient client = GetClient(); string floorModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.FloorModelIdPrefix).ConfigureAwait(false); string roomModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.RoomModelIdPrefix).ConfigureAwait(false); string hvacModelId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.HvacModelIdPrefix).ConfigureAwait(false); string floorTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.FloorTwinIdPrefix).ConfigureAwait(false); string roomTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.RoomTwinIdPrefix).ConfigureAwait(false); string hvacTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.HvacTwinIdPrefix).ConfigureAwait(false); try { // create floor, room and hvac model string floorModel = TestAssetsHelper.GetFloorModelPayload(floorModelId, roomModelId, hvacModelId); string roomModel = TestAssetsHelper.GetRoomModelPayload(roomModelId, floorModelId); string hvacModel = TestAssetsHelper.GetHvacModelPayload(hvacModelId, floorModelId); await client.CreateModelsAsync(new List <string> { floorModel, roomModel, hvacModel }).ConfigureAwait(false); // create floor twin BasicDigitalTwin floorTwin = TestAssetsHelper.GetFloorTwinPayload(floorModelId); await client.CreateDigitalTwinAsync <BasicDigitalTwin>(floorTwinId, floorTwin).ConfigureAwait(false); // Create room twin BasicDigitalTwin roomTwin = TestAssetsHelper.GetRoomTwinPayload(roomModelId); await client.CreateDigitalTwinAsync <BasicDigitalTwin>(roomTwinId, roomTwin).ConfigureAwait(false); BasicRelationship floorContainsRoomPayload = TestAssetsHelper.GetRelationshipWithPropertyPayload(roomTwinId, ContainsRelationship, "isAccessRestricted", true); BasicRelationship floorTwinContainedInRelationshipPayload = TestAssetsHelper.GetRelationshipPayload(floorTwinId, ContainedInRelationship); // For the sake of test simplicity, we'll just add multiple relationships from the same floor to the same room. for (int i = 0; i < bulkRelationshipCount; i++) { var floorContainsRoomRelationshipId = $"FloorToRoomRelationship-{GetRandom()}"; // create Relationship from Floor -> Room await client .CreateRelationshipAsync <BasicRelationship>( floorTwinId, floorContainsRoomRelationshipId, floorContainsRoomPayload) .ConfigureAwait(false); } // For the sake of test simplicity, we'll just add multiple relationships from the same room to the same floor. for (int i = 0; i < bulkRelationshipCount; i++) { var roomContainedInFloorRelationshipId = $"RoomToFloorRelationship-{GetRandom()}"; // create Relationship from Room -> Floor await client .CreateRelationshipAsync <BasicRelationship>( roomTwinId, roomContainedInFloorRelationshipId, floorTwinContainedInRelationshipPayload) .ConfigureAwait(false); } // LIST incoming relationships by page AsyncPageable <IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync(floorTwinId); int incomingRelationshipPageCount = 0; await foreach (Page <IncomingRelationship> incomingRelationshipPage in incomingRelationships.AsPages()) { incomingRelationshipPageCount++; if (incomingRelationshipPage.ContinuationToken != null) { incomingRelationshipPage.Values.Count.Should().Be(defaultRelationshipPageSize, "Unexpected page size for a non-terminal page"); } } incomingRelationshipPageCount.Should().BeGreaterThan(1, "Expected more than one page of incoming relationships"); // LIST outgoing relationships by page AsyncPageable <string> outgoingRelationships = client.GetRelationshipsAsync(floorTwinId); int outgoingRelationshipPageCount = 0; await foreach (Page <string> outgoingRelationshipPage in outgoingRelationships.AsPages()) { outgoingRelationshipPageCount++; if (outgoingRelationshipPage.ContinuationToken != null) { outgoingRelationshipPage.Values.Count.Should().Be(defaultRelationshipPageSize, "Unexpected page size for a non-terminal page"); } } outgoingRelationshipPageCount.Should().BeGreaterThan(1, "Expected more than one page of outgoing relationships"); } finally { // clean up try { await Task .WhenAll( client.DeleteDigitalTwinAsync(floorTwinId), client.DeleteDigitalTwinAsync(roomTwinId), client.DeleteDigitalTwinAsync(hvacTwinId), client.DeleteModelAsync(hvacModelId), client.DeleteModelAsync(floorModelId), client.DeleteModelAsync(roomModelId)) .ConfigureAwait(false); } catch (Exception ex) { Assert.Fail($"Test clean up failed: {ex.Message}"); } } }
public async Task Component_UpdateComponentSucceedsWhenIfMatchHeaderIsCorrect() { // arrange DigitalTwinsClient client = GetClient(); string wifiModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.WifiModelIdPrefix).ConfigureAwait(false); string roomWithWifiModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.RoomWithWifiModelIdPrefix).ConfigureAwait(false); string roomWithWifiTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.RoomWithWifiTwinIdPrefix).ConfigureAwait(false); string wifiComponentName = "wifiAccessPoint"; try { // CREATE // create roomWithWifi model string wifiModel = TestAssetsHelper.GetWifiModelPayload(wifiModelId); // create wifi model string roomWithWifiModel = TestAssetsHelper.GetRoomWithWifiModelPayload(roomWithWifiModelId, wifiModelId, wifiComponentName); await client.CreateModelsAsync(new List <string> { roomWithWifiModel, wifiModel }).ConfigureAwait(false); // create room digital twin BasicDigitalTwin roomWithWifiTwin = TestAssetsHelper.GetRoomWithWifiTwinPayload(roomWithWifiModelId, wifiComponentName); await client.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>(roomWithWifiTwinId, roomWithWifiTwin); // Get the component Response <object> getComponentResponse = await client .GetComponentAsync <object>( roomWithWifiTwinId, wifiComponentName) .ConfigureAwait(false); // Patch component JsonPatchDocument componentUpdatePatchDocument = new JsonPatchDocument(); componentUpdatePatchDocument.AppendReplace("/Network", "New Network"); Response updateComponentResponse = await client .UpdateComponentAsync( roomWithWifiTwinId, wifiComponentName, componentUpdatePatchDocument) .ConfigureAwait(false); // Get the latest ETag ETag?etagBeforeUpdate = (await client.GetDigitalTwinAsync <BasicDigitalTwin>(roomWithWifiTwinId)).Value.ETag; Assert.IsNotNull(etagBeforeUpdate); // Patch component again, but with the now out of date ETag JsonPatchDocument secondComponentUpdatePatchDocument = new JsonPatchDocument(); componentUpdatePatchDocument.AppendReplace("/Network", "Even newer Network"); try { await client .UpdateComponentAsync( roomWithWifiTwinId, wifiComponentName, secondComponentUpdatePatchDocument, etagBeforeUpdate) .ConfigureAwait(false); } catch (RequestFailedException ex) when(ex.Status == (int)HttpStatusCode.PreconditionFailed) { throw new AssertionException("UpdateComponent should not have thrown PreconditionFailed because the ETag was up to date", ex); } } finally { // clean up try { if (!string.IsNullOrWhiteSpace(roomWithWifiTwinId)) { await client.DeleteDigitalTwinAsync(roomWithWifiTwinId).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(roomWithWifiModelId)) { await client.DeleteModelAsync(roomWithWifiModelId).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(wifiModelId)) { await client.DeleteModelAsync(wifiModelId).ConfigureAwait(false); } } catch (Exception ex) { Assert.Fail($"Test clean up failed: {ex.Message}"); } } }
public async Task TestNewtonsoftObjectSerializerWithDigitalTwins() { DigitalTwinsClient defaultClient = GetClient(); string roomTwinId = await GetUniqueTwinIdAsync(defaultClient, TestAssetDefaults.RoomTwinIdPrefix).ConfigureAwait(false); string floorModelId = await GetUniqueModelIdAsync(defaultClient, TestAssetDefaults.FloorModelIdPrefix).ConfigureAwait(false); string roomModelId = await GetUniqueModelIdAsync(defaultClient, TestAssetDefaults.RoomModelIdPrefix).ConfigureAwait(false); try { // arrange // create room model string roomModel = TestAssetsHelper.GetRoomModelPayload(roomModelId, floorModelId); await CreateAndListModelsAsync(defaultClient, new List <string> { roomModel }).ConfigureAwait(false); // act // create room twin BasicDigitalTwin roomTwin = TestAssetsHelper.GetRoomTwinPayload(roomModelId); await defaultClient.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>(roomTwinId, roomTwin).ConfigureAwait(false); // Create a client with NewtonsoftJsonObjectSerializer configured as the serializer. DigitalTwinsClient testClient = GetClient( new DigitalTwinsClientOptions { Serializer = new NewtonsoftJsonObjectSerializer() }); // Get digital twin using the simple DigitalTwin model annotated with Newtonsoft attributes SimpleNewtonsoftDtModel getResponse = await testClient.GetDigitalTwinAsync <SimpleNewtonsoftDtModel>(roomTwinId).ConfigureAwait(false); getResponse.Id.Should().NotBeNullOrEmpty("Digital twin ID should not be null or empty"); // Query DigitalTwins using the simple DigitalTwin model annotated with Newtonsoft attributes AsyncPageable <SimpleNewtonsoftDtModel> queryResponse = testClient.QueryAsync <SimpleNewtonsoftDtModel>("SELECT * FROM DIGITALTWINS"); await foreach (SimpleNewtonsoftDtModel twin in queryResponse) { twin.Id.Should().NotBeNullOrEmpty("Digital twin Id should not be null or empty"); } } finally { // cleanup try { // delete twin if (!string.IsNullOrWhiteSpace(roomTwinId)) { await defaultClient.DeleteDigitalTwinAsync(roomTwinId).ConfigureAwait(false); } // delete models if (!string.IsNullOrWhiteSpace(roomModelId)) { await defaultClient.DeleteModelAsync(roomModelId).ConfigureAwait(false); } } catch (Exception ex) { Assert.Fail($"Test clean up failed: {ex.Message}"); } } }
/// <summary> /// Creates a digital twin with Component and upates Component /// </summary> public async Task RunSamplesAsync(DigitalTwinsClient client) { PrintHeader("COMPONENT SAMPLE"); // For the purpose of this example we will create temporary models using a random model Ids. // We have to make sure these model Ids are unique within the DT instance. string componentModelId = await GetUniqueModelIdAsync(SamplesConstants.TemporaryComponentModelPrefix, client); string modelId = await GetUniqueModelIdAsync(SamplesConstants.TemporaryModelPrefix, client); string basicDtId = await GetUniqueTwinIdAsync(SamplesConstants.TemporaryTwinPrefix, client); string newComponentModelPayload = SamplesConstants.TemporaryComponentModelPayload .Replace(SamplesConstants.ComponentId, componentModelId); string newModelPayload = SamplesConstants.TemporaryModelWithComponentPayload .Replace(SamplesConstants.ModelId, modelId) .Replace(SamplesConstants.ComponentId, componentModelId); // Then we create models await client.CreateModelsAsync( new[] { newComponentModelPayload, newModelPayload }); Console.WriteLine($"Created models {componentModelId} and {modelId}."); #region Snippet:DigitalTwinsSampleCreateBasicTwin // Create digital twin with component payload using the BasicDigitalTwin serialization helper var basicTwin = new BasicDigitalTwin { Id = basicDtId, // model Id of digital twin Metadata = { ModelId = modelId }, Contents = { // digital twin properties { "Prop1", "Value1" }, { "Prop2", 987 }, // component { "Component1", new BasicDigitalTwinComponent { // component properties Contents = { { "ComponentProp1", "Component value 1" }, { "ComponentProp2", 123 }, }, } }, }, }; Response <BasicDigitalTwin> createDigitalTwinResponse = await client.CreateOrReplaceDigitalTwinAsync(basicDtId, basicTwin); Console.WriteLine($"Created digital twin '{createDigitalTwinResponse.Value.Id}'."); #endregion Snippet:DigitalTwinsSampleCreateBasicTwin // You can also get a digital twin as a BasicDigitalTwin type. // It works well for basic stuff, but as you can see it gets more difficult when delving into // more complex properties, like components. #region Snippet:DigitalTwinsSampleGetBasicDigitalTwin Response <BasicDigitalTwin> getBasicDtResponse = await client.GetDigitalTwinAsync <BasicDigitalTwin>(basicDtId); if (getBasicDtResponse.GetRawResponse().Status == (int)HttpStatusCode.OK) { BasicDigitalTwin basicDt = getBasicDtResponse.Value; // Must cast Component1 as a JsonElement and get its raw text in order to deserialize it as a dictionary string component1RawText = ((JsonElement)basicDt.Contents["Component1"]).GetRawText(); IDictionary <string, object> component1 = JsonSerializer.Deserialize <IDictionary <string, object> >(component1RawText); Console.WriteLine($"Retrieved and deserialized digital twin {basicDt.Id}:\n\t" + $"ETag: {basicDt.ETag}\n\t" + $"Prop1: {basicDt.Contents["Prop1"]}\n\t" + $"Prop2: {basicDt.Contents["Prop2"]}\n\t" + $"ComponentProp1: {component1["ComponentProp1"]}\n\t" + $"ComponentProp2: {component1["ComponentProp2"]}"); } #endregion Snippet:DigitalTwinsSampleGetBasicDigitalTwin string customDtId = await GetUniqueTwinIdAsync(SamplesConstants.TemporaryTwinPrefix, client); // Alternatively, you can create your own custom data types to serialize and deserialize your digital twins. // By specifying your properties and types directly, it requires less code or knowledge of the type for // interaction. #region Snippet:DigitalTwinsSampleCreateCustomTwin var customTwin = new CustomDigitalTwin { Id = customDtId, Metadata = { ModelId = modelId }, Prop1 = "Prop1 val", Prop2 = 987, Component1 = new MyCustomComponent { ComponentProp1 = "Component prop1 val", ComponentProp2 = 123, }, }; Response <CustomDigitalTwin> createCustomDigitalTwinResponse = await client.CreateOrReplaceDigitalTwinAsync(customDtId, customTwin); Console.WriteLine($"Created digital twin '{createCustomDigitalTwinResponse.Value.Id}'."); #endregion Snippet:DigitalTwinsSampleCreateCustomTwin // Getting a digital twin as a custom data type is extremely easy. // Custom types provide the best possible experience. #region Snippet:DigitalTwinsSampleGetCustomDigitalTwin Response <CustomDigitalTwin> getCustomDtResponse = await client.GetDigitalTwinAsync <CustomDigitalTwin>(customDtId); CustomDigitalTwin customDt = 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}"); #endregion Snippet:DigitalTwinsSampleGetCustomDigitalTwin #region Snippet:DigitalTwinsSampleUpdateComponent // Update Component1 by replacing the property ComponentProp1 value, // using an optional utility to build the payload. var componentJsonPatchDocument = new JsonPatchDocument(); componentJsonPatchDocument.AppendReplace("/ComponentProp1", "Some new value"); await client.UpdateComponentAsync(basicDtId, "Component1", componentJsonPatchDocument); Console.WriteLine($"Updated component for digital twin '{basicDtId}'."); #endregion Snippet:DigitalTwinsSampleUpdateComponent // Get Component #region Snippet:DigitalTwinsSampleGetComponent await client.GetComponentAsync <MyCustomComponent>(basicDtId, SamplesConstants.ComponentName); Console.WriteLine($"Retrieved component for digital twin '{basicDtId}'."); #endregion Snippet:DigitalTwinsSampleGetComponent // Clean up try { await client.DeleteDigitalTwinAsync(basicDtId); await client.DeleteDigitalTwinAsync(customDtId); } catch (RequestFailedException ex) { Console.WriteLine($"Failed to delete digital twin due to {ex}"); } try { await client.DeleteModelAsync(modelId); await client.DeleteModelAsync(componentModelId); } catch (RequestFailedException ex) { Console.WriteLine($"Failed to delete models due to {ex}"); } }
public async Task DigitalTwins_DeleteTwinFailsIfMatchProvidesOutdatedEtag() { DigitalTwinsClient client = GetClient(); string roomTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.RoomTwinIdPrefix).ConfigureAwait(false); string floorModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.FloorModelIdPrefix).ConfigureAwait(false); string roomModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.RoomModelIdPrefix).ConfigureAwait(false); try { // arrange // create room model string roomModel = TestAssetsHelper.GetRoomModelPayload(roomModelId, floorModelId); await client.CreateModelsAsync(new List <string> { roomModel }).ConfigureAwait(false); // act // create room twin BasicDigitalTwin roomTwin = TestAssetsHelper.GetRoomTwinPayload(roomModelId); await client.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>(roomTwinId, roomTwin).ConfigureAwait(false); // get twin ETag?etagBeforeUpdate = (await client.GetDigitalTwinAsync <BasicDigitalTwin>(roomTwinId).ConfigureAwait(false)).Value.ETag; // update twin JsonPatchDocument updateTwinPatchDocument = new JsonPatchDocument(); updateTwinPatchDocument.AppendAdd("/Humidity", 30); updateTwinPatchDocument.AppendReplace("/Temperature", 70); updateTwinPatchDocument.AppendRemove("/EmployeeId"); await client.UpdateDigitalTwinAsync(roomTwinId, updateTwinPatchDocument, ETag.All).ConfigureAwait(false); // assert Func <Task> act = async() => { // since the ETag is out of date, this call should throw a 412 await client.DeleteDigitalTwinAsync(roomTwinId, etagBeforeUpdate).ConfigureAwait(false); }; act.Should().Throw <RequestFailedException>() .And.Status.Should().Be((int)HttpStatusCode.PreconditionFailed); } finally { // cleanup try { if (!string.IsNullOrWhiteSpace(roomModelId)) { await client.DeleteModelAsync(roomModelId).ConfigureAwait(false); } } catch (Exception ex) { Assert.Fail($"Test clean up failed: {ex.Message}"); } } }
public async Task DigitalTwins_DeleteTwinSucceedsIfMatchProvidesCorrectEtag() { DigitalTwinsClient client = GetClient(); string roomTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.RoomTwinIdPrefix).ConfigureAwait(false); string floorModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.FloorModelIdPrefix).ConfigureAwait(false); string roomModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.RoomModelIdPrefix).ConfigureAwait(false); try { // arrange // create room model string roomModel = TestAssetsHelper.GetRoomModelPayload(roomModelId, floorModelId); await client.CreateModelsAsync(new List <string> { roomModel }).ConfigureAwait(false); // act // create room twin BasicDigitalTwin roomTwin = TestAssetsHelper.GetRoomTwinPayload(roomModelId); await client.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>(roomTwinId, roomTwin).ConfigureAwait(false); // update twin JsonPatchDocument updateTwinPatchDocument = new JsonPatchDocument(); updateTwinPatchDocument.AppendAdd("/Humidity", 30); updateTwinPatchDocument.AppendReplace("/Temperature", 70); updateTwinPatchDocument.AppendRemove("/EmployeeId"); await client.UpdateDigitalTwinAsync(roomTwinId, updateTwinPatchDocument, ETag.All).ConfigureAwait(false); // get twin ETag?correctETag = (await client.GetDigitalTwinAsync <BasicDigitalTwin>(roomTwinId).ConfigureAwait(false)).Value.ETag; Assert.IsNotNull(correctETag); try { // since the ETag is not out of date, this call should not throw a 412 await client.DeleteDigitalTwinAsync(roomTwinId, correctETag).ConfigureAwait(false); } catch (RequestFailedException ex) when(ex.Status == (int)HttpStatusCode.PreconditionFailed) { throw new AssertionException("UpdateRelationship should not have thrown PreconditionFailed because the ETag was up to date", ex); } } finally { // cleanup try { if (!string.IsNullOrWhiteSpace(roomModelId)) { await client.DeleteModelAsync(roomModelId).ConfigureAwait(false); } } catch (Exception ex) { Assert.Fail($"Test clean up failed: {ex.Message}"); } } }
public async Task Component_Lifecycle() { // arrange DigitalTwinsClient client = GetClient(); string wifiModelId = await GetUniqueModelIdAsync(client, TestAssetSettings.WifiModelIdPrefix).ConfigureAwait(false); string roomWithWifiModelId = await GetUniqueModelIdAsync(client, TestAssetSettings.RoomWithWifiModelIdPrefix).ConfigureAwait(false); string roomWithWifiTwinId = await GetUniqueTwinIdAsync(client, TestAssetSettings.RoomWithWifiTwinIdPrefix).ConfigureAwait(false); string wifiComponentName = "wifiAccessPoint"; try { // CREATE // create roomWithWifi model string wifiModel = TestAssetsHelper.GetWifiModelPayload(wifiModelId); // create wifi model string roomWithWifiModel = TestAssetsHelper.GetRoomWithWifiModelPayload(roomWithWifiModelId, wifiModelId, wifiComponentName); await client.CreateModelsAsync(new List <string> { roomWithWifiModel, wifiModel }).ConfigureAwait(false); // create room digital twin string roomWithWifiTwin = TestAssetsHelper.GetRoomWithWifiTwinPayload(roomWithWifiModelId, wifiModelId, wifiComponentName); await client.CreateDigitalTwinAsync(roomWithWifiTwinId, roomWithWifiTwin); // Get the component Response <string> getComponentResponse = await client .GetComponentAsync( roomWithWifiTwinId, wifiComponentName) .ConfigureAwait(false); // The response to the GET request should be 200 (OK) getComponentResponse.GetRawResponse().Status.Should().Be((int)HttpStatusCode.OK); // Patch component Response <string> updateComponentResponse = await client .UpdateComponentAsync( roomWithWifiTwinId, wifiComponentName, TestAssetsHelper.GetWifiComponentUpdatePayload()) .ConfigureAwait(false); // The response to the Patch request should be 204 (No content) updateComponentResponse.GetRawResponse().Status.Should().Be((int)HttpStatusCode.NoContent); } finally { // clean up try { if (!string.IsNullOrWhiteSpace(roomWithWifiTwinId)) { await client.DeleteDigitalTwinAsync(roomWithWifiTwinId).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(roomWithWifiModelId)) { await client.DeleteModelAsync(roomWithWifiModelId).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(wifiModelId)) { await client.DeleteModelAsync(wifiModelId).ConfigureAwait(false); } } catch (Exception ex) { Assert.Fail($"Test clean up failed: {ex.Message}"); } } }
public async Task DigitalTwins_Lifecycle() { DigitalTwinsClient client = GetClient(); string roomTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.RoomTwinIdPrefix).ConfigureAwait(false); string floorModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.FloorModelIdPrefix).ConfigureAwait(false); string roomModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.RoomModelIdPrefix).ConfigureAwait(false); try { // arrange // create room model string roomModel = TestAssetsHelper.GetRoomModelPayload(roomModelId, floorModelId); await client.CreateModelsAsync(new List <string> { roomModel }).ConfigureAwait(false); // act // create room twin BasicDigitalTwin roomTwin = TestAssetsHelper.GetRoomTwinPayload(roomModelId); await client.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>(roomTwinId, roomTwin).ConfigureAwait(false); // get twin await client.GetDigitalTwinAsync <BasicDigitalTwin>(roomTwinId).ConfigureAwait(false); // update twin JsonPatchDocument updateTwinPatchDocument = new JsonPatchDocument(); updateTwinPatchDocument.AppendAdd("/Humidity", 30); updateTwinPatchDocument.AppendReplace("/Temperature", 70); updateTwinPatchDocument.AppendRemove("/EmployeeId"); var requestOptions = new UpdateDigitalTwinOptions { IfMatch = "*" }; await client.UpdateDigitalTwinAsync(roomTwinId, updateTwinPatchDocument, requestOptions).ConfigureAwait(false); // delete a twin await client.DeleteDigitalTwinAsync(roomTwinId).ConfigureAwait(false); // assert Func <Task> act = async() => { await client.GetDigitalTwinAsync <BasicDigitalTwin>(roomTwinId).ConfigureAwait(false); }; act.Should().Throw <RequestFailedException>() .And.Status.Should().Be((int)HttpStatusCode.NotFound); } finally { // cleanup try { if (!string.IsNullOrWhiteSpace(roomModelId)) { await client.DeleteModelAsync(roomModelId).ConfigureAwait(false); } } catch (Exception ex) { Assert.Fail($"Test clean up failed: {ex.Message}"); } } }
/// <summary> /// Creates two digital twins, and connect them with relationships. /// </summary> 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"); string sampleBuildingModelId = "dtmi:com:samples:SampleBuilding;1"; string sampleFloorModelId = "dtmi:com:samples:SampleFloor;1"; await ModelLifecycleSamples.TryDeleteModelAsync(client, sampleBuildingModelId); await ModelLifecycleSamples.TryDeleteModelAsync(client, sampleFloorModelId); // Create a building digital twin model. string buildingModelPayload = SamplesConstants.TemporaryModelWithRelationshipPayload .Replace(SamplesConstants.RelationshipTargetModelId, sampleFloorModelId) .Replace(SamplesConstants.ModelId, sampleBuildingModelId) .Replace(SamplesConstants.ModelDisplayName, "Building") .Replace(SamplesConstants.RelationshipName, "contains"); await client.CreateModelsAsync( new[] { buildingModelPayload }); Console.WriteLine($"Created model '{sampleBuildingModelId}'."); // Create a floor digital twin model. string floorModelPayload = SamplesConstants.TemporaryModelWithRelationshipPayload .Replace(SamplesConstants.RelationshipTargetModelId, sampleBuildingModelId) .Replace(SamplesConstants.ModelId, sampleFloorModelId) .Replace(SamplesConstants.ModelDisplayName, "Floor") .Replace(SamplesConstants.RelationshipName, "containedIn"); await client.CreateModelsAsync(new[] { floorModelPayload }); // Get the model we just created Response <DigitalTwinsModelData> getFloorModelResponse = await client.GetModelAsync(sampleFloorModelId).ConfigureAwait(false); Console.WriteLine($"Created model '{getFloorModelResponse.Value.Id}'"); // Create a building digital twin. var buildingDigitalTwin = new BasicDigitalTwin { Id = "buildingTwinId", Metadata = { ModelId = sampleBuildingModelId } }; Response <BasicDigitalTwin> createDigitalTwinResponse = await client.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>("buildingTwinId", buildingDigitalTwin); Console.WriteLine($"Created twin '{createDigitalTwinResponse.Value.Id}'."); // Create a floor digital. var floorDigitalTwin = new BasicDigitalTwin { Id = "floorTwinId", Metadata = { ModelId = sampleFloorModelId } }; Response <BasicDigitalTwin> createFloorDigitalTwinResponse = await client.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>("floorTwinId", floorDigitalTwin); Console.WriteLine($"Created twin '{createFloorDigitalTwinResponse.Value.Id}'."); // 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", Properties = { { "Prop1", "Prop1 value" }, { "Prop2", 6 } } }; Response <BasicRelationship> createBuildingFloorRelationshipResponse = await client .CreateOrReplaceRelationshipAsync <BasicRelationship>("buildingTwinId", "buildingFloorRelationshipId", buildingFloorRelationshipPayload); Console.WriteLine($"Created a digital twin relationship '{createBuildingFloorRelationshipResponse.Value.Id}' " + $"from twin '{createBuildingFloorRelationshipResponse.Value.SourceId}' to twin '{createBuildingFloorRelationshipResponse.Value.TargetId}'."); #endregion Snippet:DigitalTwinsSampleCreateBasicRelationship // You can get a relationship as a BasicRelationship type. #region Snippet:DigitalTwinsSampleGetBasicRelationship Response <BasicRelationship> getBasicRelationshipResponse = await client.GetRelationshipAsync <BasicRelationship>( "buildingTwinId", "buildingFloorRelationshipId"); if (getBasicRelationshipResponse.GetRawResponse().Status == (int)HttpStatusCode.OK) { BasicRelationship basicRelationship = getBasicRelationshipResponse.Value; Console.WriteLine($"Retrieved relationship '{basicRelationship.Id}' from twin {basicRelationship.SourceId}.\n\t" + $"Prop1: {basicRelationship.Properties["Prop1"]}\n\t" + $"Prop2: {basicRelationship.Properties["Prop2"]}"); } #endregion Snippet:DigitalTwinsSampleGetBasicRelationship // Alternatively, you can create your own custom data types and use these types when calling into the APIs. // 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 }; Response <CustomRelationship> createCustomRelationshipResponse = await client .CreateOrReplaceRelationshipAsync <CustomRelationship>("floorTwinId", "floorBuildingRelationshipId", floorBuildingRelationshipPayload); Console.WriteLine($"Created a digital twin relationship '{createCustomRelationshipResponse.Value.Id}' " + $"from twin '{createCustomRelationshipResponse.Value.SourceId}' to twin '{createCustomRelationshipResponse.Value.TargetId}'."); #endregion Snippet:DigitalTwinsSampleCreateCustomRelationship // Getting a relationship as a custom data type is extremely easy. #region Snippet:DigitalTwinsSampleGetCustomRelationship Response <CustomRelationship> getCustomRelationshipResponse = await client.GetRelationshipAsync <CustomRelationship>( "floorTwinId", "floorBuildingRelationshipId"); CustomRelationship getCustomRelationship = getCustomRelationshipResponse.Value; Console.WriteLine($"Retrieved and deserialized relationship '{getCustomRelationship.Id}' from twin '{getCustomRelationship.SourceId}'.\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 <BasicRelationship> relationships = client.GetRelationshipsAsync <BasicRelationship>("buildingTwinId"); await foreach (BasicRelationship relationship in relationships) { Console.WriteLine($"Retrieved relationship '{relationship.Id}' with source {relationship.SourceId}' and " + $"target {relationship.TargetId}.\n\t" + $"Prop1: {relationship.Properties["Prop1"]}\n\t" + $"Prop2: {relationship.Properties["Prop2"]}"); } #endregion Snippet:DigitalTwinsSampleGetAllRelationships // Get all incoming relationships in the graph where buildingTwinId is the target of the relationship. #region Snippet:DigitalTwinsSampleGetIncomingRelationships AsyncPageable <IncomingRelationship> incomingRelationships = client.GetIncomingRelationshipsAsync("buildingTwinId"); await foreach (IncomingRelationship incomingRelationship in incomingRelationships) { Console.WriteLine($"Found an incoming relationship '{incomingRelationship.RelationshipId}' from '{incomingRelationship.SourceId}'."); } #endregion Snippet:DigitalTwinsSampleGetIncomingRelationships // Delete the contains relationship, created earlier in the sample code, from building to floor. #region Snippet:DigitalTwinsSampleDeleteRelationship await client.DeleteRelationshipAsync("buildingTwinId", "buildingFloorRelationshipId"); Console.WriteLine($"Deleted relationship 'buildingFloorRelationshipId'."); #endregion Snippet:DigitalTwinsSampleDeleteRelationship // Delete the containedIn relationship, created earlier in the sample code, from floor to building. await client.DeleteRelationshipAsync("floorTwinId", "floorBuildingRelationshipId"); Console.WriteLine($"Deleted relationship 'floorBuildingRelationshipId'."); // Clean up. try { // Delete all twins await client.DeleteDigitalTwinAsync("buildingTwinId"); await client.DeleteDigitalTwinAsync("floorTwinId"); } catch (RequestFailedException ex) { Console.WriteLine($"Failed to delete twin due to {ex}."); } try { await client.DeleteModelAsync(sampleBuildingModelId); await client.DeleteModelAsync(sampleFloorModelId); } catch (RequestFailedException ex) { Console.WriteLine($"Failed to delete model due to {ex}."); } }
public async Task Component_UpdateComponentFailsWhenIfMatchHeaderOutOfDate() { // arrange DigitalTwinsClient client = GetClient(); string wifiModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.WifiModelIdPrefix).ConfigureAwait(false); string roomWithWifiModelId = await GetUniqueModelIdAsync(client, TestAssetDefaults.RoomWithWifiModelIdPrefix).ConfigureAwait(false); string roomWithWifiTwinId = await GetUniqueTwinIdAsync(client, TestAssetDefaults.RoomWithWifiTwinIdPrefix).ConfigureAwait(false); string wifiComponentName = "wifiAccessPoint"; try { // CREATE // create roomWithWifi model string wifiModel = TestAssetsHelper.GetWifiModelPayload(wifiModelId); // create wifi model string roomWithWifiModel = TestAssetsHelper.GetRoomWithWifiModelPayload(roomWithWifiModelId, wifiModelId, wifiComponentName); await CreateAndListModelsAsync(client, new List <string> { roomWithWifiModel, wifiModel }).ConfigureAwait(false); // create room digital twin BasicDigitalTwin roomWithWifiTwin = TestAssetsHelper.GetRoomWithWifiTwinPayload(roomWithWifiModelId, wifiComponentName); await client.CreateOrReplaceDigitalTwinAsync <BasicDigitalTwin>(roomWithWifiTwinId, roomWithWifiTwin); // Get the component Response <object> getComponentResponse = await client .GetComponentAsync <object>( roomWithWifiTwinId, wifiComponentName) .ConfigureAwait(false); ETag?etagBeforeUpdate = (await client.GetDigitalTwinAsync <BasicDigitalTwin>(roomWithWifiTwinId)).Value.ETag; // Patch component JsonPatchDocument componentUpdatePatchDocument = new JsonPatchDocument(); componentUpdatePatchDocument.AppendReplace("/Network", "New Network"); Response updateComponentResponse = await client .UpdateComponentAsync( roomWithWifiTwinId, wifiComponentName, componentUpdatePatchDocument) .ConfigureAwait(false); // Patch component again, but with the now out of date ETag JsonPatchDocument secondComponentUpdatePatchDocument = new JsonPatchDocument(); secondComponentUpdatePatchDocument.AppendReplace("/Network", "Even newer Network"); Func <Task> act = async() => { await client .UpdateComponentAsync( roomWithWifiTwinId, wifiComponentName, secondComponentUpdatePatchDocument, etagBeforeUpdate) .ConfigureAwait(false); }; act.Should().Throw <RequestFailedException>() .And.Status.Should().Be((int)HttpStatusCode.PreconditionFailed); } catch (Exception ex) { Assert.Fail($"Failure in executing a step in the test case: {ex.Message}."); } finally { // clean up try { if (!string.IsNullOrWhiteSpace(roomWithWifiTwinId)) { await client.DeleteDigitalTwinAsync(roomWithWifiTwinId).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(roomWithWifiModelId)) { await client.DeleteModelAsync(roomWithWifiModelId).ConfigureAwait(false); } if (!string.IsNullOrWhiteSpace(wifiModelId)) { await client.DeleteModelAsync(wifiModelId).ConfigureAwait(false); } } catch (Exception ex) { Assert.Fail($"Test clean up failed: {ex.Message}"); } } }