From e41a27783faf09c60bdddadb40de8a954d5d8c22 Mon Sep 17 00:00:00 2001 From: Azad Abbasi Date: Sat, 3 Oct 2020 10:55:39 -0700 Subject: [PATCH] Fix(device): Fix issue with .NET standard 5 preview -> Not supported exceptions. Fixes #1592 (#1594) --- .../src/Common/ReadOnlyMergeDictionary.cs | 154 ------------------ iothub/device/src/Common/Utils.cs | 18 ++ .../src/Transport/Mqtt/MqttIotHubAdapter.cs | 4 +- iothub/device/tests/UtilsTests.cs | 60 +++++++ 4 files changed, 80 insertions(+), 156 deletions(-) delete mode 100644 iothub/device/src/Common/ReadOnlyMergeDictionary.cs diff --git a/iothub/device/src/Common/ReadOnlyMergeDictionary.cs b/iothub/device/src/Common/ReadOnlyMergeDictionary.cs deleted file mode 100644 index 6f8dbfa065..0000000000 --- a/iothub/device/src/Common/ReadOnlyMergeDictionary.cs +++ /dev/null @@ -1,154 +0,0 @@ -// 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; -using System.Collections.Generic; -using System.Diagnostics.Contracts; - -namespace Microsoft.Azure.Devices.Client.Common -{ - internal sealed class ReadOnlyMergeDictionary : IDictionary, IList> - { - private readonly IDictionary mainDictionary; - private readonly IDictionary secondaryDictionary; - - public ReadOnlyMergeDictionary(IDictionary mainDictionary, IDictionary secondaryDictionary) - { - Contract.Requires(mainDictionary != null); - Contract.Requires(secondaryDictionary != null); - - this.mainDictionary = mainDictionary; - this.secondaryDictionary = secondaryDictionary; - } - - IEnumerator> IEnumerable>.GetEnumerator() - { - foreach (KeyValuePair pair in this.mainDictionary) - { - yield return pair; - } - - foreach (KeyValuePair pair in this.secondaryDictionary) - { - if (!this.mainDictionary.ContainsKey(pair.Key)) - { - yield return pair; - } - } - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable>)this).GetEnumerator(); - } - - void ICollection>.Add(KeyValuePair item) - { - throw new NotSupportedException(); - } - - void ICollection>.Clear() - { - throw new NotSupportedException(); - } - - bool ICollection>.Contains(KeyValuePair item) - { - throw new NotSupportedException(); - } - - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) - { - throw new NotSupportedException(); - } - - bool ICollection>.Remove(KeyValuePair item) - { - throw new NotSupportedException(); - } - - int ICollection>.Count - { - get { throw new NotSupportedException(); } - } - - bool ICollection>.IsReadOnly - { - get { return true; } - } - - bool IDictionary.ContainsKey(TKey key) - { - if (this.mainDictionary.ContainsKey(key)) - { - return true; - } - return this.secondaryDictionary.ContainsKey(key); - } - - void IDictionary.Add(TKey key, TValue value) - { - throw new NotSupportedException(); - } - - bool IDictionary.Remove(TKey key) - { - throw new NotSupportedException(); - } - - public bool TryGetValue(TKey key, out TValue value) - { - if (this.mainDictionary.TryGetValue(key, out value)) - { - return true; - } - return this.secondaryDictionary.TryGetValue(key, out value); - } - - TValue IDictionary.this[TKey key] - { - get - { - TValue value; - if (!this.TryGetValue(key, out value)) - { - throw new ArgumentException("Specified key was not found in the dictionary.", nameof(key)); - } - return value; - } - set { throw new NotSupportedException(); } - } - - ICollection IDictionary.Keys - { - get { throw new NotSupportedException(); } - } - - ICollection IDictionary.Values - { - get { throw new NotSupportedException(); } - } - - public int IndexOf(KeyValuePair item) - { - throw new NotSupportedException(); - } - - public void Insert(int index, KeyValuePair item) - { - throw new NotSupportedException(); - } - - public void RemoveAt(int index) - { - throw new NotSupportedException(); - } - - public KeyValuePair this[int index] - { - get { throw new NotSupportedException(); } - set { throw new NotSupportedException(); } - } - } -} diff --git a/iothub/device/src/Common/Utils.cs b/iothub/device/src/Common/Utils.cs index edee661dcd..7d4596e296 100644 --- a/iothub/device/src/Common/Utils.cs +++ b/iothub/device/src/Common/Utils.cs @@ -2,7 +2,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; +using System.Linq; using System.Text; using Microsoft.Azure.Devices.Client.Extensions; using Newtonsoft.Json; @@ -106,5 +109,20 @@ public static void ValidateDataIsEmptyOrJson(byte[] data) } } } + + public static IReadOnlyDictionary MergeDictionaries(IDictionary[] dictionaries) + { + // No item in the array should be null. + if (dictionaries == null || dictionaries.Any(item => item == null)) + { + throw new ArgumentNullException(nameof(dictionaries), "Provided dictionaries should not be null"); + } + + Dictionary result = dictionaries.SelectMany(dict => dict) + .ToLookup(pair => pair.Key, pair => pair.Value) + .ToDictionary(group => group.Key, group => group.First()); + + return new ReadOnlyDictionary(result); + } } } diff --git a/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs b/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs index 3e55acad80..46ae279290 100644 --- a/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs +++ b/iothub/device/src/Transport/Mqtt/MqttIotHubAdapter.cs @@ -1237,7 +1237,7 @@ public static async Task WriteMessageAsync(IChannelHandlerContext context, objec } } - internal string PopulateMessagePropertiesFromMessage(string topicName, Message message) + internal static string PopulateMessagePropertiesFromMessage(string topicName, Message message) { var systemProperties = new Dictionary(); foreach (KeyValuePair property in message.SystemProperties) @@ -1247,7 +1247,7 @@ internal string PopulateMessagePropertiesFromMessage(string topicName, Message m systemProperties[propertyName] = ConvertFromSystemProperties(property.Value); } } - string properties = UrlEncodedDictionarySerializer.Serialize(new ReadOnlyMergeDictionary(systemProperties, message.Properties)); + string properties = UrlEncodedDictionarySerializer.Serialize(Utils.MergeDictionaries(new IDictionary[] { systemProperties, message.Properties })); string msg = properties.Length != 0 ? topicName.EndsWith(SegmentSeparator, StringComparison.Ordinal) ? topicName + properties + SegmentSeparator : topicName + SegmentSeparator + properties diff --git a/iothub/device/tests/UtilsTests.cs b/iothub/device/tests/UtilsTests.cs index aa06817776..a92c6dc808 100644 --- a/iothub/device/tests/UtilsTests.cs +++ b/iothub/device/tests/UtilsTests.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace Microsoft.Azure.Devices.Client.Test @@ -35,5 +36,64 @@ public void ConvertDeliveryAckTypeToStringInvalidValueFail() { Utils.ConvertDeliveryAckTypeToString((DeliveryAcknowledgement)100500); } + + [TestMethod] + [ExpectedException(typeof(ArgumentNullException))] + public void MergeDictionaries_OneItemInListIsNull() + { + var dict1 = new Dictionary { { 1, "One" }, { 2, "Two" }, { 3, "Three" } }; + + Utils.MergeDictionaries(new Dictionary[] { dict1, null }); + } + + [TestMethod] + public void MergeDictionaries_TwoDictionaries_NoOverlap() + { + var dict1 = new Dictionary { { 1, "One" }, { 2, "Two" }, { 3, "Three" } }; + var dict2 = new Dictionary { { 4, "Four" }, { 5, "Five" }, { 6, "Six" } }; + + var result = Utils.MergeDictionaries(new Dictionary[] { dict1, dict2 }); + + Assert.AreEqual(6, result.Count, $"Number of items in the merged dictionary should be equal to {dict1.Count + dict2.Count}"); + } + + [TestMethod] + public void MergeDictionaries_ThreeDictionaries_NoOverlap() + { + var dict1 = new Dictionary { { 1, "One" }, { 2, "Two" }, { 3, "Three" } }; + var dict2 = new Dictionary { { 4, "Four" }, { 5, "Five" }, { 6, "Six" } }; + var dict3 = new Dictionary { { 7, "Seven" }, { 8, "Eight" }, { 9, "Nine" } }; + + var result = Utils.MergeDictionaries(new Dictionary[] { dict1, dict2, dict3 }); + + Assert.AreEqual(9, result.Count, $"Number of items in the merged dictionary should be equal to {dict1.Count + dict2.Count + dict3.Count}"); + } + + [TestMethod] + public void MergeDictionaries_TwoDictionaries_OneOverLap_PicksFirst() + { + var dict1 = new Dictionary { { 1, "One" }, { 2, "Two" }, { 3, "Three" } }; + var dict2 = new Dictionary { { 1, "One1" }, { 4, "Four" }, { 5, "Five" } }; + + var result = Utils.MergeDictionaries(new Dictionary[] { dict1, dict2 }); + + // There is one overlapping pair, we should pick the first one. + Assert.AreEqual(5, result.Count, $"Number of items in the merged dictionary should be equal to {dict1.Count + dict2.Count - 1}"); + Assert.AreEqual(dict1[1], result[1], $"The first item in the list takes priority"); + } + + [TestMethod] + public void MergeDictionaries_ThreeDictionaries_OneOverLap_PicksFirst() + { + var dict1 = new Dictionary { { 1, "One" }, { 2, "Two" }, { 3, "Three" } }; + var dict2 = new Dictionary { { 1, "One1" }, { 4, "Four" }, { 5, "Five" } }; + var dict3 = new Dictionary { { 1, "One2" }, { 6, "Six" }, { 7, "Seven" } }; + + var result = Utils.MergeDictionaries(new Dictionary[] { dict1, dict2, dict3 }); + + // There is one overlapping pair, we should pick the first one. + Assert.AreEqual(7, result.Count, $"Number of items in the merged dictionary should be equal to {dict1.Count + dict2.Count + dict3.Count - 2}"); + Assert.AreEqual(dict1[1], result[1], $"The first item in the list takes priority"); + } } }