Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@ public FilesClient() { }
}
public enum IfMatchPrecondition
{
Unconditional = 0,
UnconditionalIfMatch = 1,
IfMatch = 2,
UnconditionalIfMatch = 0,
IfMatch = 1,
}
public partial class IoTHubServiceClient
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ public async Task<ModuleIdentity> UpdateModuleIdentityAsync(string deviceId, str
Console.WriteLine($"Updating module identity with Id: '{moduleIdentity.ModuleId}'. Setting 'ManagedBy' property to: '{Environment.UserName}'");
moduleIdentity.ManagedBy = Environment.UserName;

Response<ModuleIdentity> response = await IoTHubServiceClient.Modules.CreateOrUpdateIdentityAsync(moduleIdentity, IfMatchPrecondition.UnconditionalIfMatch);
Response<ModuleIdentity> response = await IoTHubServiceClient.Modules.CreateOrUpdateIdentityAsync(moduleIdentity);

ModuleIdentity updatedModule = response.Value;

Expand Down Expand Up @@ -256,7 +256,7 @@ public async Task<TwinData> UpdateModuleTwinAsync(string deviceId, string module

moduleTwin.Properties.Desired.Add(new KeyValuePair<string, object>(userPropName, Environment.UserName));

Response<TwinData> response = await IoTHubServiceClient.Modules.UpdateTwinAsync(moduleTwin, IfMatchPrecondition.UnconditionalIfMatch);
Response<TwinData> response = await IoTHubServiceClient.Modules.UpdateTwinAsync(moduleTwin);

TwinData updatedTwin = response.Value;

Expand Down Expand Up @@ -294,7 +294,7 @@ public async Task DeleteModuleIdentityAsync(string deviceId, string moduleId)
Console.WriteLine($"Deleting module identity: DeviceId: '{moduleIdentity.DeviceId}', ModuleId: '{moduleIdentity.ModuleId}', ETag: '{moduleIdentity.Etag}'");

// We use UnconditionalIfMatch to force delete the Module Identity (disregard the IfMatch ETag).
Response response = await IoTHubServiceClient.Modules.DeleteIdentityAsync(moduleIdentity, IfMatchPrecondition.UnconditionalIfMatch);
Response response = await IoTHubServiceClient.Modules.DeleteIdentityAsync(moduleIdentity);

SampleLogger.PrintSuccess($"Successfully deleted module identity: DeviceId: '{deviceId}', ModuleId: '{moduleId}'");
}
Expand All @@ -320,8 +320,7 @@ public async Task DeleteDeviceIdentityAsync(string deviceId)

Console.WriteLine($"Deleting device identity with Id: '{deviceIdentity.DeviceId}'");

// We use UnconditionalIfMatch to force delete the Device Identity (disregard the IfMatch ETag).
Response response = await IoTHubServiceClient.Devices.DeleteIdentityAsync(deviceIdentity, IfMatchPrecondition.UnconditionalIfMatch);
Response response = await IoTHubServiceClient.Devices.DeleteIdentityAsync(deviceIdentity);

SampleLogger.PrintSuccess($"Successfully deleted device identity with Id: '{deviceIdentity.DeviceId}'");
}
Expand Down
8 changes: 4 additions & 4 deletions sdk/iot/Azure.Iot.Hub.Service/src/DevicesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ internal DevicesClient(RegistryManagerRestClient registryManagerClient, TwinRest
/// Create or update a device identity.
/// </summary>
/// <param name="deviceIdentity">the device identity to create.</param>
/// <param name="precondition">The condition on which to perform this operation. To create a device identity, this value must be equal to <see cref="IfMatchPrecondition.Unconditional"/>.</param>
/// <param name="precondition">The condition on which to perform this operation. To create a device identity, this value must be equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The created device identity and the http response <see cref="Response{T}"/>.</returns>
public virtual Task<Response<DeviceIdentity>> CreateOrUpdateIdentityAsync(
Expand All @@ -61,7 +61,7 @@ public virtual Task<Response<DeviceIdentity>> CreateOrUpdateIdentityAsync(
/// Create or update a device identity.
/// </summary>
/// <param name="deviceIdentity">the device identity to create.</param>
/// <param name="precondition">The condition on which to perform this operation. To create a device identity, this value must be equal to <see cref="IfMatchPrecondition.Unconditional"/>.</param>
/// <param name="precondition">The condition on which to perform this operation. To create a device identity, this value must be equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The created device identity and the http response <see cref="Response{T}"/>.</returns>
public virtual Response<DeviceIdentity> CreateOrUpdateIdentity(
Expand Down Expand Up @@ -99,7 +99,7 @@ public virtual Response<DeviceIdentity> GetIdentity(string deviceId, Cancellatio
/// <summary>
/// Delete a single device identity.
/// </summary>
/// <param name="deviceIdentity">the device identity to delete. If no ETag is present on the device, then the condition must be equal to <see cref="IfMatchPrecondition.Unconditional"/> or equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="deviceIdentity">the device identity to delete. If no ETag is present on the device, then the condition must be equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/> or equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="precondition">The condition on which to delete the device.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The http response <see cref="Response{T}"/>.</returns>
Expand All @@ -116,7 +116,7 @@ public virtual Task<Response> DeleteIdentityAsync(
/// <summary>
/// Delete a single device identity.
/// </summary>
/// <param name="deviceIdentity">the device identity to delete. If no ETag is present on the device, then the condition must be equal to <see cref="IfMatchPrecondition.Unconditional"/> or equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="deviceIdentity">the device identity to delete. If no ETag is present on the device, then the condition must be equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/> or equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="precondition">The condition on which to delete the device.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The http response <see cref="Response{T}"/>.</returns>
Expand Down
6 changes: 0 additions & 6 deletions sdk/iot/Azure.Iot.Hub.Service/src/IfMatchPrecondition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ namespace Azure.Iot.Hub.Service
/// </summary>
public enum IfMatchPrecondition
{
/// <summary>
/// Perform this operation regardless of if the provided resource matches the service's representation
/// of the object. This will cause the HTTP request to be sent with no ifMatch header. The service will never respond with a 412 error code with this setting.
/// </summary>
Unconditional,

/// <summary>
/// Perform this operation as long as the provided resource exists in the service. This will cause the HTTP request to be sent with an ifMatch header with value "*". For create or update
/// operations, if the resource does not exist, then the service will not execute the operation and will respond to the request with a 412 error code. For delete operations, if the resource
Expand Down
16 changes: 5 additions & 11 deletions sdk/iot/Azure.Iot.Hub.Service/src/IfMatchPreconditionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,12 @@ internal static class IfMatchPreconditionExtensions
/// <returns>The ifMatch header value.</returns>
internal static string GetIfMatchHeaderValue(IfMatchPrecondition precondition, string ETag)
{
if (precondition == IfMatchPrecondition.IfMatch)
return precondition switch
{
return ETag;
}
else if (precondition == IfMatchPrecondition.UnconditionalIfMatch)
{
return "*";
}
else //precondition == IfMatchPrecondition.Unconditional
{
return null;
}
IfMatchPrecondition.IfMatch => $"\"{ETag}\"",
IfMatchPrecondition.UnconditionalIfMatch => "*",
_ => null,
};
}
}
}
8 changes: 4 additions & 4 deletions sdk/iot/Azure.Iot.Hub.Service/src/ModulesClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ internal ModulesClient(RegistryManagerRestClient registryManagerClient, TwinRest
/// Create a module identity.
/// </summary>
/// <param name="moduleIdentity">The module identity to create.</param>
/// <param name="precondition">The condition on which to perform this operation. To create a module identity, this value must be equal to <see cref="IfMatchPrecondition.Unconditional"/>.</param>
/// <param name="precondition">The condition on which to perform this operation. To create a module identity, this value must be equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The created module identity and the http response <see cref="Response{T}"/>.</returns>
public virtual Task<Response<ModuleIdentity>> CreateOrUpdateIdentityAsync(
Expand All @@ -59,7 +59,7 @@ public virtual Task<Response<ModuleIdentity>> CreateOrUpdateIdentityAsync(
/// Create a module identity.
/// </summary>
/// <param name="moduleIdentity">The module identity to create.</param>
/// <param name="precondition">The condition on which to perform this operation. To create a module identity, this value must be equal to <see cref="IfMatchPrecondition.Unconditional"/>.</param>
/// <param name="precondition">The condition on which to perform this operation. To create a module identity, this value must be equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The created module identity and the http response <see cref="Response{T}"/>.</returns>
public virtual Response<ModuleIdentity> CreateOrUpdateIdentity(
Expand Down Expand Up @@ -123,7 +123,7 @@ public virtual Response<IReadOnlyList<ModuleIdentity>> GetIdentities(string devi
/// <summary>
/// Delete a single module identity.
/// </summary>
/// <param name="moduleIdentity">The module identity to delete. If no ETag is present on the module identity, then the condition must be equal to <see cref="IfMatchPrecondition.Unconditional"/> or equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="moduleIdentity">The module identity to delete. If no ETag is present on the module identity, then the condition must be equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/> or equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="precondition">The condition on which to delete the module identity.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The http response <see cref="Response{T}"/>.</returns>
Expand All @@ -140,7 +140,7 @@ public virtual Task<Response> DeleteIdentityAsync(
/// <summary>
/// Delete a single module identity.
/// </summary>
/// <param name="moduleIdentity">The module identity to delete. If no ETag is present on the module identity, then the condition must be equal to <see cref="IfMatchPrecondition.Unconditional"/> or equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="moduleIdentity">The module identity to delete. If no ETag is present on the module identity, then the condition must be equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/> or equal to <see cref="IfMatchPrecondition.UnconditionalIfMatch"/>.</param>
/// <param name="precondition">The condition on which to delete the module identity.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The http response <see cref="Response{T}"/>.</returns>
Expand Down
52 changes: 52 additions & 0 deletions sdk/iot/Azure.Iot.Hub.Service/tests/DevicesClientTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,58 @@ public async Task DevicesClient_IdentityLifecycle()
}
}

/// <summary>
/// Test the logic for ETag if-match header
/// </summary>
[Test]
public async Task DevicesClient_UpdateDevice_EtagDoesNotMatch()
{
string testDeviceId = $"UpdateWithETag{GetRandom()}";

DeviceIdentity device = null;
IoTHubServiceClient client = GetClient();

try
{
// Create a device
Response<DeviceIdentity> createResponse = await client.Devices.CreateOrUpdateIdentityAsync(
new Models.DeviceIdentity
{
DeviceId = testDeviceId
}).ConfigureAwait(false);

// Store the device object to later update it with invalid ETag
device = createResponse.Value;

// Update the device to get a new ETag value.
device.Status = DeviceStatus.Disabled;
Response<DeviceIdentity> getResponse = await client.Devices.CreateOrUpdateIdentityAsync(device).ConfigureAwait(false);
DeviceIdentity updatedDevice = getResponse.Value;

Assert.AreNotEqual(updatedDevice.Etag, device.Etag, "ETag should have been updated.");

// Perform another update using the old device object to verify precondition fails.
device.Status = DeviceStatus.Enabled;
try
{
Response<DeviceIdentity> updateResponse = await client.Devices.CreateOrUpdateIdentityAsync(device).ConfigureAwait(false);
Assert.Fail($"Update call with outdated ETag should fail with 412 (PreconditionFailed)");
}
// We will catch the exception and verify status is 412 (PreconditionfFailed)
catch (RequestFailedException ex)
{
Assert.AreEqual(412, ex.Status, $"Expected the update to fail with http status code 412 (PreconditionFailed)");
}

// Perform the same update and ignore the ETag value by providing UnconditionalIfMatch precondition
await client.Devices.CreateOrUpdateIdentityAsync(device, IfMatchPrecondition.UnconditionalIfMatch).ConfigureAwait(false);
}
finally
{
await Cleanup(client, device);
}
}

/// <summary>
/// Test basic operations of a Device Twin.
/// </summary>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading