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

TryGetValue methods should check for the component identifier when applicable and should not throw any exceptions #2111

Merged
merged 8 commits into from
Jul 20, 2021
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
74 changes: 56 additions & 18 deletions iothub/device/src/ClientPropertyCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,9 @@ public bool Contains(string componentName, string propertyName)
/// <typeparam name="T">The type to cast the object to.</typeparam>
/// <param name="componentName">The component which holds the required property.</param>
/// <param name="propertyName">The property to get.</param>
/// <param name="propertyValue">The value of the component-level property.</param>
/// <returns>true if the property collection contains a component level property with the specified key; otherwise, false.</returns>
/// <param name="propertyValue">When this method returns true, this contains the value of the component-level property.
/// When this method returns false, this contains the default value of the type <c>T</c> passed in.</param>
/// <returns>True if a component-level property of type <c>T</c> with the specified key was found; otherwise, it returns false.</returns>
public virtual bool TryGetValue<T>(string componentName, string propertyName, out T propertyValue)
{
if (Logging.IsEnabled && Convention == null)
Expand All @@ -180,35 +181,72 @@ public virtual bool TryGetValue<T>(string componentName, string propertyName, ou
$"TryGetValue will attempt to get the property value but may not behave as expected.", nameof(TryGetValue));
}

// If either the component name or the property name is null, empty or whitespace,
abhipsaMisra marked this conversation as resolved.
Show resolved Hide resolved
// then return false with the default value of the type <T> passed in.
if (string.IsNullOrWhiteSpace(componentName) || string.IsNullOrWhiteSpace(propertyName))
{
propertyValue = default;
return false;
}

if (Contains(componentName, propertyName))
{
object componentProperties = Collection[componentName];

// If the ClientPropertyCollection was constructed by the user application (eg. for updating the client properties)
// then the componentProperties are retrieved as a dictionary.
// The required property value can be fetched from the dictionary directly.
if (componentProperties is IDictionary<string, object> nestedDictionary)
{
if (nestedDictionary.TryGetValue(propertyName, out object dictionaryElement))
// First verify that the retrieved dictionary contains the component identifier { "__t": "c" }.
// If not, then the retrieved nested dictionary is actually a root-level property of type map.
if (nestedDictionary.TryGetValue(ConventionBasedConstants.ComponentIdentifierKey, out object componentIdentifierValue)
&& componentIdentifierValue.ToString() == ConventionBasedConstants.ComponentIdentifierValue)
{
// If the value is null, go ahead and return it.
if (dictionaryElement == null)
{
propertyValue = default;
return true;
}

// If the object is of type T or can be cast to type T, go ahead and return it.
if (dictionaryElement is T valueRef
|| NumericHelpers.TryCastNumericTo(dictionaryElement, out valueRef))
if (nestedDictionary.TryGetValue(propertyName, out object dictionaryElement))
{
propertyValue = valueRef;
return true;
// If the value associated with the key is null, then return true with the default value of the type <T> passed in.
if (dictionaryElement == null)
{
propertyValue = default;
return true;
}

// If the object is of type T or can be cast to type T, go ahead and return it.
if (dictionaryElement is T valueRef
|| NumericHelpers.TryCastNumericTo(dictionaryElement, out valueRef))
{
propertyValue = valueRef;
return true;
}
}
}
}
else
{
// If it's not, we need to try to convert it using the serializer.
Convention.PayloadSerializer.TryGetNestedObjectValue<T>(componentProperties, propertyName, out propertyValue);
return true;
// If the ClientPropertyCollection was constructed by the SDK (eg. when retrieving the client properties)
// then the componentProperties are retrieved as the json object that is defined in the PayloadConvention.
// The required property value then needs to be deserialized accordingly.
try
{
// First verify that the retrieved dictionary contains the component identifier { "__t": "c" }.
// If not, then the retrieved nested dictionary is actually a root-level property of type map.
if (Convention
.PayloadSerializer
.TryGetNestedObjectValue(componentProperties, ConventionBasedConstants.ComponentIdentifierKey, out string componentIdentifierValue)
&& componentIdentifierValue == ConventionBasedConstants.ComponentIdentifierValue)
{
// Since the value cannot be cast to <T> directly, we need to try to convert it using the serializer.
// If it can be successfully converted, go ahead and return it.
Convention.PayloadSerializer.TryGetNestedObjectValue<T>(componentProperties, propertyName, out propertyValue);
return true;
}
}
catch
{
// In case the value cannot be converted using the serializer,
// then return false with the default value of the type <T> passed in.
}
}
}

Expand Down
32 changes: 23 additions & 9 deletions iothub/device/src/PayloadCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,11 @@ public bool Contains(string key)
/// <summary>
/// Gets the value of the object from the collection.
/// </summary>
/// <remarks>
/// This class is used for both sending and receiving properties for the device.
/// </remarks>
/// <typeparam name="T">The type to cast the object to.</typeparam>
/// <param name="key">The key of the property to get.</param>
/// <param name="value">The value of the object from the collection.</param>
/// <returns>True if the collection contains an element with the specified key; otherwise, it returns false.</returns>
/// <param name="value">When this method returns true, this contains the value of the object from the collection.
/// When this method returns false, this contains the default value of the type <c>T</c> passed in.</param>
/// <returns>True if a value of type <c>T</c> with the specified key was found; otherwise, it returns false.</returns>
public bool TryGetValue<T>(string key, out T value)
{
if (Logging.IsEnabled && Convention == null)
Expand All @@ -122,9 +120,16 @@ public bool TryGetValue<T>(string key, out T value)
$"TryGetValue will attempt to get the property value but may not behave as expected.", nameof(TryGetValue));
}

// If the key is null, empty or whitespace, then return false with the default value of the type <T> passed in.
if (string.IsNullOrWhiteSpace(key))
{
value = default;
return false;
}

if (Collection.ContainsKey(key))
{
// If the value is null, go ahead and return it.
// If the value associated with the key is null, then return true with the default value of the type <T> passed in.
if (Collection[key] == null)
{
value = default;
Expand All @@ -139,9 +144,18 @@ public bool TryGetValue<T>(string key, out T value)
return true;
}

// If it's not, we need to try to convert it using the serializer.
value = Convention.PayloadSerializer.ConvertFromObject<T>(Collection[key]);
return true;
try
{
// If the value cannot be cast to <T> directly, we need to try to convert it using the serializer.
// If it can be successfully converted, go ahead and return it.
value = Convention.PayloadSerializer.ConvertFromObject<T>(Collection[key]);
return true;
}
catch
{
// In case the value cannot be converted using the serializer,
// then return false with the default value of the type <T> passed in.
}
}

value = default;
Expand Down
Loading