iothub/service/src/Registry/RegistryManager.cs (2,463 lines of code) (raw):

// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Azure.Devices.Common; using Microsoft.Azure.Devices.Common.Exceptions; using Microsoft.Azure.Devices.Shared; using Newtonsoft.Json; using System.Diagnostics.CodeAnalysis; #if !NET451 using Azure; using Azure.Core; #endif namespace Microsoft.Azure.Devices { /// <summary> /// Contains methods that services can use to perform create, remove, update and delete operations on devices. /// </summary> /// <remarks> /// For more information, see <see href="https://github.com/Azure/azure-iot-sdk-csharp#iot-hub-service-sdk"/>. /// <para> /// This client creates lifetime long instances of <see cref="HttpClient"/> that are tied to the URI of the /// IoT hub specified, configure any proxy settings, and connection lease timeout. /// For that reason, the instances are not static and an application using this client /// should create and save it for all use. Repeated creation may cause /// <see href="https://docs.microsoft.com/azure/architecture/antipatterns/improper-instantiation/">socket exhaustion</see>. /// </para> /// </remarks> [SuppressMessage( "Naming", "CA1716:Identifiers should not match keywords", Justification = "Cannot change parameter names as it is considered a breaking change.")] public class RegistryManager : IDisposable { private const string AdminUriFormat = "/$admin/{0}?{1}"; private const string RequestUriFormat = "/devices/{0}?{1}"; private const string JobsUriFormat = "/jobs{0}?{1}"; private const string StatisticsUriFormat = "/statistics/devices?" + ClientApiVersionHelper.ApiVersionQueryString; private const string DevicesRequestUriFormat = "/devices/?top={0}&{1}"; private const string DevicesQueryUriFormat = "/devices/query?" + ClientApiVersionHelper.ApiVersionQueryString; private const string WildcardEtag = "*"; private const string ContinuationTokenHeader = "x-ms-continuation"; private const string PageSizeHeader = "x-ms-max-item-count"; private const string TwinUriFormat = "/twins/{0}?{1}"; private const string ModulesRequestUriFormat = "/devices/{0}/modules/{1}?{2}"; private const string ModulesOnDeviceRequestUriFormat = "/devices/{0}/modules?{1}"; private const string ModuleTwinUriFormat = "/twins/{0}/modules/{1}?{2}"; private const string ConfigurationRequestUriFormat = "/configurations/{0}?{1}"; private const string ConfigurationsRequestUriFormat = "/configurations/?top={0}&{1}"; private const string ApplyConfigurationOnDeviceUriFormat = "/devices/{0}/applyConfigurationContent?" + ClientApiVersionHelper.ApiVersionQueryString; private static readonly TimeSpan s_regexTimeoutMilliseconds = TimeSpan.FromMilliseconds(500); private static readonly Regex s_deviceIdRegex = new Regex( @"^[A-Za-z0-9\-:.+%_#*?!(),=@;$']{1,128}$", RegexOptions.Compiled | RegexOptions.IgnoreCase, s_regexTimeoutMilliseconds); private static readonly TimeSpan s_defaultOperationTimeout = TimeSpan.FromSeconds(100); private static readonly TimeSpan s_defaultGetDevicesOperationTimeout = TimeSpan.FromSeconds(120); private readonly string _iotHubName; private IHttpClientHelper _httpClientHelper; /// <summary> /// Creates an instance of RegistryManager, provided for unit testing purposes only. /// </summary> public RegistryManager() { } internal RegistryManager(IotHubConnectionProperties connectionProperties, HttpTransportSettings transportSettings) { _iotHubName = connectionProperties.IotHubName; _httpClientHelper = new HttpClientHelper( connectionProperties.HttpsEndpoint, connectionProperties, ExceptionHandlingHelper.GetDefaultErrorMapping(), s_defaultOperationTimeout, transportSettings.Proxy, transportSettings.ConnectionLeaseTimeoutMilliseconds); } // internal test helper internal RegistryManager(string iotHubName, IHttpClientHelper httpClientHelper) { _iotHubName = iotHubName; _httpClientHelper = httpClientHelper ?? throw new ArgumentNullException(nameof(httpClientHelper)); } /// <summary> /// Creates RegistryManager from an IoT hub connection string. /// </summary> /// <param name="connectionString">The IoT hub connection string.</param> /// <returns>A RegistryManager instance.</returns> public static RegistryManager CreateFromConnectionString(string connectionString) { return CreateFromConnectionString(connectionString, new HttpTransportSettings()); } /// <summary> /// Creates an instance of RegistryManager, authenticating using an IoT hub connection string, and specifying /// HTTP transport settings. /// </summary> /// <param name="connectionString">The IoT hub connection string.</param> /// <param name="transportSettings">The HTTP transport settings.</param> /// <returns>A RegistryManager instance.</returns> public static RegistryManager CreateFromConnectionString(string connectionString, HttpTransportSettings transportSettings) { if (transportSettings == null) { throw new ArgumentNullException(nameof(transportSettings), "The HTTP transport settings cannot be null."); } TlsVersions.Instance.SetLegacyAcceptableVersions(); var iotHubConnectionString = IotHubConnectionString.Parse(connectionString); return new RegistryManager(iotHubConnectionString, transportSettings); } #if !NET451 /// <summary> /// Creates RegistryManager, authenticating using an identity in Azure Active Directory (AAD). /// </summary> /// <remarks> /// For more about information on the options of authenticating using a derived instance of <see cref="TokenCredential"/>, see /// <see href="https://docs.microsoft.com/dotnet/api/overview/azure/identity-readme"/>. /// For more information on configuring IoT hub with Azure Active Directory, see /// <see href="https://docs.microsoft.com/azure/iot-hub/iot-hub-dev-guide-azure-ad-rbac"/> /// </remarks> /// <param name="hostName">IoT hub host name.</param> /// <param name="credential">Azure Active Directory (AAD) credentials to authenticate with IoT hub.</param> /// <param name="transportSettings">The HTTP transport settings.</param> /// <returns>A RegistryManager instance.</returns> public static RegistryManager Create( string hostName, TokenCredential credential, HttpTransportSettings transportSettings = default) { return Create(hostName, credential, CommonConstants.IotHubAadTokenScopes, transportSettings); } /// <summary> /// Creates RegistryManager, authenticating using an identity in Azure Active Directory (AAD). /// </summary> /// <remarks> /// For more about information on the options of authenticating using a derived instance of <see cref="TokenCredential"/>, see /// <see href="https://docs.microsoft.com/dotnet/api/overview/azure/identity-readme"/>. /// For more information on configuring IoT hub with Azure Active Directory, see /// <see href="https://docs.microsoft.com/azure/iot-hub/iot-hub-dev-guide-azure-ad-rbac"/> /// </remarks> /// <param name="hostName">IoT hub host name.</param> /// <param name="credential">Azure Active Directory (AAD) credentials to authenticate with IoT hub.</param> /// <param name="transportSettings">The HTTP transport settings.</param> /// <param name="scopes">The custom scopes to use when authenticating.</param> /// <returns>A RegistryManager instance.</returns> public static RegistryManager Create( string hostName, TokenCredential credential, string[] scopes, HttpTransportSettings transportSettings = default) { if (string.IsNullOrEmpty(hostName)) { throw new ArgumentNullException($"{nameof(hostName)}, Parameter cannot be null or empty"); } if (credential == null) { throw new ArgumentNullException($"{nameof(credential)}, Parameter cannot be null"); } var tokenCredentialProperties = new IotHubTokenCrendentialProperties(hostName, credential, scopes); return new RegistryManager(tokenCredentialProperties, transportSettings ?? new HttpTransportSettings()); } /// <summary> /// Creates RegistryManager using a shared access signature provided and refreshed as necessary by the caller. /// </summary> /// <remarks> /// Users may wish to build their own shared access signature (SAS) tokens rather than give the shared key to the SDK and let it manage signing and renewal. /// The <see cref="AzureSasCredential"/> object gives the SDK access to the SAS token, while the caller can update it as necessary using the /// <see cref="AzureSasCredential.Update(string)"/> method. /// </remarks> /// <param name="hostName">IoT hub host name.</param> /// <param name="credential">Credential that generates a SAS token to authenticate with IoT hub. See <see cref="AzureSasCredential"/>.</param> /// <param name="transportSettings">The HTTP transport settings.</param> /// <returns>A RegistryManager instance.</returns> public static RegistryManager Create( string hostName, AzureSasCredential credential, HttpTransportSettings transportSettings = default) { if (string.IsNullOrEmpty(hostName)) { throw new ArgumentNullException($"{nameof(hostName)}, Parameter cannot be null or empty"); } if (credential == null) { throw new ArgumentNullException($"{nameof(credential)}, Parameter cannot be null"); } var sasCredentialProperties = new IotHubSasCredentialProperties(hostName, credential); return new RegistryManager(sasCredentialProperties, transportSettings ?? new HttpTransportSettings()); } #endif /// <inheritdoc /> public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// <summary> /// Releases unmanaged and - optionally - managed resources. /// </summary> /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param> protected virtual void Dispose(bool disposing) { if (disposing && _httpClientHelper != null) { _httpClientHelper.Dispose(); _httpClientHelper = null; } } /// <summary> /// Explicitly open the RegistryManager instance. /// </summary> public virtual Task OpenAsync() { return TaskHelpers.CompletedTask; } /// <summary> /// Closes the RegistryManager instance and disposes its resources. /// </summary> public virtual Task CloseAsync() { return TaskHelpers.CompletedTask; } /// <summary> /// Register a new device with the system /// </summary> /// <param name="device">The Device object being registered.</param> /// <returns>The Device object with the generated keys and ETags.</returns> public virtual Task<Device> AddDeviceAsync(Device device) { return AddDeviceAsync(device, CancellationToken.None); } /// <summary> /// Register a new device with the system /// </summary> /// <param name="device">The Device object being registered.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Device object with the generated keys and ETags.</returns> public virtual Task<Device> AddDeviceAsync(Device device, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Adding device {device?.Id} with type {device?.Authentication?.Type}", nameof(AddDeviceAsync)); try { EnsureInstanceNotClosed(); ValidateDeviceId(device); if (!string.IsNullOrEmpty(device.ETag)) { throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); } ValidateDeviceAuthentication(device.Authentication, device.Id); NormalizeDevice(device); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.PreconditionFailed, async responseMessage => new PreconditionFailedException( await ExceptionHandlingHelper .GetExceptionMessageAsync(responseMessage) .ConfigureAwait(false)) } }; return _httpClientHelper.PutAsync(GetRequestUri(device.Id), device, PutOperationType.CreateEntity, errorMappingOverrides, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(AddDeviceAsync)} threw an exception: {ex}", nameof(AddDeviceAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Adding device {device?.Id}", nameof(AddDeviceAsync)); } } /// <summary> /// Register a new module with device in the system /// </summary> /// <param name="module">The Module object being registered.</param> /// <returns>The Module object with the generated keys and ETags.</returns> public virtual Task<Module> AddModuleAsync(Module module) { return AddModuleAsync(module, CancellationToken.None); } /// <summary> /// Register a new module with device in the system /// </summary> /// <param name="module">The Module object being registered.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Module object with the generated keys and ETags.</returns> public virtual Task<Module> AddModuleAsync(Module module, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Adding module: {module?.Id}", nameof(AddModuleAsync)); try { EnsureInstanceNotClosed(); ValidateModuleId(module); if (!string.IsNullOrEmpty(module.ETag)) { throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); } ValidateDeviceAuthentication(module.Authentication, module.DeviceId); // auto generate keys if not specified if (module.Authentication == null) { module.Authentication = new AuthenticationMechanism(); } var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.PreconditionFailed, async responseMessage => new PreconditionFailedException( await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, { HttpStatusCode.Conflict, async responseMessage => new ModuleAlreadyExistsException( await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, { HttpStatusCode.RequestEntityTooLarge, async responseMessage => new TooManyModulesOnDeviceException( await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } }; return _httpClientHelper.PutAsync(GetModulesRequestUri(module.DeviceId, module.Id), module, PutOperationType.CreateEntity, errorMappingOverrides, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(AddModuleAsync)} threw an exception: {ex}", nameof(AddModuleAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Adding module: {module?.Id}", nameof(AddModuleAsync)); } } /// <summary> /// Adds a Device with Twin information /// </summary> /// <param name="device">The device to add.</param> /// <param name="twin">The twin information for the device being added.</param> /// <returns>The result of the add operation.</returns> public virtual Task<BulkRegistryOperationResult> AddDeviceWithTwinAsync(Device device, Twin twin) { return AddDeviceWithTwinAsync(device, twin, CancellationToken.None); } /// <summary> /// Adds a Device with Twin information /// </summary> /// <param name="device">The device to add.</param> /// <param name="twin">The twin information for the device being added.</param> /// <param name="cancellationToken">A cancellation token to cancel the operation.</param> /// <returns>The result of the add operation.</returns> public virtual Task<BulkRegistryOperationResult> AddDeviceWithTwinAsync(Device device, Twin twin, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Adding device with twin: {device?.Id}", nameof(AddDeviceWithTwinAsync)); try { ValidateDeviceId(device); if (!string.IsNullOrWhiteSpace(device.ETag)) { throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); } var exportImportDeviceList = new List<ExportImportDevice>(1); var exportImportDevice = new ExportImportDevice(device, ImportMode.Create) { Tags = twin?.Tags, Properties = new ExportImportDevice.PropertyContainer { DesiredProperties = twin?.Properties.Desired, ReportedProperties = twin?.Properties.Reported, } }; exportImportDeviceList.Add(exportImportDevice); return BulkDeviceOperationsAsync<BulkRegistryOperationResult>( exportImportDeviceList, ClientApiVersionHelper.ApiVersionQueryString, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(AddDeviceWithTwinAsync)} threw an exception: {ex}", nameof(AddDeviceWithTwinAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Adding device with twin: {device?.Id}", nameof(AddDeviceWithTwinAsync)); } } /// <summary> /// Register a list of new devices with the system /// </summary> /// <param name="devices">The Device objects being registered.</param> /// <returns>Returns a string array of error messages.</returns> [Obsolete("Use AddDevices2Async")] public virtual Task<string[]> AddDevicesAsync(IEnumerable<Device> devices) { return AddDevicesAsync(devices, CancellationToken.None); } /// <summary> /// Register a list of new devices with the system /// </summary> /// <param name="devices">The Device objects being registered.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>Returns a string array of error messages.</returns> [Obsolete("Use AddDevices2Async")] public virtual Task<string[]> AddDevicesAsync(IEnumerable<Device> devices, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Adding {devices?.Count()} devices", nameof(AddDevicesAsync)); try { return BulkDeviceOperationsAsync<string[]>( GenerateExportImportDeviceListForBulkOperations(devices, ImportMode.Create), ClientApiVersionHelper.ApiVersionQueryString, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(AddDevicesAsync)} threw an exception: {ex}", nameof(AddDevicesAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Adding {devices?.Count()} devices", nameof(AddDevicesAsync)); } } /// <summary> /// Register a list of new devices with the system /// </summary> /// <param name="devices">The Device objects being registered.</param> /// <returns>Returns a BulkRegistryOperationResult object.</returns> public virtual Task<BulkRegistryOperationResult> AddDevices2Async(IEnumerable<Device> devices) { return AddDevices2Async(devices, CancellationToken.None); } /// <summary> /// Register a list of new devices with the system /// </summary> /// <param name="devices">The Device objects being registered.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>Returns a BulkRegistryOperationResult object.</returns> public virtual Task<BulkRegistryOperationResult> AddDevices2Async(IEnumerable<Device> devices, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Adding {devices?.Count()} devices", nameof(AddDevices2Async)); try { return BulkDeviceOperationsAsync<BulkRegistryOperationResult>( GenerateExportImportDeviceListForBulkOperations(devices, ImportMode.Create), ClientApiVersionHelper.ApiVersionQueryString, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(AddDevices2Async)} threw an exception: {ex}", nameof(AddDevices2Async)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Adding {devices?.Count()} devices", nameof(AddDevices2Async)); } } /// <summary> /// Update the mutable fields of the device registration /// </summary> /// <param name="device">The Device object with updated fields.</param> /// <returns>The Device object with updated ETag.</returns> public virtual Task<Device> UpdateDeviceAsync(Device device) { return UpdateDeviceAsync(device, CancellationToken.None); } /// <summary> /// Update the mutable fields of the device registration /// </summary> /// <param name="device">The Device object with updated fields.</param> /// <param name="forceUpdate">Forces the device object to be replaced without regard for an ETag match.</param> /// <returns>The Device object with updated ETag.</returns> public virtual Task<Device> UpdateDeviceAsync(Device device, bool forceUpdate) { return UpdateDeviceAsync(device, forceUpdate, CancellationToken.None); } /// <summary> /// Update the mutable fields of the device registration /// </summary> /// <param name="device">The Device object with updated fields.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Device object with updated ETag.</returns> public virtual Task<Device> UpdateDeviceAsync(Device device, CancellationToken cancellationToken) { return UpdateDeviceAsync(device, false, cancellationToken); } /// <summary> /// Update the mutable fields of the device registration /// </summary> /// <param name="device">The Device object with updated fields.</param> /// <param name="forceUpdate">Forces the device object to be replaced even if it was updated since it was retrieved last time.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Device object with updated ETags.</returns> public virtual Task<Device> UpdateDeviceAsync(Device device, bool forceUpdate, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Updating device: {device?.Id}", nameof(UpdateDeviceAsync)); try { EnsureInstanceNotClosed(); ValidateDeviceId(device); if (string.IsNullOrWhiteSpace(device.ETag) && !forceUpdate) { throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingDevice); } ValidateDeviceAuthentication(device.Authentication, device.Id); NormalizeDevice(device); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>() { { HttpStatusCode.PreconditionFailed, async (responseMessage) => new PreconditionFailedException(await ExceptionHandlingHelper .GetExceptionMessageAsync(responseMessage) .ConfigureAwait(false)) }, { HttpStatusCode.NotFound, async responseMessage => { string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); return new DeviceNotFoundException(responseContent, (Exception)null); } } }; PutOperationType operationType = forceUpdate ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity; return _httpClientHelper.PutAsync(GetRequestUri(device.Id), device, operationType, errorMappingOverrides, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(UpdateDeviceAsync)} threw an exception: {ex}", nameof(UpdateDeviceAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Updating device: {device?.Id}", nameof(UpdateDeviceAsync)); } } /// <summary> /// Update the mutable fields of the module registration /// </summary> /// <param name="module">The Module object with updated fields.</param> /// <returns>The Module object with updated ETags.</returns> public virtual Task<Module> UpdateModuleAsync(Module module) { return UpdateModuleAsync(module, CancellationToken.None); } /// <summary> /// Update the mutable fields of the module registration /// </summary> /// <param name="module">The Module object with updated fields.</param> /// <param name="forceUpdate">Forces the device object to be replaced without regard for an ETag match.</param> /// <returns>The Module object with updated ETags.</returns> public virtual Task<Module> UpdateModuleAsync(Module module, bool forceUpdate) { return UpdateModuleAsync(module, forceUpdate, CancellationToken.None); } /// <summary> /// Update the mutable fields of the module registration /// </summary> /// <param name="module">The Module object with updated fields.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Module object with updated ETags.</returns> public virtual Task<Module> UpdateModuleAsync(Module module, CancellationToken cancellationToken) { return UpdateModuleAsync(module, false, CancellationToken.None); } /// <summary> /// Update the mutable fields of the module registration /// </summary> /// <param name="module">The Module object with updated fields.</param> /// <param name="forceUpdate">Forces the module object to be replaced even if it was updated since it was retrieved last time.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Module object with updated ETags.</returns> public virtual Task<Module> UpdateModuleAsync(Module module, bool forceUpdate, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Updating module: {module?.Id}", nameof(UpdateModuleAsync)); try { EnsureInstanceNotClosed(); ValidateModuleId(module); if (string.IsNullOrWhiteSpace(module.ETag) && !forceUpdate) { throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingDevice); } ValidateDeviceAuthentication(module.Authentication, module.DeviceId); // auto generate keys if not specified if (module.Authentication == null) { module.Authentication = new AuthenticationMechanism(); } var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>() { { HttpStatusCode.PreconditionFailed, async (responseMessage) => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, { HttpStatusCode.NotFound, async responseMessage => { string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); return new ModuleNotFoundException(responseContent, (Exception)null); } } }; PutOperationType operationType = forceUpdate ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity; return _httpClientHelper.PutAsync(GetModulesRequestUri(module.DeviceId, module.Id), module, operationType, errorMappingOverrides, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(UpdateModuleAsync)} threw an exception: {ex}", nameof(UpdateModuleAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Updating module: {module?.Id}", nameof(UpdateModuleAsync)); } } /// <summary> /// Update a list of devices with the system /// </summary> /// <param name="devices">The Device objects being updated.</param> /// <returns>Returns a string array of error messages.</returns> [Obsolete("Use UpdateDevices2Async")] public virtual Task<string[]> UpdateDevicesAsync(IEnumerable<Device> devices) { return UpdateDevicesAsync(devices, false, CancellationToken.None); } /// <summary> /// Update a list of devices with the system /// </summary> /// <param name="devices">The Device objects being updated.</param> /// <param name="forceUpdate">Forces the device object to be replaced even if it was updated since it was retrieved last time.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>Returns a string array of error messages.</returns> [Obsolete("Use UpdateDevices2Async")] public virtual Task<string[]> UpdateDevicesAsync(IEnumerable<Device> devices, bool forceUpdate, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Updating multiple devices: count: {devices?.Count()}", nameof(UpdateDevicesAsync)); try { return BulkDeviceOperationsAsync<string[]>( GenerateExportImportDeviceListForBulkOperations(devices, forceUpdate ? ImportMode.Update : ImportMode.UpdateIfMatchETag), ClientApiVersionHelper.ApiVersionQueryString, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(UpdateDevicesAsync)} threw an exception: {ex}", nameof(UpdateDevicesAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Updating multiple devices: count: {devices?.Count()}", nameof(UpdateDevicesAsync)); } } /// <summary> /// Update a list of devices with the system /// </summary> /// <param name="devices">The Device objects being updated.</param> /// <returns>Returns a BulkRegistryOperationResult object.</returns> public virtual Task<BulkRegistryOperationResult> UpdateDevices2Async(IEnumerable<Device> devices) { return UpdateDevices2Async(devices, false, CancellationToken.None); } /// <summary> /// Update a list of devices with the system /// </summary> /// <param name="devices">The Device objects being updated.</param> /// <param name="forceUpdate">Forces the device object to be replaced even if it was updated since it was retrieved last time.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>Returns a BulkRegistryOperationResult object.</returns> public virtual Task<BulkRegistryOperationResult> UpdateDevices2Async(IEnumerable<Device> devices, bool forceUpdate, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Updating multiple devices: count: {devices?.Count()} - Force update: {forceUpdate}", nameof(UpdateDevices2Async)); try { return BulkDeviceOperationsAsync<BulkRegistryOperationResult>( GenerateExportImportDeviceListForBulkOperations(devices, forceUpdate ? ImportMode.Update : ImportMode.UpdateIfMatchETag), ClientApiVersionHelper.ApiVersionQueryString, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(UpdateDevices2Async)} threw an exception: {ex}", nameof(UpdateDevices2Async)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Updating multiple devices: count: {devices?.Count()} - Force update: {forceUpdate}", nameof(UpdateDevices2Async)); } } /// <summary> /// Deletes a previously registered device from the system. /// </summary> /// <param name="deviceId">The id of the device being deleted.</param> public virtual Task RemoveDeviceAsync(string deviceId) { return RemoveDeviceAsync(deviceId, CancellationToken.None); } /// <summary> /// Deletes a previously registered device from the system. /// </summary> /// <param name="deviceId">The id of the device being deleted.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> public virtual Task RemoveDeviceAsync(string deviceId, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Removing device: {deviceId}", nameof(RemoveDeviceAsync)); try { EnsureInstanceNotClosed(); if (string.IsNullOrWhiteSpace(deviceId)) { throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); } // use wild-card ETag var eTag = new ETagHolder { ETag = "*" }; return RemoveDeviceAsync(deviceId, eTag, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(RemoveDeviceAsync)} threw an exception: {ex}", nameof(RemoveDeviceAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Removing device: {deviceId}", nameof(RemoveDeviceAsync)); } } /// <summary> /// Deletes a previously registered device from the system. /// </summary> /// <param name="device">The device being deleted.</param> public virtual Task RemoveDeviceAsync(Device device) { return RemoveDeviceAsync(device, CancellationToken.None); } /// <summary> /// Deletes a previously registered device from the system. /// </summary> /// <param name="device">The device being deleted.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> public virtual Task RemoveDeviceAsync(Device device, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Removing device: {device?.Id}", nameof(RemoveDeviceAsync)); try { EnsureInstanceNotClosed(); ValidateDeviceId(device); return string.IsNullOrWhiteSpace(device.ETag) ? throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingDevice) : RemoveDeviceAsync(device.Id, device, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(RemoveDeviceAsync)} threw an exception: {ex}", nameof(RemoveDeviceAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Removing device: {device?.Id}", nameof(RemoveDeviceAsync)); } } /// <summary> /// Deletes a previously registered module from device in the system. /// </summary> /// <param name="deviceId">The id of the device being deleted.</param> /// <param name="moduleId">The id of the moduleId being deleted.</param> public virtual Task RemoveModuleAsync(string deviceId, string moduleId) { return RemoveModuleAsync(deviceId, moduleId, CancellationToken.None); } /// <summary> /// Deletes a previously registered module from device in the system. /// </summary> /// <param name="deviceId">The id of the device being deleted.</param> /// <param name="moduleId">The id of the moduleId being deleted.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> public virtual Task RemoveModuleAsync(string deviceId, string moduleId, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Removing module: device Id:{deviceId} moduleId: {moduleId}", nameof(RemoveDeviceAsync)); try { EnsureInstanceNotClosed(); if (string.IsNullOrWhiteSpace(deviceId) || string.IsNullOrEmpty(moduleId)) { throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); } // use wild-card ETag var eTag = new ETagHolder { ETag = "*" }; return RemoveDeviceModuleAsync(deviceId, moduleId, eTag, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(RemoveModuleAsync)} threw an exception: {ex}", nameof(RemoveModuleAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Removing module: device Id:{deviceId} moduleId: {moduleId}", nameof(RemoveModuleAsync)); } } /// <summary> /// Deletes a previously registered module from device in the system. /// </summary> /// <param name="module">The module being deleted.</param> public virtual Task RemoveModuleAsync(Module module) { return RemoveModuleAsync(module, CancellationToken.None); } /// <summary> /// Deletes a previously registered module from device in the system. /// </summary> /// <param name="module">The module being deleted.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> public virtual Task RemoveModuleAsync(Module module, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Removing module: device Id:{module?.DeviceId} moduleId: {module?.Id}", nameof(RemoveModuleAsync)); try { EnsureInstanceNotClosed(); ValidateModuleId(module); return string.IsNullOrWhiteSpace(module.ETag) ? throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingDevice) : RemoveDeviceModuleAsync(module.DeviceId, module.Id, module, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(RemoveModuleAsync)} threw an exception: {ex}", nameof(RemoveModuleAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Removing module: device Id:{module?.DeviceId} moduleId: {module?.Id}", nameof(RemoveModuleAsync)); } } /// <summary> /// Deletes a list of previously registered devices from the system. /// </summary> /// <param name="devices">The devices being deleted.</param> [Obsolete("Use RemoveDevices2Async")] public virtual Task<string[]> RemoveDevicesAsync(IEnumerable<Device> devices) { return RemoveDevicesAsync(devices, false, CancellationToken.None); } /// <summary> /// Deletes a list of previously registered devices from the system. /// </summary> /// <param name="devices">The devices being deleted.</param> /// <param name="forceRemove">Forces the device object to be removed without regard for an ETag match.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> [Obsolete("Use RemoveDevices2Async")] public virtual Task<string[]> RemoveDevicesAsync(IEnumerable<Device> devices, bool forceRemove, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevicesAsync)); try { return BulkDeviceOperationsAsync<string[]>( GenerateExportImportDeviceListForBulkOperations(devices, forceRemove ? ImportMode.Delete : ImportMode.DeleteIfMatchETag), ClientApiVersionHelper.ApiVersionQueryString, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(RemoveDevicesAsync)} threw an exception: {ex}", nameof(RemoveDevicesAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevicesAsync)); } } /// <summary> /// Deletes a list of previously registered devices from the system. /// </summary> /// <param name="devices">The devices being deleted.</param> /// <returns>Returns a BulkRegistryOperationResult object.</returns> public virtual Task<BulkRegistryOperationResult> RemoveDevices2Async(IEnumerable<Device> devices) { return RemoveDevices2Async(devices, false, CancellationToken.None); } /// <summary> /// Deletes a list of previously registered devices from the system. /// </summary> /// <param name="devices">The devices being deleted.</param> /// <param name="forceRemove">Forces the device object to be removed even if it was updated since it was retrieved last time.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>Returns a BulkRegistryOperationResult object.</returns> public virtual Task<BulkRegistryOperationResult> RemoveDevices2Async(IEnumerable<Device> devices, bool forceRemove, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevices2Async)); try { return BulkDeviceOperationsAsync<BulkRegistryOperationResult>( GenerateExportImportDeviceListForBulkOperations(devices, forceRemove ? ImportMode.Delete : ImportMode.DeleteIfMatchETag), ClientApiVersionHelper.ApiVersionQueryString, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(RemoveDevicesAsync)} threw an exception: {ex}", nameof(RemoveDevicesAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Removing devices : count: {devices?.Count()} - Force remove: {forceRemove}", nameof(RemoveDevicesAsync)); } } /// <summary> /// Gets usage statistics for the IoT hub. /// </summary> public virtual Task<RegistryStatistics> GetRegistryStatisticsAsync() { return GetRegistryStatisticsAsync(CancellationToken.None); } /// <summary> /// Gets usage statistics for the IoT hub. /// </summary> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> public virtual Task<RegistryStatistics> GetRegistryStatisticsAsync(CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Getting registry statistics", nameof(GetRegistryStatisticsAsync)); try { EnsureInstanceNotClosed(); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new IotHubNotFoundException(_iotHubName)) } }; return _httpClientHelper.GetAsync<RegistryStatistics>(GetStatisticsUri(), errorMappingOverrides, null, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetRegistryStatisticsAsync)} threw an exception: {ex}", nameof(GetRegistryStatisticsAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Getting registry statistics", nameof(GetRegistryStatisticsAsync)); } } /// <summary> /// Retrieves the specified Device object. /// </summary> /// <param name="deviceId">The id of the device being retrieved.</param> /// <returns>The Device object.</returns> public virtual Task<Device> GetDeviceAsync(string deviceId) { return GetDeviceAsync(deviceId, CancellationToken.None); } /// <summary> /// Retrieves the specified Device object. /// </summary> /// <param name="deviceId">The id of the device being retrieved.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Device object.</returns> public virtual Task<Device> GetDeviceAsync(string deviceId, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Getting device: {deviceId}", nameof(GetDeviceAsync)); try { if (string.IsNullOrWhiteSpace(deviceId)) { throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); } EnsureInstanceNotClosed(); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>() { { HttpStatusCode.NotFound, async responseMessage => new DeviceNotFoundException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } }; return _httpClientHelper.GetAsync<Device>(GetRequestUri(deviceId), errorMappingOverrides, null, false, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetDeviceAsync)} threw an exception: {ex}", nameof(GetDeviceAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Getting device: {deviceId}", nameof(GetDeviceAsync)); } } /// <summary> /// Retrieves the specified Module object. /// </summary> /// <param name="deviceId">The id of the device being retrieved.</param> /// <param name="moduleId">The id of the module being retrieved.</param> /// <returns>The Module object.</returns> public virtual Task<Module> GetModuleAsync(string deviceId, string moduleId) { return GetModuleAsync(deviceId, moduleId, CancellationToken.None); } /// <summary> /// Retrieves the specified Module object. /// </summary> /// <param name="deviceId">The id of the device being retrieved.</param> /// <param name="moduleId">The id of the module being retrieved.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Module object.</returns> public virtual Task<Module> GetModuleAsync(string deviceId, string moduleId, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Getting module: device Id: {deviceId} - module Id: {moduleId}", nameof(GetModuleAsync)); try { if (string.IsNullOrWhiteSpace(deviceId)) { throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); } if (string.IsNullOrWhiteSpace(moduleId)) { throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "moduleId")); } EnsureInstanceNotClosed(); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.NotFound, responseMessage => Task.FromResult<Exception>(new ModuleNotFoundException(deviceId, moduleId)) }, }; return _httpClientHelper.GetAsync<Module>(GetModulesRequestUri(deviceId, moduleId), errorMappingOverrides, null, false, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetModuleAsync)} threw an exception: {ex}", nameof(GetModuleAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Getting module: device Id: {deviceId} - module Id: {moduleId}", nameof(GetModuleAsync)); } } /// <summary> /// Retrieves the module identities on device /// </summary> /// <param name="deviceId">The device Id.</param> /// <returns>List of modules on device.</returns> public virtual Task<IEnumerable<Module>> GetModulesOnDeviceAsync(string deviceId) { return GetModulesOnDeviceAsync(deviceId, CancellationToken.None); } /// <summary> /// Retrieves the module identities on device /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>List of modules on device.</returns> public virtual Task<IEnumerable<Module>> GetModulesOnDeviceAsync(string deviceId, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Getting module on device: {deviceId}", nameof(GetModulesOnDeviceAsync)); try { EnsureInstanceNotClosed(); return _httpClientHelper.GetAsync<IEnumerable<Module>>( GetModulesOnDeviceRequestUri(deviceId), null, null, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetModulesOnDeviceAsync)} threw an exception: {ex}", nameof(GetModulesOnDeviceAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Getting module on device: {deviceId}", nameof(GetModulesOnDeviceAsync)); } } /// <summary> /// Retrieves specified number of devices from every IoT hub partition. /// This is an approximation and not a definitive list. Results are not ordered. /// </summary> /// <returns>The list of devices.</returns> [Obsolete("Use CreateQuery(\"select * from devices\", pageSize);")] public virtual Task<IEnumerable<Device>> GetDevicesAsync(int maxCount) { return GetDevicesAsync(maxCount, CancellationToken.None); } /// <summary> /// Retrieves specified number of devices from every IoT hub partition. /// This is an approximation and not a definitive list. Results are not ordered. /// </summary> /// <returns>The list of devices.</returns> [Obsolete("Use CreateQuery(\"select * from devices\", pageSize);")] public virtual Task<IEnumerable<Device>> GetDevicesAsync(int maxCount, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Getting devices - max count: {maxCount}", nameof(GetDevicesAsync)); try { EnsureInstanceNotClosed(); return _httpClientHelper.GetAsync<IEnumerable<Device>>( GetDevicesRequestUri(maxCount), s_defaultGetDevicesOperationTimeout, null, null, true, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetDevicesAsync)} threw an exception: {ex}", nameof(GetDevicesAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Getting devices - max count: {maxCount}", nameof(GetDevicesAsync)); } } /// <summary> /// Retrieves a handle through which a result for a given query can be fetched. /// </summary> /// <param name="sqlQueryString">The SQL query.</param> /// <returns>A handle used to fetch results for a SQL query.</returns> public virtual IQuery CreateQuery(string sqlQueryString) { return CreateQuery(sqlQueryString, null); } /// <summary> /// Retrieves a handle through which a result for a given query can be fetched. /// </summary> /// <param name="sqlQueryString">The SQL query.</param> /// <param name="pageSize">The maximum number of items per page.</param> /// <returns>A handle used to fetch results for a SQL query.</returns> public virtual IQuery CreateQuery(string sqlQueryString, int? pageSize) { if (Logging.IsEnabled) Logging.Enter(this, $"Creating query", nameof(CreateQuery)); try { return new Query((token) => ExecuteQueryAsync( sqlQueryString, pageSize, token, CancellationToken.None)); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(CreateQuery)} threw an exception: {ex}", nameof(CreateQuery)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Creating query", nameof(CreateQuery)); } } /// <summary> /// Copies registered device data to a set of blobs in a specific container in a storage account. /// </summary> /// <param name="storageAccountConnectionString">ConnectionString to the destination StorageAccount.</param> /// <param name="containerName">Destination blob container name.</param> public virtual Task ExportRegistryAsync(string storageAccountConnectionString, string containerName) { return ExportRegistryAsync(storageAccountConnectionString, containerName, CancellationToken.None); } /// <summary> /// Copies registered device data to a set of blobs in a specific container in a storage account. /// </summary> /// <param name="storageAccountConnectionString">ConnectionString to the destination StorageAccount.</param> /// <param name="containerName">Destination blob container name.</param> /// <param name="cancellationToken">Task cancellation token.</param> public virtual Task ExportRegistryAsync(string storageAccountConnectionString, string containerName, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Exporting registry", nameof(ExportRegistryAsync)); try { EnsureInstanceNotClosed(); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new IotHubNotFoundException(_iotHubName)) } }; return _httpClientHelper.PostAsync( GetAdminUri("exportRegistry"), new ExportImportRequest { ContainerName = containerName, StorageConnectionString = storageAccountConnectionString, }, errorMappingOverrides, null, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(ExportRegistryAsync)} threw an exception: {ex}", nameof(ExportRegistryAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Exporting registry", nameof(ExportRegistryAsync)); } } /// <summary> /// Imports registered device data from a set of blobs in a specific container in a storage account. /// </summary> /// <param name="storageAccountConnectionString">ConnectionString to the source StorageAccount.</param> /// <param name="containerName">Source blob container name.</param> public virtual Task ImportRegistryAsync(string storageAccountConnectionString, string containerName) { return ImportRegistryAsync(storageAccountConnectionString, containerName, CancellationToken.None); } /// <summary> /// Imports registered device data from a set of blobs in a specific container in a storage account. /// </summary> /// <param name="storageAccountConnectionString">ConnectionString to the source StorageAccount.</param> /// <param name="containerName">Source blob container name.</param> /// <param name="cancellationToken">Task cancellation token.</param> public virtual Task ImportRegistryAsync(string storageAccountConnectionString, string containerName, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Importing registry", nameof(ImportRegistryAsync)); try { EnsureInstanceNotClosed(); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new IotHubNotFoundException(_iotHubName)) } }; return _httpClientHelper.PostAsync( GetAdminUri("importRegistry"), new ExportImportRequest { ContainerName = containerName, StorageConnectionString = storageAccountConnectionString, }, errorMappingOverrides, null, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(ImportRegistryAsync)} threw an exception: {ex}", nameof(ImportRegistryAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Importing registry", nameof(ImportRegistryAsync)); } } #pragma warning disable CA1054 // Uri parameters should not be strings /// <summary> /// Creates a new bulk job to export device registrations to the container specified by the provided URI. /// </summary> /// <param name="exportBlobContainerUri">Destination blob container URI.</param> /// <param name="excludeKeys">Specifies whether to exclude the Device's Keys during the export.</param> /// <returns>JobProperties of the newly created job.</returns> public virtual Task<JobProperties> ExportDevicesAsync(string exportBlobContainerUri, bool excludeKeys) { return ExportDevicesAsync( JobProperties.CreateForExportJob( exportBlobContainerUri, excludeKeys)); } /// <summary> /// Creates a new bulk job to export device registrations to the container specified by the provided URI. /// </summary> /// <param name="exportBlobContainerUri">Destination blob container URI.</param> /// <param name="excludeKeys">Specifies whether to exclude the Device's Keys during the export.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>JobProperties of the newly created job.</returns> public virtual Task<JobProperties> ExportDevicesAsync(string exportBlobContainerUri, bool excludeKeys, CancellationToken cancellationToken) { return ExportDevicesAsync( JobProperties.CreateForExportJob( exportBlobContainerUri, excludeKeys), cancellationToken); } /// <summary> /// Creates a new bulk job to export device registrations to the container specified by the provided URI. /// </summary> /// <param name="exportBlobContainerUri">Destination blob container URI.</param> /// <param name="outputBlobName">The name of the blob that will be created in the provided output blob container.</param> /// <param name="excludeKeys">Specifies whether to exclude the Device's Keys during the export.</param> /// <returns>JobProperties of the newly created job.</returns> public virtual Task<JobProperties> ExportDevicesAsync(string exportBlobContainerUri, string outputBlobName, bool excludeKeys) { return ExportDevicesAsync( JobProperties.CreateForExportJob( exportBlobContainerUri, excludeKeys, outputBlobName)); } /// <summary> /// Creates a new bulk job to export device registrations to the container specified by the provided URI. /// </summary> /// <param name="exportBlobContainerUri">Destination blob container URI.</param> /// <param name="outputBlobName">The name of the blob that will be created in the provided output blob container.</param> /// <param name="excludeKeys">Specifies whether to exclude the Device's Keys during the export.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>JobProperties of the newly created job.</returns> public virtual Task<JobProperties> ExportDevicesAsync(string exportBlobContainerUri, string outputBlobName, bool excludeKeys, CancellationToken cancellationToken) { return ExportDevicesAsync( JobProperties.CreateForExportJob( exportBlobContainerUri, excludeKeys, outputBlobName), cancellationToken); } /// <summary> /// Creates a new bulk job to export device registrations to the container specified by the provided URI. /// </summary> /// <param name="jobParameters">Parameters for the job.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <remarks>Conditionally includes configurations, if specified.</remarks> /// <returns>JobProperties of the newly created job.</returns> public virtual Task<JobProperties> ExportDevicesAsync(JobProperties jobParameters, CancellationToken cancellationToken = default) { if (jobParameters == null) { throw new ArgumentNullException(nameof(jobParameters)); } if (Logging.IsEnabled) Logging.Enter(this, $"Export Job running with {jobParameters}", nameof(ExportDevicesAsync)); try { jobParameters.Type = JobType.ExportDevices; return CreateJobAsync(jobParameters, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(ExportDevicesAsync)} threw an exception: {ex}", nameof(ExportDevicesAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Export Job running with {jobParameters}", nameof(ExportDevicesAsync)); } } /// <summary> /// Creates a new bulk job to import device registrations into the IoT hub. /// </summary> /// <param name="importBlobContainerUri">Source blob container URI.</param> /// <param name="outputBlobContainerUri">Destination blob container URI.</param> /// <returns>JobProperties of the newly created job.</returns> public virtual Task<JobProperties> ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri) { return ImportDevicesAsync( JobProperties.CreateForImportJob( importBlobContainerUri, outputBlobContainerUri)); } /// <summary> /// Creates a new bulk job to import device registrations into the IoT hub. /// </summary> /// <param name="importBlobContainerUri">Source blob container URI.</param> /// <param name="outputBlobContainerUri">Destination blob container URI.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>JobProperties of the newly created job.</returns> public virtual Task<JobProperties> ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, CancellationToken cancellationToken) { return ImportDevicesAsync( JobProperties.CreateForImportJob( importBlobContainerUri, outputBlobContainerUri), cancellationToken); } /// <summary> /// Creates a new bulk job to import device registrations into the IoT hub. /// </summary> /// <param name="importBlobContainerUri">Source blob container URI.</param> /// <param name="outputBlobContainerUri">Destination blob container URI.</param> /// <param name="inputBlobName">The blob name to be used when importing from the provided input blob container.</param> /// <returns>JobProperties of the newly created job.</returns> public virtual Task<JobProperties> ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, string inputBlobName) { return ImportDevicesAsync( JobProperties.CreateForImportJob( importBlobContainerUri, outputBlobContainerUri, inputBlobName)); } /// <summary> /// Creates a new bulk job to import device registrations into the IoT hub. /// </summary> /// <param name="importBlobContainerUri">Source blob container URI.</param> /// <param name="outputBlobContainerUri">Destination blob container URI.</param> /// <param name="inputBlobName">The blob name to be used when importing from the provided input blob container.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>JobProperties of the newly created job.</returns> public virtual Task<JobProperties> ImportDevicesAsync(string importBlobContainerUri, string outputBlobContainerUri, string inputBlobName, CancellationToken cancellationToken) { return ImportDevicesAsync( JobProperties.CreateForImportJob( importBlobContainerUri, outputBlobContainerUri, inputBlobName), cancellationToken); } #pragma warning restore CA1054 // Uri parameters should not be strings /// <summary> /// Creates a new bulk job to import device registrations into the IoT hub. /// </summary> /// <param name="jobParameters">Parameters for the job.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <remarks>Conditionally includes configurations, if specified.</remarks> /// <returns>JobProperties of the newly created job.</returns> public virtual Task<JobProperties> ImportDevicesAsync(JobProperties jobParameters, CancellationToken cancellationToken = default) { if (jobParameters == null) { throw new ArgumentNullException(nameof(jobParameters)); } if (Logging.IsEnabled) Logging.Enter(this, $"Import Job running with {jobParameters}", nameof(ImportDevicesAsync)); try { jobParameters.Type = JobType.ImportDevices; return CreateJobAsync(jobParameters, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(ExportDevicesAsync)} threw an exception: {ex}", nameof(ImportDevicesAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Import Job running with {jobParameters}", nameof(ImportDevicesAsync)); } } /// <summary> /// Gets the job with the specified Id. /// </summary> /// <param name="jobId">Id of the Job object to retrieve.</param> /// <returns>JobProperties of the job specified by the provided jobId.</returns> public virtual Task<JobProperties> GetJobAsync(string jobId) { return GetJobAsync(jobId, CancellationToken.None); } /// <summary> /// Gets the job with the specified Id. /// </summary> /// <param name="jobId">Id of the Job object to retrieve.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>JobProperties of the job specified by the provided jobId.</returns> public virtual Task<JobProperties> GetJobAsync(string jobId, CancellationToken cancellationToken) { Logging.Enter(this, $"Getting job {jobId}", nameof(GetJobsAsync)); try { EnsureInstanceNotClosed(); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new JobNotFoundException(jobId)) } }; return _httpClientHelper.GetAsync<JobProperties>( GetJobUri("/{0}".FormatInvariant(jobId)), errorMappingOverrides, null, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetJobsAsync)} threw an exception: {ex}", nameof(GetJobsAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Getting job {jobId}", nameof(GetJobsAsync)); } } /// <summary> /// List all jobs for the IoT hub. /// </summary> /// <returns>IEnumerable of JobProperties of all jobs for this IoT hub.</returns> public virtual Task<IEnumerable<JobProperties>> GetJobsAsync() { return GetJobsAsync(CancellationToken.None); } /// <summary> /// List all jobs for the IoT hub. /// </summary> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>IEnumerable of JobProperties of all jobs for this IoT hub.</returns> public virtual Task<IEnumerable<JobProperties>> GetJobsAsync(CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Getting job", nameof(GetJobsAsync)); try { EnsureInstanceNotClosed(); return _httpClientHelper.GetAsync<IEnumerable<JobProperties>>( GetJobUri(string.Empty), null, null, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetJobsAsync)} threw an exception: {ex}", nameof(GetJobsAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Getting job", nameof(GetJobsAsync)); } } /// <summary> /// Cancels/Deletes the job with the specified Id. /// </summary> /// <param name="jobId">Id of the job to cancel.</param> public virtual Task CancelJobAsync(string jobId) { return CancelJobAsync(jobId, CancellationToken.None); } /// <summary> /// Cancels/Deletes the job with the specified Id. /// </summary> /// <param name="jobId">Id of the job to cancel.</param> /// <param name="cancellationToken">Task cancellation token.</param> public virtual Task CancelJobAsync(string jobId, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Canceling job: {jobId}", nameof(CancelJobAsync)); try { EnsureInstanceNotClosed(); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.NotFound, responseMessage => Task.FromResult((Exception)new JobNotFoundException(jobId)) } }; IETagHolder jobETag = new ETagHolder { ETag = jobId, }; return _httpClientHelper.DeleteAsync( GetJobUri("/{0}".FormatInvariant(jobId)), jobETag, errorMappingOverrides, null, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetJobsAsync)} threw an exception: {ex}", nameof(GetJobsAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Getting job {jobId}", nameof(GetJobsAsync)); } } /// <summary> /// Gets <see cref="Twin"/> from IotHub /// </summary> /// <param name="deviceId">The device Id.</param> /// <returns>Twin instance.</returns> public virtual Task<Twin> GetTwinAsync(string deviceId) { return GetTwinAsync(deviceId, CancellationToken.None); } /// <summary> /// Gets <see cref="Twin"/> from IotHub /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Twin instance.</returns> public virtual Task<Twin> GetTwinAsync(string deviceId, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Getting device twin on device: {deviceId}", nameof(GetTwinAsync)); try { if (string.IsNullOrWhiteSpace(deviceId)) { throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); } EnsureInstanceNotClosed(); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>() { { HttpStatusCode.NotFound, async responseMessage => new DeviceNotFoundException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } }; return _httpClientHelper.GetAsync<Twin>(GetTwinUri(deviceId), errorMappingOverrides, null, false, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetTwinAsync)} threw an exception: {ex}", nameof(GetTwinAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Getting device twin on device: {deviceId}", nameof(GetTwinAsync)); } } /// <summary> /// Gets Module's <see cref="Twin"/> from IotHub /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="moduleId">The module Id.</param> /// <returns>Twin instance.</returns> public virtual Task<Twin> GetTwinAsync(string deviceId, string moduleId) { return GetTwinAsync(deviceId, moduleId, CancellationToken.None); } /// <summary> /// Gets Module's <see cref="Twin"/> from IotHub /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="moduleId">The module Id.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Twin instance.</returns> public virtual Task<Twin> GetTwinAsync(string deviceId, string moduleId, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Getting device twin on device: {deviceId} and module: {moduleId}", nameof(GetTwinAsync)); try { if (string.IsNullOrWhiteSpace(deviceId)) { throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "deviceId")); } if (string.IsNullOrWhiteSpace(moduleId)) { throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "moduleId")); } EnsureInstanceNotClosed(); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>() { { HttpStatusCode.NotFound, async responseMessage => new ModuleNotFoundException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false), (Exception)null) } }; return _httpClientHelper.GetAsync<Twin>(GetModuleTwinRequestUri(deviceId, moduleId), errorMappingOverrides, null, false, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetTwinAsync)} threw an exception: {ex}", nameof(GetTwinAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Getting device twin on device: {deviceId} and module: {moduleId}", nameof(GetTwinAsync)); } } /// <summary> /// Updates the mutable fields of <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="twinPatch">Twin with updated fields.</param> /// <param name="etag">Twin's ETag.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> UpdateTwinAsync(string deviceId, Twin twinPatch, string etag) { return UpdateTwinAsync(deviceId, twinPatch, etag, CancellationToken.None); } /// <summary> /// Updates the mutable fields of <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="twinPatch">Twin with updated fields.</param> /// <param name="etag">Twin's ETag.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> UpdateTwinAsync(string deviceId, Twin twinPatch, string etag, CancellationToken cancellationToken) { return UpdateTwinInternalAsync(deviceId, twinPatch, etag, false, cancellationToken); } /// <summary> /// Updates the mutable fields of <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="jsonTwinPatch">Twin json with updated fields.</param> /// <param name="etag">Twin's ETag.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> UpdateTwinAsync(string deviceId, string jsonTwinPatch, string etag) { return UpdateTwinAsync(deviceId, jsonTwinPatch, etag, CancellationToken.None); } /// <summary> /// Updates the mutable fields of <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="jsonTwinPatch">Twin json with updated fields.</param> /// <param name="etag">Twin's ETag.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> UpdateTwinAsync(string deviceId, string jsonTwinPatch, string etag, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Updating device twin on device: {deviceId}", nameof(UpdateTwinAsync)); try { if (string.IsNullOrWhiteSpace(jsonTwinPatch)) { throw new ArgumentNullException(nameof(jsonTwinPatch)); } // TODO: Do we need to deserialize Twin, only to serialize it again? Twin twin = JsonConvert.DeserializeObject<Twin>(jsonTwinPatch, JsonSerializerSettingsInitializer.GetJsonSerializerSettings()); return UpdateTwinAsync(deviceId, twin, etag, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(UpdateTwinAsync)} threw an exception: {ex}", nameof(UpdateTwinAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Updating device twin on device: {deviceId}", nameof(UpdateTwinAsync)); } } /// <summary> /// Updates the mutable fields of Module's <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="moduleId">The module Id.</param> /// <param name="twinPatch">Twin with updated fields.</param> /// <param name="etag">Twin's ETag.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> UpdateTwinAsync(string deviceId, string moduleId, Twin twinPatch, string etag) { return UpdateTwinAsync(deviceId, moduleId, twinPatch, etag, CancellationToken.None); } /// <summary> /// Updates the mutable fields of Module's <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="moduleId">The module Id.</param> /// <param name="twinPatch">Twin with updated fields.</param> /// <param name="etag">Twin's ETag.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> UpdateTwinAsync(string deviceId, string moduleId, Twin twinPatch, string etag, CancellationToken cancellationToken) { return UpdateTwinInternalAsync(deviceId, moduleId, twinPatch, etag, false, cancellationToken); } /// <summary> /// Updates the mutable fields of Module's <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="moduleId">The module Id.</param> /// <param name="jsonTwinPatch">Twin json with updated fields.</param> /// <param name="etag">Twin's ETag.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> UpdateTwinAsync(string deviceId, string moduleId, string jsonTwinPatch, string etag) { return UpdateTwinAsync(deviceId, moduleId, jsonTwinPatch, etag, CancellationToken.None); } /// <summary> /// Updates the mutable fields of Module's <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="moduleId">The module Id.</param> /// <param name="jsonTwinPatch">Twin json with updated fields.</param> /// <param name="etag">Twin's ETag.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> UpdateTwinAsync(string deviceId, string moduleId, string jsonTwinPatch, string etag, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Updating device twin on device: {deviceId} and module: {moduleId}", nameof(UpdateTwinAsync)); try { if (string.IsNullOrWhiteSpace(jsonTwinPatch)) { throw new ArgumentNullException(nameof(jsonTwinPatch)); } // TODO: Do we need to deserialize Twin, only to serialize it again? Twin twin = JsonConvert.DeserializeObject<Twin>(jsonTwinPatch, JsonSerializerSettingsInitializer.GetJsonSerializerSettings()); return UpdateTwinAsync(deviceId, moduleId, twin, etag, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(UpdateTwinAsync)} threw an exception: {ex}", nameof(UpdateTwinAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Updating device twin on device: {deviceId} and module: {moduleId}", nameof(UpdateTwinAsync)); } } /// <summary> /// Update the mutable fields for a list of <see cref="Twin"/>s previously created within the system /// </summary> /// <param name="twins">List of <see cref="Twin"/>s with updated fields.</param> /// <returns>Result of the bulk update operation.</returns> public virtual Task<BulkRegistryOperationResult> UpdateTwins2Async(IEnumerable<Twin> twins) { return UpdateTwins2Async(twins, false, CancellationToken.None); } /// <summary> /// Update the mutable fields for a list of <see cref="Twin"/>s previously created within the system /// </summary> /// <param name="twins">List of <see cref="Twin"/>s with updated fields.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Result of the bulk update operation.</returns> public virtual Task<BulkRegistryOperationResult> UpdateTwins2Async(IEnumerable<Twin> twins, CancellationToken cancellationToken) { return UpdateTwins2Async(twins, false, cancellationToken); } /// <summary> /// Update the mutable fields for a list of <see cref="Twin"/>s previously created within the system /// </summary> /// <param name="twins">List of <see cref="Twin"/>s with updated fields.</param> /// <param name="forceUpdate">Forces the <see cref="Twin"/> object to be updated even if it has changed since it was retrieved last time.</param> /// <returns>Result of the bulk update operation.</returns> public virtual Task<BulkRegistryOperationResult> UpdateTwins2Async(IEnumerable<Twin> twins, bool forceUpdate) { return UpdateTwins2Async(twins, forceUpdate, CancellationToken.None); } /// <summary> /// Update the mutable fields for a list of <see cref="Twin"/>s previously created within the system /// </summary> /// <param name="twins">List of <see cref="Twin"/>s with updated fields.</param> /// <param name="forceUpdate">Forces the <see cref="Twin"/> object to be updated even if it has changed since it was retrieved last time.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Result of the bulk update operation.</returns> public virtual Task<BulkRegistryOperationResult> UpdateTwins2Async(IEnumerable<Twin> twins, bool forceUpdate, CancellationToken cancellationToken) { return BulkDeviceOperationsAsync<BulkRegistryOperationResult>( GenerateExportImportDeviceListForTwinBulkOperations(twins, forceUpdate ? ImportMode.UpdateTwin : ImportMode.UpdateTwinIfMatchETag), ClientApiVersionHelper.ApiVersionQueryString, cancellationToken); } /// <summary> /// Updates the mutable fields of <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="newTwin">New Twin object to replace with.</param> /// <param name="etag">Twin's ETag.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> ReplaceTwinAsync(string deviceId, Twin newTwin, string etag) { return ReplaceTwinAsync(deviceId, newTwin, etag, CancellationToken.None); } /// <summary> /// Updates the mutable fields of <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="newTwin">New Twin object to replace with.</param> /// <param name="etag">Twin's ETag.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> ReplaceTwinAsync(string deviceId, Twin newTwin, string etag, CancellationToken cancellationToken) { return UpdateTwinInternalAsync(deviceId, newTwin, etag, true, cancellationToken); } /// <summary> /// Updates the mutable fields of <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="newTwinJson">New Twin json to replace with.</param> /// <param name="etag">Twin's ETag.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> ReplaceTwinAsync(string deviceId, string newTwinJson, string etag) { return ReplaceTwinAsync(deviceId, newTwinJson, etag, CancellationToken.None); } /// <summary> /// Updates the mutable fields of <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="newTwinJson">New Twin json to replace with.</param> /// <param name="etag">Twin's ETag.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> ReplaceTwinAsync(string deviceId, string newTwinJson, string etag, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Replacing device twin on device: {deviceId}", nameof(ReplaceTwinAsync)); try { if (string.IsNullOrWhiteSpace(newTwinJson)) { throw new ArgumentNullException(nameof(newTwinJson)); } // TODO: Do we need to deserialize Twin, only to serialize it again? Twin twin = JsonConvert.DeserializeObject<Twin>(newTwinJson, JsonSerializerSettingsInitializer.GetJsonSerializerSettings()); return ReplaceTwinAsync(deviceId, twin, etag, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(ReplaceTwinAsync)} threw an exception: {ex}", nameof(ReplaceTwinAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Replacing device twin on device: {deviceId}", nameof(ReplaceTwinAsync)); } } /// <summary> /// Updates the mutable fields of Module's <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="moduleId">The module Id.</param> /// <param name="newTwin">New Twin object to replace with.</param> /// <param name="etag">Twin's ETag.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> ReplaceTwinAsync(string deviceId, string moduleId, Twin newTwin, string etag) { return ReplaceTwinAsync(deviceId, moduleId, newTwin, etag, CancellationToken.None); } /// <summary> /// Updates the mutable fields of Module's <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="moduleId">The module Id.</param> /// <param name="newTwin">New Twin object to replace with.</param> /// <param name="etag">Twin's ETag.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> ReplaceTwinAsync(string deviceId, string moduleId, Twin newTwin, string etag, CancellationToken cancellationToken) { return UpdateTwinInternalAsync(deviceId, moduleId, newTwin, etag, true, cancellationToken); } /// <summary> /// Updates the mutable fields of Module's <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="moduleId">The module Id.</param> /// <param name="newTwinJson">New Twin json to replace with.</param> /// <param name="etag">Twin's ETag.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> ReplaceTwinAsync(string deviceId, string moduleId, string newTwinJson, string etag) { return ReplaceTwinAsync(deviceId, moduleId, newTwinJson, etag, CancellationToken.None); } /// <summary> /// Updates the mutable fields of Module's <see cref="Twin"/> /// </summary> /// <param name="deviceId">The device Id.</param> /// <param name="moduleId">The module Id.</param> /// <param name="newTwinJson">New Twin json to replace with.</param> /// <param name="etag">Twin's ETag.</param> /// <param name="cancellationToken">Task cancellation token.</param> /// <returns>Updated Twin instance.</returns> public virtual Task<Twin> ReplaceTwinAsync(string deviceId, string moduleId, string newTwinJson, string etag, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(newTwinJson)) { throw new ArgumentNullException(nameof(newTwinJson)); } // TODO: Do we need to deserialize Twin, only to serialize it again? Twin twin = JsonConvert.DeserializeObject<Twin>(newTwinJson, JsonSerializerSettingsInitializer.GetJsonSerializerSettings()); return ReplaceTwinAsync(deviceId, moduleId, twin, etag, cancellationToken); } /// <summary> /// Register a new Configuration for Azure IoT Edge in IoT hub /// </summary> /// <param name="configuration">The Configuration object being registered.</param> /// <returns>The Configuration object.</returns> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task<Configuration> AddConfigurationAsync(Configuration configuration) { return AddConfigurationAsync(configuration, CancellationToken.None); } /// <summary> /// Register a new Configuration for Azure IoT Edge in IoT hub /// </summary> /// <param name="configuration">The Configuration object being registered.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Configuration object.</returns> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task<Configuration> AddConfigurationAsync(Configuration configuration, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Adding configuration: {configuration?.Id}", nameof(AddConfigurationAsync)); try { EnsureInstanceNotClosed(); if (!string.IsNullOrEmpty(configuration.ETag)) { throw new ArgumentException(ApiResources.ETagSetWhileCreatingConfiguration); } var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.PreconditionFailed, async responseMessage => new PreconditionFailedException( await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } }; return _httpClientHelper.PutAsync(GetConfigurationRequestUri(configuration.Id), configuration, PutOperationType.CreateEntity, errorMappingOverrides, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(AddConfigurationAsync)} threw an exception: {ex}", nameof(AddConfigurationAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Adding configuration: {configuration?.Id}", nameof(AddConfigurationAsync)); } } /// <summary> /// Retrieves the specified Configuration object. /// </summary> /// <param name="configurationId">The id of the Configuration being retrieved.</param> /// <returns>The Configuration object.</returns> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task<Configuration> GetConfigurationAsync(string configurationId) { return GetConfigurationAsync(configurationId, CancellationToken.None); } /// <summary> /// Retrieves the specified Configuration object. /// </summary> /// <param name="configurationId">The id of the Configuration being retrieved.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Configuration object.</returns> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task<Configuration> GetConfigurationAsync(string configurationId, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Getting configuration: {configurationId}", nameof(GetConfigurationAsync)); try { if (string.IsNullOrWhiteSpace(configurationId)) { throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "configurationId")); } EnsureInstanceNotClosed(); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>() { { HttpStatusCode.NotFound, async responseMessage => new ConfigurationNotFoundException( await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } }; return _httpClientHelper.GetAsync<Configuration>(GetConfigurationRequestUri(configurationId), errorMappingOverrides, null, false, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetConfigurationAsync)} threw an exception: {ex}", nameof(GetConfigurationAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Get configuration: {configurationId}", nameof(GetConfigurationAsync)); } } /// <summary> /// Retrieves specified number of configurations from every IoT hub partition. /// Results are not ordered. /// </summary> /// <returns>The list of configurations.</returns> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task<IEnumerable<Configuration>> GetConfigurationsAsync(int maxCount) { return GetConfigurationsAsync(maxCount, CancellationToken.None); } /// <summary> /// Retrieves specified number of configurations from every IoT hub partition. /// Results are not ordered. /// </summary> /// <returns>The list of configurations.</returns> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task<IEnumerable<Configuration>> GetConfigurationsAsync(int maxCount, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Getting configuration: max count: {maxCount}", nameof(GetConfigurationsAsync)); try { EnsureInstanceNotClosed(); return _httpClientHelper.GetAsync<IEnumerable<Configuration>>( GetConfigurationsRequestUri(maxCount), null, null, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(GetConfigurationsAsync)} threw an exception: {ex}", nameof(GetConfigurationsAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Getting configuration: max count: {maxCount}", nameof(GetConfigurationsAsync)); } } /// <summary> /// Update the mutable fields of the Configuration registration /// </summary> /// <param name="configuration">The Configuration object with updated fields.</param> /// <returns>The Configuration object with updated ETag.</returns> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task<Configuration> UpdateConfigurationAsync(Configuration configuration) { return UpdateConfigurationAsync(configuration, CancellationToken.None); } /// <summary> /// Update the mutable fields of the Configuration registration /// </summary> /// <param name="configuration">The Configuration object with updated fields.</param> /// <param name="forceUpdate">Forces the device object to be replaced without regard for an ETag match.</param> /// <returns>The Configuration object with updated ETags.</returns> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task<Configuration> UpdateConfigurationAsync(Configuration configuration, bool forceUpdate) { return UpdateConfigurationAsync(configuration, forceUpdate, CancellationToken.None); } /// <summary> /// Update the mutable fields of the Configuration registration /// </summary> /// <param name="configuration">The Configuration object with updated fields.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Configuration object with updated ETags.</returns> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task<Configuration> UpdateConfigurationAsync(Configuration configuration, CancellationToken cancellationToken) { return UpdateConfigurationAsync(configuration, false, cancellationToken); } /// <summary> /// Update the mutable fields of the Configuration registration /// </summary> /// <param name="configuration">The Configuration object with updated fields.</param> /// <param name="forceUpdate">Forces the Configuration object to be replaced even if it was updated since it was retrieved last time.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <returns>The Configuration object with updated ETags.</returns> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task<Configuration> UpdateConfigurationAsync(Configuration configuration, bool forceUpdate, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Updating configuration: {configuration?.Id} - Force update: {forceUpdate}", nameof(UpdateConfigurationAsync)); try { EnsureInstanceNotClosed(); if (string.IsNullOrWhiteSpace(configuration.ETag) && !forceUpdate) { throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingConfiguration); } var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>>() { { HttpStatusCode.PreconditionFailed, async (responseMessage) => new PreconditionFailedException( await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, { HttpStatusCode.NotFound, async responseMessage => { string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); return new ConfigurationNotFoundException(responseContent, (Exception)null); } } }; PutOperationType operationType = forceUpdate ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity; return _httpClientHelper.PutAsync(GetConfigurationRequestUri(configuration.Id), configuration, operationType, errorMappingOverrides, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(UpdateConfigurationAsync)} threw an exception: {ex}", nameof(UpdateConfigurationAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Updating configuration: {configuration?.Id} - Force update: {forceUpdate}", nameof(UpdateConfigurationAsync)); } } /// <summary> /// Deletes a previously registered device from the system. /// </summary> /// <param name="configurationId">The id of the Configuration being deleted.</param> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task RemoveConfigurationAsync(string configurationId) { return RemoveConfigurationAsync(configurationId, CancellationToken.None); } /// <summary> /// Deletes a previously registered device from the system. /// </summary> /// <param name="configurationId">The id of the configurationId being deleted.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task RemoveConfigurationAsync(string configurationId, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Removing configuration: {configurationId}", nameof(RemoveConfigurationAsync)); try { EnsureInstanceNotClosed(); if (string.IsNullOrWhiteSpace(configurationId)) { throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrWhitespace, "configurationId")); } // use wild-card ETag var eTag = new ETagHolder { ETag = "*" }; return RemoveConfigurationAsync(configurationId, eTag, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(RemoveConfigurationAsync)} threw an exception: {ex}", nameof(RemoveConfigurationAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Removing configuration: {configurationId}", nameof(RemoveConfigurationAsync)); } } /// <summary> /// Deletes a previously registered device from the system. /// </summary> /// <param name="configuration">The Configuration being deleted.</param> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task RemoveConfigurationAsync(Configuration configuration) { return RemoveConfigurationAsync(configuration, CancellationToken.None); } /// <summary> /// Deletes a previously registered device from the system. /// </summary> /// <param name="configuration">The Configuration being deleted.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> /// <seealso href="https://docs.microsoft.com/azure/iot-hub/iot-hub-automatic-device-management"/> public virtual Task RemoveConfigurationAsync(Configuration configuration, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Removing configuration: {configuration?.Id}", nameof(RemoveConfigurationAsync)); try { EnsureInstanceNotClosed(); return string.IsNullOrWhiteSpace(configuration.ETag) ? throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingConfiguration) : RemoveConfigurationAsync(configuration.Id, configuration, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(RemoveConfigurationAsync)} threw an exception: {ex}", nameof(RemoveConfigurationAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Removing configuration: {configuration?.Id}", nameof(RemoveConfigurationAsync)); } } /// <summary> /// Applies configuration content to an Edge device to create a deployment. /// </summary> /// <remarks><see cref="ConfigurationContent.ModulesContent"/> is required. /// <see cref="ConfigurationContent.DeviceContent"/> is optional. /// <see cref="ConfigurationContent.ModuleContent"/> is not applicable.</remarks> /// <param name="deviceId">The device Id.</param> /// <param name="content">The configuration.</param> public virtual Task ApplyConfigurationContentOnDeviceAsync(string deviceId, ConfigurationContent content) { return ApplyConfigurationContentOnDeviceAsync(deviceId, content, CancellationToken.None); } /// <summary> /// Applies configuration content to an Edge device. /// </summary> /// <remarks><see cref="ConfigurationContent.ModulesContent"/> is required. /// <see cref="ConfigurationContent.DeviceContent"/> is optional. /// <see cref="ConfigurationContent.ModuleContent"/> is not applicable.</remarks> /// <param name="deviceId">The device Id.</param> /// <param name="content">The configuration.</param> /// <param name="cancellationToken">The token which allows the operation to be canceled.</param> public virtual Task ApplyConfigurationContentOnDeviceAsync(string deviceId, ConfigurationContent content, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Applying configuration content on device: {deviceId}", nameof(ApplyConfigurationContentOnDeviceAsync)); try { return _httpClientHelper.PostAsync(GetApplyConfigurationOnDeviceRequestUri(deviceId), content, null, null, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(ApplyConfigurationContentOnDeviceAsync)} threw an exception: {ex}", nameof(ApplyConfigurationContentOnDeviceAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Applying configuration content on device: {deviceId}", nameof(ApplyConfigurationContentOnDeviceAsync)); } } private Task RemoveConfigurationAsync(string configurationId, IETagHolder eTagHolder, CancellationToken cancellationToken) { var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.NotFound, async responseMessage => { string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); return new ConfigurationNotFoundException(responseContent, (Exception) null); } }, { HttpStatusCode.PreconditionFailed, async responseMessage => new PreconditionFailedException( await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } }; return _httpClientHelper.DeleteAsync(GetConfigurationRequestUri(configurationId), eTagHolder, errorMappingOverrides, null, cancellationToken); } private Task<Twin> UpdateTwinInternalAsync(string deviceId, Twin twin, string etag, bool isReplace, CancellationToken cancellationToken) { EnsureInstanceNotClosed(); if (twin != null) { twin.DeviceId = deviceId; } ValidateTwinId(twin); if (string.IsNullOrEmpty(etag)) { throw new ArgumentNullException(nameof(etag)); } var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.PreconditionFailed, async responseMessage => new PreconditionFailedException( await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, { HttpStatusCode.NotFound, async responseMessage => new DeviceNotFoundException( await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false), (Exception)null) } }; return isReplace ? _httpClientHelper.PutAsync<Twin, Twin>( GetTwinUri(deviceId), twin, etag, etag == WildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, errorMappingOverrides, cancellationToken) : _httpClientHelper.PatchAsync<Twin, Twin>( GetTwinUri(deviceId), twin, etag, etag == WildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, errorMappingOverrides, cancellationToken); } private Task<Twin> UpdateTwinInternalAsync(string deviceId, string moduleId, Twin twin, string etag, bool isReplace, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Replacing device twin on device: {deviceId} - module: {moduleId} - is replace: {isReplace}", nameof(UpdateTwinAsync)); try { EnsureInstanceNotClosed(); if (twin != null) { twin.DeviceId = deviceId; twin.ModuleId = moduleId; } ValidateTwinId(twin); if (string.IsNullOrEmpty(etag)) { throw new ArgumentNullException(nameof(etag)); } var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.PreconditionFailed, async responseMessage => new PreconditionFailedException( await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, { HttpStatusCode.NotFound, async responseMessage => new ModuleNotFoundException( await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false), (Exception)null) } }; return isReplace ? _httpClientHelper.PutAsync<Twin, Twin>( GetModuleTwinRequestUri(deviceId, moduleId), twin, etag, etag == WildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, errorMappingOverrides, cancellationToken) : _httpClientHelper.PatchAsync<Twin, Twin>( GetModuleTwinRequestUri(deviceId, moduleId), twin, etag, etag == WildcardEtag ? PutOperationType.ForceUpdateEntity : PutOperationType.UpdateEntity, errorMappingOverrides, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(UpdateTwinAsync)} threw an exception: {ex}", nameof(UpdateTwinAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Replacing device twin on device: {deviceId} - module: {moduleId} - is replace: {isReplace}", nameof(UpdateTwinAsync)); } } private async Task<QueryResult> ExecuteQueryAsync(string sqlQueryString, int? pageSize, string continuationToken, CancellationToken cancellationToken) { EnsureInstanceNotClosed(); if (string.IsNullOrWhiteSpace(sqlQueryString)) { throw new ArgumentException(IotHubApiResources.GetString(ApiResources.ParameterCannotBeNullOrEmpty, nameof(sqlQueryString))); } var customHeaders = new Dictionary<string, string>(); if (!string.IsNullOrWhiteSpace(continuationToken)) { customHeaders.Add(ContinuationTokenHeader, continuationToken); } if (pageSize != null) { customHeaders.Add(PageSizeHeader, pageSize.ToString()); } HttpResponseMessage response = await _httpClientHelper .PostAsync( QueryDevicesRequestUri(), new QuerySpecification { Sql = sqlQueryString }, null, customHeaders, new MediaTypeHeaderValue("application/json") { CharSet = "utf-8" }, null, cancellationToken) .ConfigureAwait(false); return await QueryResult.FromHttpResponseAsync(response).ConfigureAwait(false); } private Task<JobProperties> CreateJobAsync(JobProperties jobProperties, CancellationToken ct) { EnsureInstanceNotClosed(); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.Forbidden, async (responseMessage) => new JobQuotaExceededException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false))} }; string clientApiVersion = ClientApiVersionHelper.ApiVersionQueryString; return _httpClientHelper.PostAsync<JobProperties, JobProperties>( GetJobUri("/create", clientApiVersion), jobProperties, errorMappingOverrides, null, ct); } private static Uri GetRequestUri(string deviceId) { deviceId = WebUtility.UrlEncode(deviceId); return new Uri(RequestUriFormat.FormatInvariant(deviceId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); } private static Uri GetModulesRequestUri(string deviceId, string moduleId) { deviceId = WebUtility.UrlEncode(deviceId); moduleId = WebUtility.UrlEncode(moduleId); return new Uri(ModulesRequestUriFormat.FormatInvariant(deviceId, moduleId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); } private static Uri GetModulesOnDeviceRequestUri(string deviceId) { deviceId = WebUtility.UrlEncode(deviceId); return new Uri(ModulesOnDeviceRequestUriFormat.FormatInvariant(deviceId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); } private static Uri GetModuleTwinRequestUri(string deviceId, string moduleId) { deviceId = WebUtility.UrlEncode(deviceId); moduleId = WebUtility.UrlEncode(moduleId); return new Uri(ModuleTwinUriFormat.FormatInvariant(deviceId, moduleId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); } private static Uri GetConfigurationRequestUri(string configurationId) { configurationId = WebUtility.UrlEncode(configurationId); return new Uri(ConfigurationRequestUriFormat.FormatInvariant(configurationId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); } private static Uri GetConfigurationsRequestUri(int maxCount) { return new Uri(ConfigurationsRequestUriFormat.FormatInvariant(maxCount, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); } private static Uri GetApplyConfigurationOnDeviceRequestUri(string deviceId) { return new Uri(ApplyConfigurationOnDeviceUriFormat.FormatInvariant(deviceId), UriKind.Relative); } private static Uri GetBulkRequestUri(string apiVersionQueryString) { return new Uri(RequestUriFormat.FormatInvariant(string.Empty, apiVersionQueryString), UriKind.Relative); } private static Uri GetJobUri(string jobId, string apiVersion = ClientApiVersionHelper.ApiVersionQueryString) { return new Uri(JobsUriFormat.FormatInvariant(jobId, apiVersion), UriKind.Relative); } private static Uri GetDevicesRequestUri(int maxCount) { return new Uri(DevicesRequestUriFormat.FormatInvariant(maxCount, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); } private static Uri QueryDevicesRequestUri() { return new Uri(DevicesQueryUriFormat, UriKind.Relative); } private static Uri GetAdminUri(string operation) { return new Uri(AdminUriFormat.FormatInvariant(operation, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); } private static Uri GetStatisticsUri() { return new Uri(StatisticsUriFormat, UriKind.Relative); } private static Uri GetTwinUri(string deviceId) { deviceId = WebUtility.UrlEncode(deviceId); return new Uri(TwinUriFormat.FormatInvariant(deviceId, ClientApiVersionHelper.ApiVersionQueryString), UriKind.Relative); } private static void ValidateDeviceId(Device device) { if (device == null) { throw new ArgumentNullException(nameof(device)); } if (string.IsNullOrWhiteSpace(device.Id)) { throw new ArgumentException("device.Id"); } if (!s_deviceIdRegex.IsMatch(device.Id)) { throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(device.Id)); } } private static void ValidateTwinId(Twin twin) { if (twin == null) { throw new ArgumentNullException(nameof(twin)); } if (string.IsNullOrWhiteSpace(twin.DeviceId)) { throw new ArgumentException("twin.DeviceId"); } if (!s_deviceIdRegex.IsMatch(twin.DeviceId)) { throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(twin.DeviceId)); } } private static void ValidateModuleId(Module module) { if (module == null) { throw new ArgumentNullException(nameof(module)); } if (string.IsNullOrWhiteSpace(module.DeviceId)) { throw new ArgumentException("module.Id"); } if (string.IsNullOrWhiteSpace(module.Id)) { throw new ArgumentException("module.ModuleId"); } if (!s_deviceIdRegex.IsMatch(module.DeviceId)) { throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(module.DeviceId)); } if (!s_deviceIdRegex.IsMatch(module.Id)) { throw new ArgumentException(ApiResources.DeviceIdInvalid.FormatInvariant(module.Id)); } } private static void ValidateDeviceAuthentication(AuthenticationMechanism authentication, string deviceId) { if (authentication != null) { // Both symmetric keys and X.509 cert thumbprints cannot be specified for the same device bool symmetricKeyIsSet = !authentication.SymmetricKey?.IsEmpty() ?? false; bool x509ThumbprintIsSet = !authentication.X509Thumbprint?.IsEmpty() ?? false; if (symmetricKeyIsSet && x509ThumbprintIsSet) { throw new ArgumentException(ApiResources.DeviceAuthenticationInvalid.FormatInvariant(deviceId ?? string.Empty)); } // Validate X.509 thumbprints or SymmetricKeys since we should not have both at the same time if (x509ThumbprintIsSet) { authentication.X509Thumbprint.IsValid(true); } else if (symmetricKeyIsSet) { authentication.SymmetricKey.IsValid(true); } } } private Task RemoveDeviceModuleAsync(string deviceId, string moduleId, IETagHolder eTagHolder, CancellationToken cancellationToken) { var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.NotFound, async responseMessage => { string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); return new DeviceNotFoundException(responseContent, (Exception) null); } }, { HttpStatusCode.PreconditionFailed, async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, }; return _httpClientHelper.DeleteAsync(GetModulesRequestUri(deviceId, moduleId), eTagHolder, errorMappingOverrides, null, cancellationToken); } private void EnsureInstanceNotClosed() { if (_httpClientHelper == null) { throw new ObjectDisposedException("RegistryManager", ApiResources.RegistryManagerInstanceAlreadyClosed); } } private static void NormalizeDevice(Device device) { // auto generate keys if not specified device.Authentication ??= new AuthenticationMechanism(); NormalizeAuthenticationInfo(device.Authentication); } private static void NormalizeAuthenticationInfo(AuthenticationMechanism authenticationInfo) { // to make it backward compatible we set the type according to the values // we don't set CA type - that has to be explicit if (authenticationInfo.SymmetricKey != null && !authenticationInfo.SymmetricKey.IsEmpty()) { authenticationInfo.Type = AuthenticationType.Sas; } if (authenticationInfo.X509Thumbprint != null && !authenticationInfo.X509Thumbprint.IsEmpty()) { authenticationInfo.Type = AuthenticationType.SelfSigned; } } private static void NormalizeExportImportDevice(ExportImportDevice device) { // auto generate keys if not specified device.Authentication ??= new AuthenticationMechanism(); NormalizeAuthenticationInfo(device.Authentication); } private static IEnumerable<ExportImportDevice> GenerateExportImportDeviceListForBulkOperations(IEnumerable<Device> devices, ImportMode importMode) { if (devices == null) { throw new ArgumentNullException(nameof(devices)); } if (!devices.Any()) { throw new ArgumentException($"Parameter {nameof(devices)} cannot be empty."); } var exportImportDeviceList = new List<ExportImportDevice>(devices.Count()); foreach (Device device in devices) { ValidateDeviceId(device); switch (importMode) { case ImportMode.Create: if (!string.IsNullOrWhiteSpace(device.ETag)) { throw new ArgumentException(ApiResources.ETagSetWhileRegisteringDevice); } break; case ImportMode.Update: // No preconditions break; case ImportMode.UpdateIfMatchETag: if (string.IsNullOrWhiteSpace(device.ETag)) { throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingDevice); } break; case ImportMode.Delete: // No preconditions break; case ImportMode.DeleteIfMatchETag: if (string.IsNullOrWhiteSpace(device.ETag)) { throw new ArgumentException(ApiResources.ETagNotSetWhileDeletingDevice); } break; default: throw new ArgumentException(IotHubApiResources.GetString(ApiResources.InvalidImportMode, importMode)); } var exportImportDevice = new ExportImportDevice(device, importMode); exportImportDeviceList.Add(exportImportDevice); } return exportImportDeviceList; } private static IEnumerable<ExportImportDevice> GenerateExportImportDeviceListForTwinBulkOperations(IEnumerable<Twin> twins, ImportMode importMode) { if (twins == null) { throw new ArgumentNullException(nameof(twins)); } if (!twins.Any()) { throw new ArgumentException($"Parameter {nameof(twins)} cannot be empty"); } var exportImportDeviceList = new List<ExportImportDevice>(twins.Count()); foreach (Twin twin in twins) { ValidateTwinId(twin); switch (importMode) { case ImportMode.UpdateTwin: // No preconditions break; case ImportMode.UpdateTwinIfMatchETag: if (string.IsNullOrWhiteSpace(twin.ETag)) { throw new ArgumentException(ApiResources.ETagNotSetWhileUpdatingTwin); } break; default: throw new ArgumentException(IotHubApiResources.GetString(ApiResources.InvalidImportMode, importMode)); } var exportImportDevice = new ExportImportDevice { Id = twin.DeviceId, ModuleId = twin.ModuleId, ImportMode = importMode, TwinETag = importMode == ImportMode.UpdateTwinIfMatchETag ? twin.ETag : null, Tags = twin.Tags, Properties = new ExportImportDevice.PropertyContainer(), }; exportImportDevice.Properties.DesiredProperties = twin.Properties?.Desired; exportImportDeviceList.Add(exportImportDevice); } return exportImportDeviceList; } private Task<T> BulkDeviceOperationsAsync<T>(IEnumerable<ExportImportDevice> devices, string version, CancellationToken cancellationToken) { if (Logging.IsEnabled) Logging.Enter(this, $"Performing bulk device operation on : {devices?.Count()} devices. version: {version}", nameof(BulkDeviceOperationsAsync)); try { BulkDeviceOperationSetup(devices); var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.PreconditionFailed, async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, { HttpStatusCode.RequestEntityTooLarge, async responseMessage => new TooManyDevicesException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, { HttpStatusCode.BadRequest, async responseMessage => new ArgumentException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) } }; return _httpClientHelper.PostAsync<IEnumerable<ExportImportDevice>, T>(GetBulkRequestUri(version), devices, errorMappingOverrides, null, cancellationToken); } catch (Exception ex) { if (Logging.IsEnabled) Logging.Error(this, $"{nameof(BulkDeviceOperationsAsync)} threw an exception: {ex}", nameof(BulkDeviceOperationsAsync)); throw; } finally { if (Logging.IsEnabled) Logging.Exit(this, $"Performing bulk device operation on : {devices?.Count()} devices. version: {version}", nameof(BulkDeviceOperationsAsync)); } } private void BulkDeviceOperationSetup(IEnumerable<ExportImportDevice> devices) { EnsureInstanceNotClosed(); if (devices == null) { throw new ArgumentNullException(nameof(devices)); } foreach (ExportImportDevice device in devices) { ValidateDeviceAuthentication(device.Authentication, device.Id); NormalizeExportImportDevice(device); } } private Task RemoveDeviceAsync(string deviceId, IETagHolder eTagHolder, CancellationToken cancellationToken) { var errorMappingOverrides = new Dictionary<HttpStatusCode, Func<HttpResponseMessage, Task<Exception>>> { { HttpStatusCode.NotFound, async responseMessage => { string responseContent = await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false); return new DeviceNotFoundException(responseContent, (Exception) null); } }, { HttpStatusCode.PreconditionFailed, async responseMessage => new PreconditionFailedException(await ExceptionHandlingHelper.GetExceptionMessageAsync(responseMessage).ConfigureAwait(false)) }, }; return _httpClientHelper.DeleteAsync(GetRequestUri(deviceId), eTagHolder, errorMappingOverrides, null, cancellationToken); } } }