Add support to get/set value of tag/property at any level
In previous implementation, there is a limitation in the Get/Set method of TwinExtension: only the value at top level could be get/set. In other words, Twin.Tags.Get("Location.City") is not supported (it will cause exception raised in SDK). In this code change, we are going to enable get/set at any level.
This commit is contained in:
Родитель
3d279c128c
Коммит
e1d07fa1b8
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Extensions
|
||||
|
@ -21,6 +22,11 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Extension
|
|||
/// <returns>Enumerator returns the flat name and value</returns>
|
||||
static public IEnumerable<KeyValuePair<string, TwinValue>> AsEnumerableFlatten(this TwinCollection collection, string prefix = "")
|
||||
{
|
||||
if (collection == null)
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, object> pair in collection)
|
||||
{
|
||||
if (pair.Value is TwinCollection)
|
||||
|
@ -47,12 +53,12 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Extension
|
|||
LastUpdated = (pair.Value as TwinCollectionValue)?.GetLastUpdated()
|
||||
});
|
||||
}
|
||||
#if DEBUG
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
throw new ApplicationException($"Unexpected TwinCollection item type: {pair.Value.GetType().FullName} @ {prefix}{pair.Key}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -76,12 +82,192 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Extension
|
|||
LastUpdated = (child.Value as TwinCollectionValue)?.GetLastUpdated()
|
||||
});
|
||||
}
|
||||
#if DEBUG
|
||||
else
|
||||
{
|
||||
#if DEBUG
|
||||
throw new ApplicationException($"Unexpected TwinCollection item JTokenType: {child.Value.Type} @ {prefix}{child.Name}");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get value of given tag or property specified by the flat name
|
||||
/// </summary>
|
||||
/// <param name="collection">Twin.Tag, Twin.Properties.Desired or Twin.Properties.Reported</param>
|
||||
/// <param name="flatName">The flat name with prefix such as 'tags.' and so on</param>
|
||||
/// <returns>The value in dynamic, or null in case error</returns>
|
||||
static public dynamic Get(this TwinCollection collection, string flatName)
|
||||
{
|
||||
if (collection == null || string.IsNullOrWhiteSpace(flatName))
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
return Get(collection, flatName.Split('.'));
|
||||
}
|
||||
|
||||
static private dynamic Get(this TwinCollection collection, IEnumerable<string> names)
|
||||
{
|
||||
var name = names.First();
|
||||
|
||||
// Pick node on current level
|
||||
if (!collection.Contains(name))
|
||||
{
|
||||
// No desired node found. Return null as error
|
||||
return null;
|
||||
}
|
||||
var child = collection[name];
|
||||
|
||||
if (names.Count() == 1)
|
||||
{
|
||||
// Current node is the target node, , return the value
|
||||
return child;
|
||||
}
|
||||
else if (child is TwinCollection)
|
||||
{
|
||||
// Current node is container, go to next level
|
||||
return Get(child as TwinCollection, names.Skip(1));
|
||||
}
|
||||
else if (child is JContainer)
|
||||
{
|
||||
// Current node is container, go to next level
|
||||
return Get(child as JContainer, names.Skip(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Currently, the container could only be TwinCollection or JContainer
|
||||
#if DEBUG
|
||||
throw new ApplicationException($"Unexpected TwinCollection item type: {child.GetType().FullName} @ ...{name}");
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static private dynamic Get(this JContainer container, IEnumerable<string> names)
|
||||
{
|
||||
var name = names.First();
|
||||
|
||||
// Pick node on current level
|
||||
var child = container[name];
|
||||
if (child == null)
|
||||
{
|
||||
// No desired node found. Return null as error
|
||||
return null;
|
||||
}
|
||||
|
||||
if (names.Count() == 1)
|
||||
{
|
||||
// Current node is the target node, return the value
|
||||
return child;
|
||||
}
|
||||
else if (child is JContainer)
|
||||
{
|
||||
// Current node is container, go to next level
|
||||
return Get(child as JContainer, names.Skip(1));
|
||||
}
|
||||
else
|
||||
{
|
||||
// The next level of JContainer must be JContainer
|
||||
#if DEBUG
|
||||
throw new ApplicationException($"Unexpected TwinCollection item JTokenType: {child.Type} @ {child.Path}");
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add/Set value of given tag or property
|
||||
/// Reminder: it is not allow to set value on the node has children
|
||||
/// </summary>
|
||||
/// <param name="collection">Twin.Tag, Twin.Properties.Desired or Twin.Properties.Reported</param>
|
||||
/// <param name="flatName">The flat name with prefix such as 'tags.' and so on</param>
|
||||
/// <param name="value">The value to be set</param>
|
||||
static public void Set(this TwinCollection collection, string flatName, dynamic value)
|
||||
{
|
||||
if (collection == null || string.IsNullOrWhiteSpace(flatName))
|
||||
{
|
||||
throw new ArgumentNullException();
|
||||
}
|
||||
|
||||
Set(collection, flatName.Split('.'), value);
|
||||
}
|
||||
|
||||
static private void Set(this TwinCollection collection, IEnumerable<string> names, dynamic value)
|
||||
{
|
||||
var name = names.First();
|
||||
|
||||
if (names.Count() == 1)
|
||||
{
|
||||
// Current node is the target node, set the value
|
||||
collection[name] = value;
|
||||
}
|
||||
else if (!collection.Contains(name))
|
||||
{
|
||||
// Current node is container, go to next level
|
||||
// The target collection is not exist, create and add it
|
||||
// Reminder: the 'add' operation perform 'copy' rather than 'link'
|
||||
var newCollection = new TwinCollection();
|
||||
Set(newCollection, names.Skip(1), value);
|
||||
collection[name] = newCollection;
|
||||
}
|
||||
else
|
||||
{
|
||||
var child = collection[name];
|
||||
if (child is TwinCollection)
|
||||
{
|
||||
// The target collection is there. Go to next level
|
||||
Set(child as TwinCollection, names.Skip(1), value);
|
||||
}
|
||||
else if (child is JContainer)
|
||||
{
|
||||
// The target collection is there. Go to next level
|
||||
Set(child as JContainer, names.Skip(1), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Currently, the container could only be TwinCollection or JContainer
|
||||
#if DEBUG
|
||||
throw new ApplicationException($"Unexpected TwinCollection item type: {child.GetType().FullName} @ ...{name}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static private void Set(this JContainer container, IEnumerable<string> names, dynamic value)
|
||||
{
|
||||
var name = names.First();
|
||||
|
||||
if (names.Count() == 1)
|
||||
{
|
||||
// Current node is the target node, set the value
|
||||
container[name] = value;
|
||||
}
|
||||
else if (!container.Contains(name))
|
||||
{
|
||||
// The target container is not exist, create and add it
|
||||
var newContainer = new JObject();
|
||||
Set(newContainer, names.Skip(1), value);
|
||||
container[name] = newContainer;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Current node is container, go to next level
|
||||
var child = container[name];
|
||||
if (child is JContainer)
|
||||
{
|
||||
Set(child as JContainer, names.Skip(1), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
// The next level of JContainer must be JContainer
|
||||
#if DEBUG
|
||||
throw new ApplicationException($"Unexpected TwinCollection item JTokenType: {child.Type} @ {child.Path}");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Extension
|
|||
if (flatName.TryTrimPrefix(selector.Key, out name))
|
||||
{
|
||||
var collection = selector.Value(twin);
|
||||
return collection.Contains(name) ? collection[name] : null;
|
||||
return TwinCollectionExtension.Get(collection, name);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,7 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.Common.Extension
|
|||
{
|
||||
if (flatName.TryTrimPrefix(selector.Key, out name))
|
||||
{
|
||||
selector.Value(twin)[name] = value;
|
||||
TwinCollectionExtension.Set(selector.Value(twin), name, value);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,6 +48,49 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.UnitTests.Common
|
|||
Assert.Equal((string)tags[7].Value.Value.Value, "Device001");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetTest()
|
||||
{
|
||||
var twin = BuildRetrievedTwin();
|
||||
|
||||
Assert.Equal(twin.Tags.Get("DisplayName").ToString(), "Device001");
|
||||
Assert.Equal(twin.Tags.Get("Location.City").ToString(), "Beijing");
|
||||
Assert.Equal((double)twin.Tags.Get("LastTelemetry.Telemetry.Temperature"), 30.5);
|
||||
|
||||
Assert.Null(twin.Tags.Get("x"));
|
||||
Assert.Throws<ArgumentNullException>(() => twin.Tags.Get(null));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SetTest()
|
||||
{
|
||||
var twin = BuildRetrievedTwin();
|
||||
|
||||
// Replace leaf
|
||||
twin.Tags.Set("Location.City", "Shanghai");
|
||||
Assert.Equal(twin.Tags.Get("Location.City").ToString(), "Shanghai");
|
||||
|
||||
// Add leaf (same level)
|
||||
twin.Tags.Set("Location.Dummy", "n/a");
|
||||
Assert.Equal(twin.Tags.Get("Location.Dummy").ToString(), "n/a");
|
||||
|
||||
// Add left (new level)
|
||||
twin.Tags.Set("Location.City.Short", "SH");
|
||||
Assert.Equal(twin.Tags.Get("Location.City.Short").ToString(), "SH");
|
||||
|
||||
// Replace intermedia node
|
||||
twin.Tags.Set("LastTelemetry.Telemetry", 3);
|
||||
Assert.Equal((int)twin.Tags.Get("LastTelemetry.Telemetry"), 3);
|
||||
|
||||
// Replace leaf
|
||||
twin.Properties.Desired.Set("FirmwareVersion.Minor", 1);
|
||||
Assert.Equal((int)twin.Properties.Desired.Get("FirmwareVersion.Minor"), 1);
|
||||
|
||||
// Replace intermedia node
|
||||
twin.Properties.Desired.Set("FirmwareVersion.Build", 1002);
|
||||
Assert.Equal((int)twin.Properties.Desired.Get("FirmwareVersion.Build"), 1002);
|
||||
}
|
||||
|
||||
private Twin BuildRetrievedTwin()
|
||||
{
|
||||
var twin = new Twin();
|
||||
|
@ -71,6 +114,16 @@ namespace Microsoft.Azure.Devices.Applications.RemoteMonitoring.UnitTests.Common
|
|||
|
||||
twin.Tags["DisplayName"] = "Device001";
|
||||
|
||||
var build = new TwinCollection();
|
||||
build["Year"] = 2016;
|
||||
build["Month"] = 11;
|
||||
|
||||
var version = new TwinCollection();
|
||||
version["Major"] = 3;
|
||||
version["Minor"] = 0;
|
||||
version["Build"] = build;
|
||||
twin.Properties.Desired["FirmwareVersion"] = version;
|
||||
|
||||
return JsonConvert.DeserializeObject<Twin>(twin.ToJson());
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче