< Summary

Class:Azure.Iot.Hub.Service.DevicesClient
Assembly:Azure.Iot.Hub.Service
File(s):C:\Git\azure-sdk-for-net\sdk\iot\Azure.Iot.Hub.Service\src\DevicesClient.cs
Covered lines:161
Uncovered lines:43
Coverable lines:204
Total lines:624
Line coverage:78.9% (161 of 204)
Covered branches:18
Total branches:44
Branch coverage:40.9% (18 of 44)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor()-100%100%
.ctor(...)-100%100%
CreateOrUpdateIdentityAsync(...)-100%100%
CreateOrUpdateIdentity(...)-100%100%
GetIdentityAsync(...)-100%100%
GetIdentity(...)-100%100%
DeleteIdentityAsync(...)-100%100%
DeleteIdentity(...)-100%100%
CreateIdentitiesWithTwinAsync(...)-100%50%
CreateIdentitiesWithTwin(...)-100%50%
CreateIdentitiesAsync(...)-100%50%
CreateIdentities(...)-100%50%
UpdateIdentitiesAsync(...)-100%50%
UpdateIdentities(...)-100%50%
DeleteIdentitiesAsync(...)-100%50%
DeleteIdentities(...)-100%50%
<GetTwinsAsync()-100%50%
<GetTwinsAsync()-0%0%
GetTwinsAsync(...)-100%100%
GetTwins(...)-100%100%
GetTwinAsync(...)-100%100%
GetTwin(...)-100%100%
UpdateTwinAsync(...)-100%100%
UpdateTwin(...)-100%100%
UpdateTwinsAsync(...)-0%0%
UpdateTwins(...)-0%0%
InvokeMethodAsync(...)-0%100%
InvokeMethod(...)-0%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\iot\Azure.Iot.Hub.Service\src\DevicesClient.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Globalization;
 7using System.Linq;
 8using System.Threading;
 9using System.Threading.Tasks;
 10using Azure.Core;
 11using Azure.Iot.Hub.Service.Models;
 12
 13namespace Azure.Iot.Hub.Service
 14{
 15    /// <summary>
 16    /// Devices client to interact with devices and device twins including CRUD operations and method invocations.
 17    /// See <see href="https://docs.microsoft.com/en-us/azure/iot-hub/iot-hub-csharp-csharp-twin-getstarted"> Getting st
 18    /// </summary>
 19    public class DevicesClient
 20    {
 21        private const string ContinuationTokenHeader = "x-ms-continuation";
 22        private const string HubDeviceQuery = "select * from devices";
 23
 24        private readonly DevicesRestClient _devicesRestClient;
 25        private readonly QueryRestClient _queryRestClient;
 26
 27        /// <summary>
 28        /// Initializes a new instance of DevicesClient.
 29        /// </summary>
 19230        protected DevicesClient()
 31        {
 19232        }
 33
 34        /// <summary>
 35        /// Initializes a new instance of DevicesClient.
 36        /// <param name="devicesRestClient"> The REST client to perform device, device twin, and bulk operations. </para
 37        /// <param name="queryRestClient"> The REST client to perform query operations for the device. </param>
 38        /// </summary>
 7639        internal DevicesClient(DevicesRestClient devicesRestClient, QueryRestClient queryRestClient)
 40        {
 7641            Argument.AssertNotNull(devicesRestClient, nameof(devicesRestClient));
 7642            Argument.AssertNotNull(queryRestClient, nameof(queryRestClient));
 43
 7644            _devicesRestClient = devicesRestClient;
 7645            _queryRestClient = queryRestClient;
 7646        }
 47
 48        /// <summary>
 49        /// Create or update a device identity.
 50        /// </summary>
 51        /// <param name="deviceIdentity">the device identity to create or update.</param>
 52        /// <param name="precondition">The condition on which to perform this operation.
 53        /// In case of create, the condition must be equal to <see cref="IfMatchPrecondition.IfMatch"/>.
 54        /// In case of update, if no ETag is present on the device, then the condition must be equal to <see cref="IfMat
 55        /// </param>
 56        /// <param name="cancellationToken">The cancellation token.</param>
 57        /// <returns>The created device identity and the http response <see cref="Response{T}"/>.</returns>
 58        /// <code snippet="Snippet:IotHubCreateDeviceIdentity">
 59        /// Response&lt;DeviceIdentity&gt; response = await IoTHubServiceClient.Devices.CreateOrUpdateIdentityAsync(devi
 60        ///
 61        /// SampleLogger.PrintSuccess($&quot;Successfully create a new device identity with Id: &apos;{response.Value.De
 62        /// </code>
 63        /// <code snippet="Snippet:IotHubUpdateDeviceIdentity">
 64        /// Response&lt;DeviceIdentity&gt; getResponse = await IoTHubServiceClient.Devices.GetIdentityAsync(deviceId);
 65        ///
 66        /// DeviceIdentity deviceIdentity = getResponse.Value;
 67        /// Console.WriteLine($&quot;Current device identity: DeviceId: &apos;{deviceIdentity.DeviceId}&apos;, Status: &
 68        ///
 69        /// Console.WriteLine($&quot;Updating device identity with Id: &apos;{deviceIdentity.DeviceId}&apos;. Disabling 
 70        /// deviceIdentity.Status = DeviceStatus.Disabled;
 71        ///
 72        /// Response&lt;DeviceIdentity&gt; response = await IoTHubServiceClient.Devices.CreateOrUpdateIdentityAsync(devi
 73        ///
 74        /// DeviceIdentity updatedDevice = response.Value;
 75        ///
 76        /// SampleLogger.PrintSuccess($&quot;Successfully updated device identity: DeviceId: &apos;{updatedDevice.Device
 77        /// </code>
 78        public virtual Task<Response<DeviceIdentity>> CreateOrUpdateIdentityAsync(
 79            DeviceIdentity deviceIdentity,
 80            IfMatchPrecondition precondition = IfMatchPrecondition.IfMatch,
 81            CancellationToken cancellationToken = default)
 82        {
 3683            Argument.AssertNotNull(deviceIdentity, nameof(deviceIdentity));
 3684            string ifMatchHeaderValue = IfMatchPreconditionExtensions.GetIfMatchHeaderValue(precondition, deviceIdentity
 3685            return _devicesRestClient.CreateOrUpdateIdentityAsync(deviceIdentity.DeviceId, deviceIdentity, ifMatchHeader
 86        }
 87
 88        /// <summary>
 89        /// Create or update a device identity.
 90        /// </summary>
 91        /// <param name="deviceIdentity">the device identity to create or update.</param>
 92        /// <param name="precondition">The condition on which to perform this operation.
 93        /// In case of create, the condition must be equal to <see cref="IfMatchPrecondition.IfMatch"/>.
 94        /// In case of update, if no ETag is present on the device, then the condition must be equal to <see cref="IfMat
 95        /// </param>
 96        /// <param name="cancellationToken">The cancellation token.</param>
 97        /// <returns>The created device identity and the http response <see cref="Response{T}"/>.</returns>
 98        public virtual Response<DeviceIdentity> CreateOrUpdateIdentity(
 99            DeviceIdentity deviceIdentity,
 100            IfMatchPrecondition precondition = IfMatchPrecondition.IfMatch,
 101            CancellationToken cancellationToken = default)
 102        {
 36103            Argument.AssertNotNull(deviceIdentity, nameof(deviceIdentity));
 36104            string ifMatchHeaderValue = IfMatchPreconditionExtensions.GetIfMatchHeaderValue(precondition, deviceIdentity
 36105            return _devicesRestClient.CreateOrUpdateIdentity(deviceIdentity.DeviceId, deviceIdentity, ifMatchHeaderValue
 106        }
 107
 108        /// <summary>
 109        /// Get a single device identity.
 110        /// </summary>
 111        /// <param name="deviceId">The unique identifier of the device identity to get.</param>
 112        /// <param name="cancellationToken">The cancellation token.</param>
 113        /// <returns>The retrieved device identity and the http response <see cref="Response{T}"/>.</returns>
 114        /// <code snippet="Snippet:IotHubGetDeviceIdentity">
 115        /// Response&lt;DeviceIdentity&gt; response = await IoTHubServiceClient.Devices.GetIdentityAsync(deviceId);
 116        ///
 117        /// DeviceIdentity deviceIdentity = response.Value;
 118        ///
 119        /// SampleLogger.PrintSuccess($&quot;\t- Device Id: &apos;{deviceIdentity.DeviceId}&apos;, ETag: &apos;{deviceId
 120        /// </code>
 121        public virtual Task<Response<DeviceIdentity>> GetIdentityAsync(string deviceId, CancellationToken cancellationTo
 122        {
 6123            return _devicesRestClient.GetDeviceAsync(deviceId, cancellationToken);
 124        }
 125
 126        /// <summary>
 127        /// Get a single device identity.
 128        /// </summary>
 129        /// <param name="deviceId">The unique identifier of the device identity to get.</param>
 130        /// <param name="cancellationToken">The cancellation token.</param>
 131        /// <returns>The retrieved device identity and the http response <see cref="Response{T}"/>.</returns>
 132        public virtual Response<DeviceIdentity> GetIdentity(string deviceId, CancellationToken cancellationToken = defau
 133        {
 6134            return _devicesRestClient.GetDevice(deviceId, cancellationToken);
 135        }
 136
 137        /// <summary>
 138        /// Delete a single device identity.
 139        /// </summary>
 140        /// <param name="deviceIdentity">the device identity to delete. If no ETag is present on the device, then the co
 141        /// <param name="precondition">The condition on which to delete the device.</param>
 142        /// <param name="cancellationToken">The cancellation token.</param>
 143        /// <returns>The http response <see cref="Response{T}"/>.</returns>
 144        /// <code snippet="Snippet:IotHubDeleteDeviceIdentity">
 145        /// Response response = await IoTHubServiceClient.Devices.DeleteIdentityAsync(deviceIdentity);
 146        ///
 147        /// SampleLogger.PrintSuccess($&quot;Successfully deleted device identity with Id: &apos;{deviceIdentity.DeviceI
 148        /// </code>
 149        public virtual Task<Response> DeleteIdentityAsync(
 150            DeviceIdentity deviceIdentity,
 151            IfMatchPrecondition precondition = IfMatchPrecondition.IfMatch,
 152            CancellationToken cancellationToken = default)
 153        {
 24154            Argument.AssertNotNull(deviceIdentity, nameof(deviceIdentity));
 24155            string ifMatchHeaderValue = IfMatchPreconditionExtensions.GetIfMatchHeaderValue(precondition, deviceIdentity
 24156            return _devicesRestClient.DeleteIdentityAsync(deviceIdentity.DeviceId, ifMatchHeaderValue, cancellationToken
 157        }
 158
 159        /// <summary>
 160        /// Delete a single device identity.
 161        /// </summary>
 162        /// <param name="deviceIdentity">the device identity to delete. If no ETag is present on the device, then the co
 163        /// <param name="precondition">The condition on which to delete the device.</param>
 164        /// <param name="cancellationToken">The cancellation token.</param>
 165        /// <returns>The http response <see cref="Response{T}"/>.</returns>
 166        public virtual Response DeleteIdentity(
 167            DeviceIdentity deviceIdentity,
 168            IfMatchPrecondition precondition = IfMatchPrecondition.IfMatch,
 169            CancellationToken cancellationToken = default)
 170        {
 24171            Argument.AssertNotNull(deviceIdentity, nameof(deviceIdentity));
 24172            string ifMatchHeaderValue = IfMatchPreconditionExtensions.GetIfMatchHeaderValue(precondition, deviceIdentity
 24173            return _devicesRestClient.DeleteIdentity(deviceIdentity.DeviceId, ifMatchHeaderValue, cancellationToken);
 174        }
 175
 176        /// <summary>
 177        /// Create multiple devices with an initial twin. A maximum of 100 creations can be done per call, and each crea
 178        /// </summary>
 179        /// <param name="devices">The pairs of devices and their twins that will be created. For fields such as deviceId
 180        /// where device and twin have a definition, the device value will override the twin value.</param>
 181        /// <param name="cancellationToken">The cancellation token.</param>
 182        /// <returns>The result of the bulk operation and the http response <see cref="Response{T}"/>.</returns>
 183        public virtual Task<Response<BulkRegistryOperationResponse>> CreateIdentitiesWithTwinAsync(IDictionary<DeviceIde
 184        {
 2185            IEnumerable<ExportImportDevice> registryOperations = devices
 22186                .Select(x => new ExportImportDevice()
 22187                {
 22188                    Id = x.Key.DeviceId,
 22189                    Authentication = x.Key.Authentication,
 22190                    Capabilities = x.Key.Capabilities,
 22191                    DeviceScope = x.Key.DeviceScope,
 22192                    Status = string.Equals(ExportImportDeviceStatus.Disabled.ToString(), x.Key.Status?.ToString(), Strin
 22193                                ? ExportImportDeviceStatus.Disabled
 22194                                : ExportImportDeviceStatus.Enabled,
 22195                    StatusReason = x.Key.StatusReason,
 22196                    ImportMode = ExportImportDeviceImportMode.Create
 22197                }.WithTags(x.Value.Tags).WithPropertiesFrom(x.Value.Properties).WithParentScopes(x.Key.ParentScopes));
 198
 2199            return _devicesRestClient.BulkRegistryOperationsAsync(registryOperations, cancellationToken);
 200        }
 201
 202        /// <summary>
 203        /// Create multiple devices with an initial twin. A maximum of 100 creations can be done per call, and each crea
 204        /// </summary>
 205        /// <param name="devices">The pairs of devices and their twins that will be created. For fields such as deviceId
 206        /// where device and twin have a definition, the device value will override the twin value.</param>
 207        /// <param name="cancellationToken">The cancellation token.</param>
 208        /// <returns>The result of the bulk operation and the http response <see cref="Response{T}"/>.</returns>
 209        public virtual Response<BulkRegistryOperationResponse> CreateIdentitiesWithTwin(IDictionary<DeviceIdentity, Twin
 210        {
 2211            IEnumerable<ExportImportDevice> registryOperations = devices
 22212                .Select(x => new ExportImportDevice()
 22213                {
 22214                    Id = x.Key.DeviceId,
 22215                    Authentication = x.Key.Authentication,
 22216                    Capabilities = x.Key.Capabilities,
 22217                    DeviceScope = x.Key.DeviceScope,
 22218                    Status = string.Equals(ExportImportDeviceStatus.Disabled.ToString(), x.Key.Status?.ToString(), Strin
 22219                                ? ExportImportDeviceStatus.Disabled
 22220                                : ExportImportDeviceStatus.Enabled,
 22221                    StatusReason = x.Key.StatusReason,
 22222                    ImportMode = ExportImportDeviceImportMode.Create
 22223                }.WithTags(x.Value.Tags).WithPropertiesFrom(x.Value.Properties).WithParentScopes(x.Key.ParentScopes));
 224
 2225            return _devicesRestClient.BulkRegistryOperations(registryOperations, cancellationToken);
 226        }
 227
 228        /// <summary>
 229        /// Create multiple devices. A maximum of 100 creations can be done per call, and each device identity must be u
 230        /// </summary>
 231        /// <param name="deviceIdentities">The device identities to create.</param>
 232        /// <param name="cancellationToken">The cancellation token.</param>
 233        /// <returns>The result of the bulk operation and the http response <see cref="Response{T}"/>.</returns>
 234        public virtual Task<Response<BulkRegistryOperationResponse>> CreateIdentitiesAsync(IEnumerable<DeviceIdentity> d
 235        {
 6236            IEnumerable<ExportImportDevice> registryOperations = deviceIdentities
 66237                .Select(x => new ExportImportDevice()
 66238                {
 66239                    Id = x.DeviceId,
 66240                    Authentication = x.Authentication,
 66241                    Capabilities = x.Capabilities,
 66242                    DeviceScope = x.DeviceScope,
 66243                    Status = string.Equals(ExportImportDeviceStatus.Disabled.ToString(), x.Status?.ToString(), StringCom
 66244                                ? ExportImportDeviceStatus.Disabled
 66245                                : ExportImportDeviceStatus.Enabled,
 66246                    StatusReason = x.StatusReason,
 66247                    ImportMode = ExportImportDeviceImportMode.Create
 66248                }.WithParentScopes(x.ParentScopes));
 249
 6250            return _devicesRestClient.BulkRegistryOperationsAsync(registryOperations, cancellationToken);
 251        }
 252
 253        /// <summary>
 254        /// Create multiple devices. A maximum of 100 creations can be done per call, and each device identity must be u
 255        /// </summary>
 256        /// <param name="deviceIdentities">The device identities to create.</param>
 257        /// <param name="cancellationToken">The cancellation token.</param>
 258        /// <returns>The result of the bulk operation and the http response <see cref="Response{T}"/>.</returns>
 259        public virtual Response<BulkRegistryOperationResponse> CreateIdentities(IEnumerable<DeviceIdentity> deviceIdenti
 260        {
 6261            IEnumerable<ExportImportDevice> registryOperations = deviceIdentities
 66262                .Select(x => new ExportImportDevice()
 66263                {
 66264                    Id = x.DeviceId,
 66265                    Authentication = x.Authentication,
 66266                    Capabilities = x.Capabilities,
 66267                    DeviceScope = x.DeviceScope,
 66268                    Status = string.Equals(ExportImportDeviceStatus.Disabled.ToString(), x.Status?.ToString(), StringCom
 66269                                ? ExportImportDeviceStatus.Disabled
 66270                                : ExportImportDeviceStatus.Enabled,
 66271                    StatusReason = x.StatusReason,
 66272                    ImportMode = ExportImportDeviceImportMode.Create
 66273                }.WithParentScopes(x.ParentScopes));
 274
 6275            return _devicesRestClient.BulkRegistryOperations(registryOperations, cancellationToken);
 276        }
 277
 278        /// <summary>
 279        /// Update multiple devices. A maximum of 100 updates can be done per call, and each operation must be done on a
 280        /// </summary>
 281        /// <param name="deviceIdentities">The devices to update.</param>
 282        /// <param name="precondition">The condition on which to update each device identity.</param>
 283        /// <param name="cancellationToken">The cancellation token.</param>
 284        /// <returns>The result of the bulk operation and the http response <see cref="Response{T}"/>.</returns>
 285        public virtual Task<Response<BulkRegistryOperationResponse>> UpdateIdentitiesAsync(
 286            IEnumerable<DeviceIdentity> deviceIdentities,
 287            BulkIfMatchPrecondition precondition = BulkIfMatchPrecondition.IfMatch,
 288            CancellationToken cancellationToken = default)
 289        {
 2290            IEnumerable<ExportImportDevice> registryOperations = deviceIdentities
 6291                .Select(x => new ExportImportDevice()
 6292                {
 6293                    Id = x.DeviceId,
 6294                    Authentication = x.Authentication,
 6295                    Capabilities = x.Capabilities,
 6296                    DeviceScope = x.DeviceScope,
 6297                    ETag = x.Etag,
 6298                    Status = string.Equals(ExportImportDeviceStatus.Disabled.ToString(), x.Status?.ToString(), StringCom
 6299                                ? ExportImportDeviceStatus.Disabled
 6300                                : ExportImportDeviceStatus.Enabled,
 6301                    StatusReason = x.StatusReason,
 6302                    ImportMode = precondition == BulkIfMatchPrecondition.Unconditional ? ExportImportDeviceImportMode.Up
 6303                }.WithParentScopes(x.ParentScopes));
 304
 2305            return _devicesRestClient.BulkRegistryOperationsAsync(registryOperations, cancellationToken);
 306        }
 307
 308        /// <summary>
 309        /// Update multiple devices. A maximum of 100 updates can be done per call, and each operation must be done on a
 310        /// </summary>
 311        /// <param name="deviceIdentities">The devices to update.</param>
 312        /// <param name="precondition">The condition on which to update each device identity.</param>
 313        /// <param name="cancellationToken">The cancellation token.</param>
 314        /// <returns>The result of the bulk operation and the http response <see cref="Response{T}"/>.</returns>
 315        public virtual Response<BulkRegistryOperationResponse> UpdateIdentities(
 316            IEnumerable<DeviceIdentity> deviceIdentities,
 317            BulkIfMatchPrecondition precondition = BulkIfMatchPrecondition.IfMatch,
 318            CancellationToken cancellationToken = default)
 319        {
 2320            IEnumerable<ExportImportDevice> registryOperations = deviceIdentities
 6321                .Select(x => new ExportImportDevice()
 6322                {
 6323                    Id = x.DeviceId,
 6324                    Authentication = x.Authentication,
 6325                    Capabilities = x.Capabilities,
 6326                    DeviceScope = x.DeviceScope,
 6327                    ETag = x.Etag,
 6328                    Status = string.Equals(ExportImportDeviceStatus.Disabled.ToString(), x.Status?.ToString(), StringCom
 6329                                ? ExportImportDeviceStatus.Disabled
 6330                                : ExportImportDeviceStatus.Enabled,
 6331                    StatusReason = x.StatusReason,
 6332                    ImportMode = precondition == BulkIfMatchPrecondition.Unconditional ? ExportImportDeviceImportMode.Up
 6333                }.WithParentScopes(x.ParentScopes));
 334
 2335            return _devicesRestClient.BulkRegistryOperations(registryOperations, cancellationToken);
 336        }
 337
 338        /// <summary>
 339        /// Delete multiple devices. A maximum of 100 deletions can be done per call. For larger scale operations, consi
 340        /// </summary>
 341        /// <param name="deviceIdentities">The devices to delete.</param>
 342        /// <param name="precondition">The condition on which to delete each device identity.</param>
 343        /// <param name="cancellationToken">The cancellation token.</param>
 344        /// <returns>The result of the bulk deletion and the http response <see cref="Response{T}"/>.</returns>
 345        public virtual Task<Response<BulkRegistryOperationResponse>> DeleteIdentitiesAsync(
 346            IEnumerable<DeviceIdentity> deviceIdentities,
 347            BulkIfMatchPrecondition precondition = BulkIfMatchPrecondition.IfMatch,
 348            CancellationToken cancellationToken = default)
 349        {
 10350            IEnumerable<ExportImportDevice> registryOperations = deviceIdentities
 94351                .Select(x => new ExportImportDevice()
 94352                {
 94353                    Id = x.DeviceId,
 94354                    ETag = x.Etag,
 94355                    ImportMode = precondition == BulkIfMatchPrecondition.Unconditional
 94356                        ? ExportImportDeviceImportMode.Delete
 94357                        : ExportImportDeviceImportMode.DeleteIfMatchETag
 94358                });
 359
 10360            return _devicesRestClient.BulkRegistryOperationsAsync(registryOperations, cancellationToken);
 361        }
 362
 363        /// <summary>
 364        /// Delete multiple devices. A maximum of 100 deletions can be done per call. For larger scale operations, consi
 365        /// </summary>
 366        /// <param name="deviceIdentities">The devices to delete.</param>
 367        /// <param name="precondition">The condition on which to delete each device identity.</param>
 368        /// <param name="cancellationToken">The cancellation token.</param>
 369        /// <returns>The result of the bulk deletion and the http response <see cref="Response{T}"/>.</returns>
 370        public virtual Response<BulkRegistryOperationResponse> DeleteIdentities(
 371            IEnumerable<DeviceIdentity> deviceIdentities,
 372            BulkIfMatchPrecondition precondition = BulkIfMatchPrecondition.IfMatch,
 373            CancellationToken cancellationToken = default)
 374        {
 10375            IEnumerable<ExportImportDevice> registryOperations = deviceIdentities
 94376                .Select(x => new ExportImportDevice()
 94377                {
 94378                    Id = x.DeviceId,
 94379                    ETag = x.Etag,
 94380                    ImportMode = precondition == BulkIfMatchPrecondition.Unconditional
 94381                        ? ExportImportDeviceImportMode.Delete
 94382                        : ExportImportDeviceImportMode.DeleteIfMatchETag
 94383                });
 384
 10385            return _devicesRestClient.BulkRegistryOperations(registryOperations, cancellationToken);
 386        }
 387
 388        /// <summary>
 389        /// List a set of device twins.
 390        /// </summary>
 391        /// <param name="pageSize">The size of each page to be retrieved from the service. Service may override this siz
 392        /// <param name="cancellationToken">The cancellation token.</param>
 393        /// <returns>A pageable set of device twins <see cref="AsyncPageable{T}"/>.</returns>
 394        public virtual AsyncPageable<TwinData> GetTwinsAsync(int? pageSize = null, CancellationToken cancellationToken =
 395        {
 396            async Task<Page<TwinData>> FirstPageFunc(int? pageSizeHint)
 397            {
 4398                var querySpecification = new QuerySpecification
 4399                {
 4400                    Query = HubDeviceQuery
 4401                };
 4402                Response<IReadOnlyList<TwinData>> response = await _queryRestClient.GetTwinsAsync(
 4403                    querySpecification,
 4404                    null,
 4405                    pageSizeHint?.ToString(CultureInfo.InvariantCulture),
 4406                    cancellationToken).ConfigureAwait(false);
 407
 4408                response.GetRawResponse().Headers.TryGetValue(ContinuationTokenHeader, out string continuationToken);
 409
 4410                return Page.FromValues(response.Value, continuationToken, response.GetRawResponse());
 4411            }
 412
 413            async Task<Page<TwinData>> NextPageFunc(string nextLink, int? pageSizeHint)
 414            {
 0415                var querySpecification = new QuerySpecification()
 0416                {
 0417                    Query = HubDeviceQuery
 0418                };
 0419                Response<IReadOnlyList<TwinData>> response = await _queryRestClient.GetTwinsAsync(
 0420                    querySpecification,
 0421                    nextLink,
 0422                    pageSizeHint?.ToString(CultureInfo.InvariantCulture),
 0423                    cancellationToken).ConfigureAwait(false);
 424
 0425                response.GetRawResponse().Headers.TryGetValue(ContinuationTokenHeader, out string continuationToken);
 0426                return Page.FromValues(response.Value, continuationToken, response.GetRawResponse());
 0427            }
 428
 4429            return PageableHelpers.CreateAsyncEnumerable(FirstPageFunc, NextPageFunc, pageSize);
 430        }
 431
 432        /// <summary>
 433        /// List a set of device twins.
 434        /// </summary>
 435        /// <param name="pageSize">The size of each page to be retrieved from the service. Service may override this siz
 436        /// <param name="cancellationToken">The cancellation token.</param>
 437        /// <returns>A pageable set of device twins <see cref="Pageable{T}"/>.</returns>
 438        public virtual Pageable<TwinData> GetTwins(int? pageSize = null, CancellationToken cancellationToken = default)
 439        {
 440            Page<TwinData> FirstPageFunc(int? pageSizeHint)
 441            {
 4442                var querySpecification = new QuerySpecification
 4443                {
 4444                    Query = HubDeviceQuery
 4445                };
 446
 4447                Response<IReadOnlyList<TwinData>> response = _queryRestClient.GetTwins(
 4448                    querySpecification,
 4449                    null,
 4450                    pageSizeHint?.ToString(CultureInfo.InvariantCulture),
 4451                    cancellationToken);
 452
 4453                response.GetRawResponse().Headers.TryGetValue(ContinuationTokenHeader, out string continuationToken);
 454
 4455                return Page.FromValues(response.Value, continuationToken, response.GetRawResponse());
 456            }
 457
 458            Page<TwinData> NextPageFunc(string nextLink, int? pageSizeHint)
 459            {
 0460                var querySpecification = new QuerySpecification()
 0461                {
 0462                    Query = HubDeviceQuery
 0463                };
 0464                Response<IReadOnlyList<TwinData>> response = _queryRestClient.GetTwins(
 0465                    querySpecification,
 0466                    nextLink,
 0467                    pageSizeHint?.ToString(CultureInfo.InvariantCulture),
 0468                    cancellationToken);
 469
 0470                response.GetRawResponse().Headers.TryGetValue(ContinuationTokenHeader, out string continuationToken);
 0471                return Page.FromValues(response.Value, continuationToken, response.GetRawResponse());
 472            }
 473
 4474            return PageableHelpers.CreateEnumerable(FirstPageFunc, NextPageFunc, pageSize);
 475        }
 476
 477        /// <summary>
 478        /// Get a device's twin.
 479        /// </summary>
 480        /// <param name="deviceId">The unique identifier of the device identity to get the twin of.</param>
 481        /// <param name="cancellationToken">The cancellation token.</param>
 482        /// <returns>The device's twin, including reported properties and desired properties and the http response <see 
 483        /// <code snippet="Snippet:IotHubGetDeviceTwin">
 484        /// Response&lt;TwinData&gt; response = await IoTHubServiceClient.Devices.GetTwinAsync(deviceId);
 485        ///
 486        /// SampleLogger.PrintSuccess($&quot;\t- Device Twin: DeviceId: &apos;{response.Value.DeviceId}&apos;, Status: &
 487        /// </code>
 488        public virtual Task<Response<TwinData>> GetTwinAsync(string deviceId, CancellationToken cancellationToken = defa
 489        {
 4490            return _devicesRestClient.GetTwinAsync(deviceId, cancellationToken);
 491        }
 492
 493        /// <summary>
 494        /// Get a device's twin.
 495        /// </summary>
 496        /// <param name="deviceId">The unique identifier of the device identity to get the twin of.</param>
 497        /// <param name="cancellationToken">The cancellation token.</param>
 498        /// <returns>The device's twin, including reported properties and desired properties.</returns>
 499        public virtual Response<TwinData> GetTwin(string deviceId, CancellationToken cancellationToken = default)
 500        {
 4501            return _devicesRestClient.GetTwin(deviceId, cancellationToken);
 502        }
 503
 504        /// <summary>
 505        /// Update a device's twin.
 506        /// </summary>
 507        /// <param name="twinUpdate">The properties to update. Any existing properties not referenced by this patch will
 508        /// <param name="precondition">The condition for which this operation will execute.</param>
 509        /// <param name="cancellationToken">The cancellation token.</param>
 510        /// <returns>The new representation of the device twin and the http response <see cref="Response{T}"/>.</returns
 511        /// <code snippet="Snippet:IotHubUpdateDeviceTwin">
 512        /// Response&lt;TwinData&gt; getResponse = await IoTHubServiceClient.Devices.GetTwinAsync(deviceId);
 513        /// TwinData deviceTwin = getResponse.Value;
 514        ///
 515        /// Console.WriteLine($&quot;Updating device twin: DeviceId: &apos;{deviceTwin.DeviceId}&apos;, ETag: &apos;{dev
 516        /// Console.WriteLine($&quot;Setting a new desired property {userPropName} to: &apos;{Environment.UserName}&apos
 517        ///
 518        /// deviceTwin.Properties.Desired.Add(new KeyValuePair&lt;string, object&gt;(userPropName, Environment.UserName)
 519        ///
 520        /// Response&lt;TwinData&gt; response = await IoTHubServiceClient.Devices.UpdateTwinAsync(deviceTwin);
 521        ///
 522        /// TwinData updatedTwin = response.Value;
 523        ///
 524        /// var userPropValue = (string)updatedTwin.Properties.Desired
 525        ///     .Where(p =&gt; p.Key == userPropName)
 526        ///     .First()
 527        ///     .Value;
 528        ///
 529        /// SampleLogger.PrintSuccess($&quot;Successfully updated device twin: DeviceId: &apos;{updatedTwin.DeviceId}&ap
 530        /// </code>
 531        public virtual Task<Response<TwinData>> UpdateTwinAsync(TwinData twinUpdate, IfMatchPrecondition precondition = 
 532        {
 2533            Argument.AssertNotNull(twinUpdate, nameof(twinUpdate));
 2534            string ifMatchHeaderValue = IfMatchPreconditionExtensions.GetIfMatchHeaderValue(precondition, twinUpdate.Eta
 2535            return _devicesRestClient.UpdateTwinAsync(twinUpdate.DeviceId, twinUpdate, ifMatchHeaderValue, cancellationT
 536        }
 537
 538        /// <summary>
 539        /// Update a device's twin.
 540        /// </summary>
 541        /// <param name="twinUpdate">The properties to update. Any existing properties not referenced by this patch will
 542        /// <param name="precondition">The condition for which this operation will execute.</param>
 543        /// <param name="cancellationToken">The cancellation token.</param>
 544        /// <returns>The new representation of the device twin and the http response <see cref="Response{T}"/>.</returns
 545        public virtual Response<TwinData> UpdateTwin(TwinData twinUpdate, IfMatchPrecondition precondition = IfMatchPrec
 546        {
 2547            Argument.AssertNotNull(twinUpdate, nameof(twinUpdate));
 2548            string ifMatchHeaderValue = IfMatchPreconditionExtensions.GetIfMatchHeaderValue(precondition, twinUpdate.Eta
 2549            return _devicesRestClient.UpdateTwin(twinUpdate.DeviceId, twinUpdate, ifMatchHeaderValue, cancellationToken)
 550        }
 551
 552        /// <summary>
 553        /// Update multiple devices' twins. A maximum of 100 updates can be done per call, and each operation must be do
 554        /// </summary>
 555        /// <param name="twinUpdates">The new twins to replace the twins on existing devices.</param>
 556        /// <param name="precondition">The condition on which to update each device twin.</param>
 557        /// <param name="cancellationToken">The cancellation token.</param>
 558        /// <returns>The result of the bulk operation and the http response <see cref="Response{T}"/>.</returns>
 559        public virtual Task<Response<BulkRegistryOperationResponse>> UpdateTwinsAsync(
 560            IEnumerable<TwinData> twinUpdates,
 561            BulkIfMatchPrecondition precondition = BulkIfMatchPrecondition.IfMatch,
 562            CancellationToken cancellationToken = default)
 563        {
 0564            IEnumerable<ExportImportDevice> registryOperations = twinUpdates
 0565                .Select(x => new ExportImportDevice()
 0566                {
 0567                    Id = x.DeviceId,
 0568                    TwinETag = x.Etag,
 0569                    ImportMode = precondition == BulkIfMatchPrecondition.Unconditional ? ExportImportDeviceImportMode.Up
 0570                }.WithTags(x.Tags).WithPropertiesFrom(x.Properties));
 571
 0572            return _devicesRestClient.BulkRegistryOperationsAsync(registryOperations, cancellationToken);
 573        }
 574
 575        /// <summary>
 576        /// Update multiple devices' twins. A maximum of 100 updates can be done per call, and each operation must be do
 577        /// </summary>
 578        /// <param name="twinUpdates">The new twins to replace the twins on existing devices.</param>
 579        /// <param name="precondition">The condition on which to update each device twin.</param>
 580        /// <param name="cancellationToken">The cancellation token.</param>
 581        /// <returns>The result of the bulk operation and the http response <see cref="Response{T}"/>.</returns>
 582        public virtual Response<BulkRegistryOperationResponse> UpdateTwins(
 583            IEnumerable<TwinData> twinUpdates,
 584            BulkIfMatchPrecondition precondition = BulkIfMatchPrecondition.IfMatch,
 585            CancellationToken cancellationToken = default)
 586        {
 0587            IEnumerable<ExportImportDevice> registryOperations = twinUpdates
 0588                .Select(x => new ExportImportDevice()
 0589                {
 0590                    Id = x.DeviceId,
 0591                    TwinETag = x.Etag,
 0592                    ImportMode = precondition == BulkIfMatchPrecondition.Unconditional
 0593                        ? ExportImportDeviceImportMode.UpdateTwin
 0594                        : ExportImportDeviceImportMode.UpdateTwinIfMatchETag
 0595                }.WithTags(x.Tags).WithPropertiesFrom(x.Properties));
 596
 0597            return _devicesRestClient.BulkRegistryOperations(registryOperations, cancellationToken);
 598        }
 599
 600        /// <summary>
 601        /// Invoke a method on a device.
 602        /// </summary>
 603        /// <param name="deviceId">The unique identifier of the device identity to invoke the method on.</param>
 604        /// <param name="directMethodRequest">The details of the method to invoke.</param>
 605        /// <param name="cancellationToken">The cancellation token.</param>
 606        /// <returns>The result of the method invocation and the http response <see cref="Response{T}"/>.</returns>
 607        public virtual Task<Response<CloudToDeviceMethodResponse>> InvokeMethodAsync(string deviceId, CloudToDeviceMetho
 608        {
 0609            return _devicesRestClient.InvokeMethodAsync(deviceId, directMethodRequest, cancellationToken);
 610        }
 611
 612        /// <summary>
 613        /// Invoke a method on a device.
 614        /// </summary>
 615        /// <param name="deviceId">The unique identifier of the device identity to invoke the method on.</param>
 616        /// <param name="directMethodRequest">The details of the method to invoke.</param>
 617        /// <param name="cancellationToken">The cancellation token.</param>
 618        /// <returns>The result of the method invocation and the http response <see cref="Response{T}"/>.</returns>
 619        public virtual Response<CloudToDeviceMethodResponse> InvokeMethod(string deviceId, CloudToDeviceMethodRequest di
 620        {
 0621            return _devicesRestClient.InvokeMethod(deviceId, directMethodRequest, cancellationToken);
 622        }
 623    }
 624}