Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update IoT hub service client init #3100

Merged
merged 1 commit into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions e2e/test/helpers/TestDevice.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public sealed class TestDevice : IDisposable
private static readonly IIotHubServiceRetryPolicy s_getRetryPolicy = new HubServiceTestRetryPolicy(s_getRetryableStatusCodes);

private X509Certificate2 _authCertificate;
private static readonly IotHubServiceClient _client = new(TestConfiguration.IotHub.ConnectionString);

private TestDevice(Device device, IAuthenticationMethod authenticationMethod)
{
Expand Down Expand Up @@ -72,7 +73,6 @@ private static async Task<TestDevice> CreateDeviceAsync(TestDeviceType type, str
string deviceName = "E2E_" + prefix + Guid.NewGuid();

// Delete existing devices named this way and create a new one.
using var serviceClient = new IotHubServiceClient(TestConfiguration.IotHub.ConnectionString);
VerboseTestLogger.WriteLine($"{nameof(GetTestDeviceAsync)}: Creating device {deviceName} with type {type}.");

IAuthenticationMethod auth = null;
Expand Down Expand Up @@ -102,7 +102,7 @@ await RetryOperationHelper
.RunWithHubServiceRetryAsync(
async () =>
{
device = await serviceClient.Devices.CreateAsync(requestDevice).ConfigureAwait(false);
device = await _client.Devices.CreateAsync(requestDevice).ConfigureAwait(false);
},
s_createRetryPolicy,
CancellationToken.None)
Expand All @@ -113,7 +113,7 @@ await RetryOperationHelper
.RunWithHubServiceRetryAsync(
async () =>
{
await serviceClient.Devices.GetAsync(requestDevice.Id).ConfigureAwait(false);
await _client.Devices.GetAsync(requestDevice.Id).ConfigureAwait(false);
},
s_getRetryPolicy,
CancellationToken.None)
Expand Down Expand Up @@ -184,13 +184,11 @@ public IotHubDeviceClient CreateDeviceClient(IotHubClientOptions options = defau

public async Task RemoveDeviceAsync()
{
using var serviceClient = new IotHubServiceClient(TestConfiguration.IotHub.ConnectionString);

await RetryOperationHelper
.RunWithHubServiceRetryAsync(
async () =>
{
await serviceClient.Devices.DeleteAsync(Id).ConfigureAwait(false);
await _client.Devices.DeleteAsync(Id).ConfigureAwait(false);
},
s_getRetryPolicy,
CancellationToken.None)
Expand Down
4 changes: 2 additions & 2 deletions iothub/service/src/ClientApiVersionHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ namespace Microsoft.Azure.Devices
internal static class ClientApiVersionHelper
{
private const string ApiVersionQueryPrefix = "api-version=";
private const string ApiVersionDefault = "2021-04-12";
internal const string ApiVersionDefault = "2021-04-12";

/// <summary>
/// The API version used in all service requests.
/// </summary>
public const string ApiVersionQueryString = ApiVersionQueryPrefix + ApiVersionDefault;
internal const string ApiVersionQueryString = ApiVersionQueryPrefix + ApiVersionDefault;
}
}
69 changes: 32 additions & 37 deletions iothub/service/src/IotHubServiceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,18 @@
namespace Microsoft.Azure.Devices
{
/// <summary>
/// The client for making service requests to IoT hub. This client contains subclients for the various feature sets
/// within IoT hub including managing device/module identities, getting/setting twin for device/modules, invoking
/// direct methods on devices/modules, and more.
/// The client for making service requests to IoT hub.
/// </summary>
/// <remarks>
/// This client is <see cref="IDisposable"/> but users are not responsible for disposing subclients within this client.
/// This client contains subclients for the various feature sets within IoT hub including managing device/module
/// identities, getting/setting twin for device/modules, invoking direct methods on devices/modules, and more.
/// <para>
/// This client creates a lifetime long instance of <see cref="HttpClient"/> that is tied to the URI of the
/// This client is <see cref="IDisposable"/>, which will dispose the subclients.
/// </para>
/// <para>
/// This client creates a lifetime-long instance of <see cref="HttpClient"/> that is tied to the URI of the
/// IoT hub specified and configured with any proxy settings provided.
/// For that reason, the instances are not static and an application using this client
/// For that reason, the HttpClient 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>
Expand All @@ -33,7 +35,7 @@ public class IotHubServiceClient : IDisposable
private readonly HttpRequestMessageFactory _httpRequestMessageFactory;
private readonly IIotHubServiceRetryPolicy _retryPolicy;
private readonly RetryHandler _retryHandler;
private const string ApiVersion = "2021-04-12";
private readonly IotHubServiceClientOptions _clientOptions;

/// <summary>
/// Creates an instance of this class. Provided for unit testing purposes only.
Expand All @@ -42,6 +44,13 @@ protected IotHubServiceClient()
{
}

private IotHubServiceClient(IotHubServiceClientOptions options)
{
_clientOptions = options?.Clone() ?? new();
_retryPolicy = _clientOptions.RetryPolicy ?? new IotHubServiceNoRetry();
_retryHandler = new RetryHandler(_retryPolicy);
}

/// <summary>
/// Create an instance of this class that authenticates service requests using an IoT hub connection string.
/// </summary>
Expand All @@ -50,23 +59,19 @@ protected IotHubServiceClient()
/// <exception cref="ArgumentNullException">Thrown when the provided connection string is null.</exception>
/// <exception cref="ArgumentException">Thrown when the provided connection string is empty or whitespace.</exception>
public IotHubServiceClient(string connectionString, IotHubServiceClientOptions options = default)
: this(options)
{
Argument.AssertNotNullOrWhiteSpace(connectionString, nameof(connectionString));
IotHubServiceClientOptions clientOptions = options != null
? options.Clone()
: new();

IotHubConnectionString iotHubConnectionString = IotHubConnectionStringParser.Parse(connectionString);
_credentialProvider = iotHubConnectionString;
_hostName = iotHubConnectionString.HostName;
_httpClient = HttpClientFactory.Create(_hostName, clientOptions);
_httpClient = HttpClientFactory.Create(_hostName, _clientOptions);
_httpRequestMessageFactory = new HttpRequestMessageFactory(
new UriBuilder(HttpClientFactory.HttpsEndpointPrefix, _hostName).Uri,
ApiVersion);
_retryPolicy = clientOptions.RetryPolicy ?? new IotHubServiceNoRetry();
_retryHandler = new RetryHandler(_retryPolicy);
ClientApiVersionHelper.ApiVersionDefault);

InitializeSubclients(clientOptions);
InitializeSubclients();
}

/// <summary>
Expand All @@ -85,24 +90,19 @@ public IotHubServiceClient(string connectionString, IotHubServiceClientOptions o
/// <exception cref="ArgumentNullException">Thrown when the provided <paramref name="hostName"/> or <paramref name="credential"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when the provided <paramref name="hostName"/> is empty or whitespace.</exception>
public IotHubServiceClient(string hostName, TokenCredential credential, IotHubServiceClientOptions options = default)
: this(options)
{
Argument.AssertNotNullOrWhiteSpace(hostName, nameof(hostName));
Argument.AssertNotNull(credential, nameof(credential));

IotHubServiceClientOptions clientOptions = options != null
? options.Clone()
: new();

_credentialProvider = new IotHubTokenCredentialProperties(hostName, credential);
_hostName = hostName;
_httpClient = HttpClientFactory.Create(_hostName, clientOptions);
_httpClient = HttpClientFactory.Create(_hostName, _clientOptions);
_httpRequestMessageFactory = new HttpRequestMessageFactory(
new UriBuilder(HttpClientFactory.HttpsEndpointPrefix, _hostName).Uri,
ApiVersion);
_retryPolicy = clientOptions.RetryPolicy ?? new IotHubServiceNoRetry();
_retryHandler = new RetryHandler(_retryPolicy);
ClientApiVersionHelper.ApiVersionDefault);

InitializeSubclients(clientOptions);
InitializeSubclients();
}

/// <summary>
Expand All @@ -120,24 +120,19 @@ public IotHubServiceClient(string hostName, TokenCredential credential, IotHubSe
/// <exception cref="ArgumentNullException">Thrown when the provided <paramref name="hostName"/> or <paramref name="credential"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when the provided <paramref name="hostName"/> is empty or whitespace.</exception>
public IotHubServiceClient(string hostName, AzureSasCredential credential, IotHubServiceClientOptions options = default)
: this(options)
{
Argument.AssertNotNullOrWhiteSpace(hostName, nameof(hostName));
Argument.AssertNotNull(credential, nameof(credential));

IotHubServiceClientOptions clientOptions = options != null
? options.Clone()
: new();

_credentialProvider = new IotHubSasCredentialProperties(hostName, credential);
_hostName = hostName;
_httpClient = HttpClientFactory.Create(_hostName, clientOptions);
_httpClient = HttpClientFactory.Create(_hostName, _clientOptions);
_httpRequestMessageFactory = new HttpRequestMessageFactory(
new UriBuilder(HttpClientFactory.HttpsEndpointPrefix, _hostName).Uri,
ApiVersion);
_retryPolicy = clientOptions.RetryPolicy ?? new IotHubServiceNoRetry();
_retryHandler = new RetryHandler(_retryPolicy);
ClientApiVersionHelper.ApiVersionDefault);

InitializeSubclients(clientOptions);
InitializeSubclients();
}

/// <summary>
Expand Down Expand Up @@ -219,7 +214,7 @@ public void Dispose()
GC.SuppressFinalize(this);
}

private void InitializeSubclients(IotHubServiceClientOptions options)
private void InitializeSubclients()
{
Devices = new DevicesClient(_hostName, _credentialProvider, _httpClient, _httpRequestMessageFactory, _retryHandler);
Modules = new ModulesClient(_hostName, _credentialProvider, _httpClient, _httpRequestMessageFactory, _retryHandler);
Expand All @@ -229,10 +224,10 @@ private void InitializeSubclients(IotHubServiceClientOptions options)
DirectMethods = new DirectMethodsClient(_hostName, _credentialProvider, _httpClient, _httpRequestMessageFactory, _retryHandler);
DigitalTwins = new DigitalTwinsClient(_hostName, _credentialProvider, _httpClient, _httpRequestMessageFactory, _retryHandler);
Twins = new TwinsClient(_hostName, _credentialProvider, _httpClient, _httpRequestMessageFactory, _retryHandler);
Messages = new MessagesClient(_hostName, _credentialProvider, _httpClient, _httpRequestMessageFactory, options, _retryHandler);
Messages = new MessagesClient(_hostName, _credentialProvider, _httpClient, _httpRequestMessageFactory, _clientOptions, _retryHandler);

MessageFeedback = new MessageFeedbackProcessorClient(_hostName, _credentialProvider, options, _retryHandler);
FileUploadNotifications = new FileUploadNotificationProcessorClient(_hostName, _credentialProvider, options, _retryHandler);
MessageFeedback = new MessageFeedbackProcessorClient(_hostName, _credentialProvider, _clientOptions, _retryHandler);
FileUploadNotifications = new FileUploadNotificationProcessorClient(_hostName, _credentialProvider, _clientOptions, _retryHandler);

// Specify the JsonSerializerSettings for subclients
JsonConvert.DefaultSettings = JsonSerializerSettingsInitializer.GetJsonSerializerSettingsDelegate();
Expand Down
8 changes: 4 additions & 4 deletions iothub/service/src/JsonSerializerSettingsInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ namespace Microsoft.Azure.Devices
/// <summary>
/// A class to initialize JsonSerializerSettings which can be applied to the project.
/// </summary>
public static class JsonSerializerSettingsInitializer
internal static class JsonSerializerSettingsInitializer
{
/// <summary>
/// A static instance of JsonSerializerSettings which sets DateParseHandling to None.
Expand All @@ -19,17 +19,17 @@ public static class JsonSerializerSettingsInitializer
/// strings to a date type, which drops trailing zeros in the microseconds date portion. By
/// specifying DateParseHandling with None, the original string will be read as-is.
/// </remarks>
public static readonly JsonSerializerSettings Settings = new()
private static readonly JsonSerializerSettings s_settings = new()
{
DateParseHandling = DateParseHandling.None
};

/// <summary>
/// Returns JsonSerializerSettings Func delegate
/// </summary>
public static Func<JsonSerializerSettings> GetJsonSerializerSettingsDelegate()
internal static Func<JsonSerializerSettings> GetJsonSerializerSettingsDelegate()
{
return () => Settings;
return () => s_settings;
}
}
}
9 changes: 2 additions & 7 deletions provisioning/service/src/ProvisioningServiceClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,7 @@ public class ProvisioningServiceClient : IDisposable
/// <exception cref="ProvisioningServiceException">Thrown if an error occurs when communicating with device provisioning service.</exception>
public ProvisioningServiceClient(string connectionString, ProvisioningServiceClientOptions options = default)
{
ProvisioningServiceClientOptions clientOptions = options != null
? options.Clone()
: new();
ProvisioningServiceClientOptions clientOptions = options?.Clone() ?? new();

Argument.AssertNotNullOrWhiteSpace(connectionString, nameof(connectionString));

Expand Down Expand Up @@ -80,10 +78,7 @@ public ProvisioningServiceClient(string connectionString, ProvisioningServiceCli
/// <inheritdoc/>
public void Dispose()
{
if (_contractApiHttp != null)
{
_contractApiHttp.Dispose();
}
_contractApiHttp?.Dispose();
GC.SuppressFinalize(this);
}
}
Expand Down