diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveActionSet.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveActionSet.cs index 967ecc76ff..53527f826b 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveActionSet.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveActionSet.cs @@ -35,6 +35,7 @@ public class AdaptiveActionSet : AdaptiveElement [XmlElement(typeof(AdaptiveShowCardAction))] [XmlElement(typeof(AdaptiveSubmitAction))] [XmlElement(typeof(AdaptiveToggleVisibilityAction))] + [XmlElement(typeof(AdaptiveUnknownAction))] #endif public List Actions { get; set; } = new List(); } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs index b55da852a5..85575b749c 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveCard.cs @@ -123,10 +123,9 @@ public AdaptiveCard() : this(new AdaptiveSchemaVersion(1, 0)) { } [JsonConverter(typeof(StringSizeWithUnitConverter), true)] [JsonProperty(Order = -4, DefaultValueHandling = DefaultValueHandling.Ignore)] #if !NETSTANDARD1_3 - [XmlElement(typeof(AdaptiveHeight))] + [XmlElement] #endif - [DefaultValue(typeof(AdaptiveHeight), "auto")] - public AdaptiveHeight Height { get; set; } + public AdaptiveHeight Height { get; set; } = new AdaptiveHeight(AdaptiveHeightType.Auto); /// /// Explicit card minimum height in pixels @@ -146,6 +145,7 @@ public AdaptiveCard() : this(new AdaptiveSchemaVersion(1, 0)) { } [JsonConverter(typeof(IgnoreEmptyItemsConverter))] #if !NETSTANDARD1_3 [XmlElement(typeof(AdaptiveTextBlock))] + [XmlElement(typeof(AdaptiveRichTextBlock))] [XmlElement(typeof(AdaptiveImage))] [XmlElement(typeof(AdaptiveContainer))] [XmlElement(typeof(AdaptiveColumnSet))] @@ -159,6 +159,7 @@ public AdaptiveCard() : this(new AdaptiveSchemaVersion(1, 0)) { } [XmlElement(typeof(AdaptiveChoiceSetInput))] [XmlElement(typeof(AdaptiveMedia))] [XmlElement(typeof(AdaptiveActionSet))] + [XmlElement(typeof(AdaptiveUnknownElement))] #endif public List Body { get; set; } = new List(); @@ -174,6 +175,7 @@ public AdaptiveCard() : this(new AdaptiveSchemaVersion(1, 0)) { } [XmlElement(typeof(AdaptiveShowCardAction))] [XmlElement(typeof(AdaptiveSubmitAction))] [XmlElement(typeof(AdaptiveToggleVisibilityAction))] + [XmlElement(typeof(AdaptiveUnknownAction))] #endif public List Actions { get; set; } = new List(); @@ -213,25 +215,7 @@ public bool ShouldSerializeJsonSchema() [DefaultValue(null)] public AdaptiveAction SelectAction { get; set; } - public bool ShouldSerializeHeight() - { - if (Height == AdaptiveHeight.Auto) - { - return false; - } - if (Height.HeightType == AdaptiveHeightType.Pixel) - { - if (!Height.Unit.HasValue) - { - return false; - } - if (Height.Unit.Value == 0) - { - return false; - } - } - return true; - } + public bool ShouldSerializeHeight() => this.Height?.ShouldSerializeAdaptiveHeight() == true; /// /// Callback that will be invoked should a null or empty version string is encountered. The callback may return an alternate version to use for parsing. diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveCollectionElement.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveCollectionElement.cs index 4dbfc3e89b..19623b11d4 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveCollectionElement.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveCollectionElement.cs @@ -16,24 +16,32 @@ namespace AdaptiveCards /// public abstract class AdaptiveCollectionElement : AdaptiveElement { - /// /// The style in which the image is displayed. /// [JsonConverter(typeof(IgnoreNullEnumConverter), true)] [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] #if !NETSTANDARD1_3 - [XmlElement] + [XmlIgnore] #endif [DefaultValue(null)] public AdaptiveContainerStyle? Style { get; set; } +#if !NETSTANDARD1_3 + // Xml Serializer doesn't handle nullable value types, but this trick allows us to serialize only if non-null + [JsonIgnore] + [XmlAttribute("Style")] + [EditorBrowsable(EditorBrowsableState.Never)] + public AdaptiveContainerStyle StyleXml { get { return (Style.HasValue) ? Style.Value : AdaptiveContainerStyle.Default; } set { Style = value; } } + public bool ShouldSerializeStyleXml() => this.Style.HasValue; +#endif + /// /// The content alignment for the element inside the container. /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] #if !NETSTANDARD1_3 - [XmlElement] + [XmlAttribute] #endif [DefaultValue(typeof(AdaptiveVerticalContentAlignment), "top")] public AdaptiveVerticalContentAlignment VerticalContentAlignment { get; set; } @@ -53,7 +61,7 @@ public abstract class AdaptiveCollectionElement : AdaptiveElement /// [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] #if !NETSTANDARD1_3 - [XmlElement] + [XmlAttribute] #endif [DefaultValue(false)] public bool Bleed { get; set; } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveContainer.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveContainer.cs index 5bf75e2ae1..707fcad84d 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveContainer.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveContainer.cs @@ -34,6 +34,7 @@ public class AdaptiveContainer : AdaptiveCollectionElement [JsonConverter(typeof(IgnoreEmptyItemsConverter))] #if !NETSTANDARD1_3 [XmlElement(typeof(AdaptiveTextBlock))] + [XmlElement(typeof(AdaptiveRichTextBlock))] [XmlElement(typeof(AdaptiveImage))] [XmlElement(typeof(AdaptiveContainer))] [XmlElement(typeof(AdaptiveColumnSet))] @@ -47,6 +48,7 @@ public class AdaptiveContainer : AdaptiveCollectionElement [XmlElement(typeof(AdaptiveToggleInput))] [XmlElement(typeof(AdaptiveMedia))] [XmlElement(typeof(AdaptiveActionSet))] + [XmlElement(typeof(AdaptiveUnknownElement))] #endif public List Items { get; set; } = new List(); diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveElement.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveElement.cs index 29c302236a..6c8c35a8f3 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveElement.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveElement.cs @@ -37,36 +37,17 @@ public abstract class AdaptiveElement : AdaptiveTypedElement [Obsolete("CardElement.Speak has been deprecated. Use AdaptiveCard.Speak", false)] public string Speak { get; set; } - public bool ShouldSerializeHeight() - { - if (Height == AdaptiveHeight.Auto) - { - return false; - } - if (Height.HeightType == AdaptiveHeightType.Pixel) - { - if (!Height.Unit.HasValue) - { - return false; - } - if (Height.Unit.Value == 0) - { - return false; - } - } - return true; - } - /// /// The amount of space the element should be separated from the previous element. Default value is . /// [JsonConverter(typeof(StringSizeWithUnitConverter), true)] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] #if !NETSTANDARD1_3 - [XmlElement(typeof(AdaptiveHeight))] + [XmlElement] #endif - [DefaultValue(typeof(AdaptiveHeight), "auto")] - public AdaptiveHeight Height { get; set; } + public AdaptiveHeight Height { get; set; } = new AdaptiveHeight(AdaptiveHeightType.Auto); + + public bool ShouldSerializeHeight() => this.Height?.ShouldSerializeAdaptiveHeight() == true; /// /// Indicates whether the element should be visible when the card has been rendered. diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveHeight.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveHeight.cs index 6e74f1cf34..0a4f6fe77b 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveHeight.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveHeight.cs @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Newtonsoft.Json; +using System; +using System.Xml.Serialization; namespace AdaptiveCards { @@ -28,30 +30,48 @@ public enum AdaptiveHeightType } - public struct AdaptiveHeight + public class AdaptiveHeight : IEquatable { public static AdaptiveHeight Auto { get; } = new AdaptiveHeight(AdaptiveHeightType.Auto); public static AdaptiveHeight Stretch { get; } = new AdaptiveHeight(AdaptiveHeightType.Stretch); - public AdaptiveHeightType _heightType; - public AdaptiveHeightType HeightType { get { return _heightType; } set { } } - - public uint? _unit; - public uint? Unit { get { return _unit; } set { } } + public AdaptiveHeight() + { + } public AdaptiveHeight(uint px) { - _heightType = AdaptiveHeightType.Pixel; - _unit = px; + HeightType = AdaptiveHeightType.Pixel; + this.Unit = px; } public AdaptiveHeight(AdaptiveHeightType heightType) { - _heightType = heightType; - _unit = null; + HeightType = heightType; + Unit = null; } + + [JsonProperty("heightType")] +#if !NETSTANDARD1_3 + [XmlAttribute] +#endif + public AdaptiveHeightType HeightType { get; set; } + + [JsonProperty("unit")] +#if !NETSTANDARD1_3 + [XmlIgnore] +#endif + public uint? Unit { get; set; } + +#if !NETSTANDARD1_3 + [XmlAttribute("Unit")] + [JsonIgnore] + public uint UnitXml { get { return Unit.HasValue ? Unit.Value : 0; } set { Unit = value; } } + public bool ShouldSerializeUnitXml() => Unit.HasValue; +#endif + public bool IsPixel() { return HeightType == AdaptiveHeightType.Pixel; @@ -63,7 +83,8 @@ public bool ShouldSerializeAdaptiveHeight() { return false; } - if( HeightType == AdaptiveHeightType.Pixel ) + + if (HeightType == AdaptiveHeightType.Pixel) { if (!Unit.HasValue) { @@ -79,23 +100,13 @@ public bool ShouldSerializeAdaptiveHeight() public static bool operator ==(AdaptiveHeight ah1, AdaptiveHeight ah2) { - if (ah1 != null && ah2 != null) - { - return ah1.Equals(ah2); - } - - if (ah1 == null && ah2 == null) - { - return true; - } - return false; + return ah1.Equals(ah2); } public static bool operator !=(AdaptiveHeight ah1, AdaptiveHeight ah2) { - return !(ah1 == ah2); + return !ah1.Equals(ah2); } - public override int GetHashCode() { if (!Unit.HasValue) @@ -107,17 +118,18 @@ public override int GetHashCode() public override bool Equals(object obj) { - if (obj is AdaptiveHeight) + return this.Equals(obj as AdaptiveHeight); + } + + public Boolean Equals(AdaptiveHeight other) + { + if (this.HeightType == other.HeightType) { - AdaptiveHeight ah = (AdaptiveHeight)obj; - if (HeightType == ah.HeightType) + if (this.HeightType == AdaptiveHeightType.Pixel) { - if (HeightType == AdaptiveHeightType.Pixel) - { - return Unit == ah.Unit; - } - return true; + return this.Unit == other.Unit; } + return true; } return false; } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveInline.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveInline.cs new file mode 100644 index 0000000000..0ec76c6b7c --- /dev/null +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveInline.cs @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using System; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace AdaptiveCards +{ + [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] + public abstract class AdaptiveInline + { + /// + /// The type name of the inline + /// + [JsonProperty(Order = -10, Required = Required.Always, DefaultValueHandling = DefaultValueHandling.Include)] +#if !NETSTANDARD1_3 + // don't serialize type with xml, because we use element name or attribute for type + [XmlIgnore] +#endif + public abstract string Type { get; set; } + + /// + /// Additional properties not found on the default schema + /// + [JsonExtensionData] +#if NETSTANDARD1_3 + public IDictionary AdditionalProperties { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); +#else + // Dictionary<> is not supported with XmlSerialization because Dictionary is not serializable, SerializableDictionary<> is + [XmlElement] + public SerializableDictionary AdditionalProperties { get; set; } = new SerializableDictionary(StringComparer.OrdinalIgnoreCase); + public bool ShouldSerializeAdditionalProperties() => this.AdditionalProperties.Count > 0; +#endif + } +} diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs index 913a0cb2cd..903ec61cc5 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveInlinesConverter.cs @@ -16,14 +16,14 @@ class AdaptiveInlinesConverter : JsonConverter public override bool CanConvert(Type objectType) { - return typeof(List).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); + return typeof(List).GetTypeInfo().IsAssignableFrom(objectType.GetTypeInfo()); } public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var array = JArray.Load(reader); List list = array.ToObject>(); - List arrayList = new List(); + List arrayList = new List(); // We only support text runs for now, which can be specified as either a string or an object foreach (object obj in list) @@ -35,7 +35,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist else { JObject jobj = (JObject)obj; - arrayList.Add((IAdaptiveInline)jobj.ToObject(typeof(AdaptiveTextRun))); + arrayList.Add((AdaptiveInline)jobj.ToObject(typeof(AdaptiveTextRun))); } } return arrayList; diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveRichTextBlock.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveRichTextBlock.cs index 89e640f259..ae7521fc2a 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveRichTextBlock.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveRichTextBlock.cs @@ -39,6 +39,6 @@ public AdaptiveRichTextBlock() #if !NETSTANDARD1_3 [XmlElement(typeof(AdaptiveTextRun))] #endif - public List Inlines { get; set; } = new List(); + public List Inlines { get; set; } = new List(); } } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTargetElement.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTargetElement.cs index e1e6c960ea..1ac0b4d06b 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveTargetElement.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTargetElement.cs @@ -6,6 +6,7 @@ using Newtonsoft.Json.Serialization; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; @@ -33,12 +34,12 @@ public AdaptiveTargetElement(string elementId, bool isVisible) /// Identifier of element to change visibility. /// #if !NETSTANDARD1_3 - [XmlIgnore] + [XmlAttribute] #endif public string ElementId { get; set; } /// - /// Value to change visibility to. + /// Value to change visibility to. /// [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] #if !NETSTANDARD1_3 @@ -46,6 +47,15 @@ public AdaptiveTargetElement(string elementId, bool isVisible) #endif public bool? IsVisible { get; set; } = null; +#if !NETSTANDARD1_3 + // Xml Serializer doesn't handle nullable value types, but this trick allows us to serialize only if non-null + [JsonIgnore] + [XmlAttribute("IsVisible")] + [EditorBrowsable(EditorBrowsableState.Never)] + public bool IsVisibleXml { get { return IsVisible.HasValue ? IsVisible.Value : true; } set { IsVisible = value; } } + public bool ShouldSerializeIsVisibleXml() => this.IsVisible.HasValue; +#endif + /// /// Implicit conversion from to . /// diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTextInput.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTextInput.cs index 0ce2b9f48e..0fadac94fe 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveTextInput.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTextInput.cs @@ -80,6 +80,7 @@ public class AdaptiveTextInput : AdaptiveInput [XmlElement(typeof(AdaptiveShowCardAction))] [XmlElement(typeof(AdaptiveSubmitAction))] [XmlElement(typeof(AdaptiveToggleVisibilityAction))] + [XmlElement(typeof(AdaptiveUnknownAction))] #endif public AdaptiveAction InlineAction { get; set; } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTextRun.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTextRun.cs index f922ba677e..23b17817a8 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveTextRun.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTextRun.cs @@ -9,13 +9,13 @@ namespace AdaptiveCards { [JsonObject(NamingStrategyType = typeof(CamelCaseNamingStrategy))] #if !NETSTANDARD1_3 - [XmlType(TypeName = AdaptiveTextBlock.TypeName)] + [XmlType(TypeName = AdaptiveTextRun.TypeName)] #endif - public class AdaptiveTextRun : IAdaptiveTextElement, IAdaptiveInline + public class AdaptiveTextRun : AdaptiveInline, IAdaptiveTextElement { public const string TypeName = "TextRun"; - public string Type { get; set; } = TypeName; + public override string Type { get; set; } = TypeName; public AdaptiveTextRun() { diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveToggleVisibilityAction.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveToggleVisibilityAction.cs index 941ecb414e..6c5dae3b6b 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveToggleVisibilityAction.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveToggleVisibilityAction.cs @@ -34,7 +34,7 @@ public class AdaptiveToggleVisibilityAction : AdaptiveAction [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] [JsonConverter(typeof(ToggleElementsConverter))] #if !NETSTANDARD1_3 - [XmlIgnore] + [XmlElement] #endif public List TargetElements { get; set; } = new List(); } diff --git a/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs index 00ec6850ee..c97f06bae2 100644 --- a/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs +++ b/source/dotnet/Library/AdaptiveCards/AdaptiveTypedElement.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Runtime.Serialization; using System.Xml.Serialization; namespace AdaptiveCards @@ -18,6 +19,7 @@ public abstract class AdaptiveTypedElement /// [JsonProperty(Order = -10, Required = Required.Always, DefaultValueHandling = DefaultValueHandling.Include)] #if !NETSTANDARD1_3 + // don't serialize type with xml, because we use element name or attribute for type [XmlIgnore] #endif public abstract string Type { get; set; } @@ -26,10 +28,14 @@ public abstract class AdaptiveTypedElement /// Additional properties not found on the default schema /// [JsonExtensionData] -#if !NETSTANDARD1_3 - [XmlIgnore] -#endif +#if NETSTANDARD1_3 public IDictionary AdditionalProperties { get; set; } = new Dictionary(StringComparer.OrdinalIgnoreCase); +#else + // Dictionary<> is not supported with XmlSerialization because Dictionary is not serializable, SerializableDictionary<> is + [XmlElement] + public SerializableDictionary AdditionalProperties { get; set; } = new SerializableDictionary(StringComparer.OrdinalIgnoreCase); + public bool ShouldSerializeAdditionalProperties() => this.AdditionalProperties.Count > 0; +#endif [JsonConverter(typeof(AdaptiveFallbackConverter))] [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)] diff --git a/source/dotnet/Library/AdaptiveCards/IAdaptiveInline.cs b/source/dotnet/Library/AdaptiveCards/IAdaptiveInline.cs deleted file mode 100644 index e0ab7c7b67..0000000000 --- a/source/dotnet/Library/AdaptiveCards/IAdaptiveInline.cs +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -namespace AdaptiveCards -{ - public interface IAdaptiveInline - { - } -} diff --git a/source/dotnet/Library/AdaptiveCards/SerializableDictionary.cs b/source/dotnet/Library/AdaptiveCards/SerializableDictionary.cs new file mode 100644 index 0000000000..c782f3236f --- /dev/null +++ b/source/dotnet/Library/AdaptiveCards/SerializableDictionary.cs @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System; +using System.Collections; +using System.Collections.Generic; +using System.Xml.Serialization; + +namespace AdaptiveCards +{ +#if !NETSTANDARD1_3 + [XmlRoot("dictionary")] + public class SerializableDictionary : Dictionary, IXmlSerializable + { + public SerializableDictionary() + : base() + { + } + + public SerializableDictionary(IEqualityComparer comparer) + : base(comparer) + { + } + + #region IXmlSerializable Members + public System.Xml.Schema.XmlSchema GetSchema() + { + return null; + } + + public void ReadXml(System.Xml.XmlReader reader) + { + XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); + XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); + + bool wasEmpty = reader.IsEmptyElement; + reader.Read(); + + if (wasEmpty) + return; + + while (reader.NodeType != System.Xml.XmlNodeType.EndElement) + { + reader.ReadStartElement("item"); + + reader.ReadStartElement("key"); + TKey key = (TKey)keySerializer.Deserialize(reader); + reader.ReadEndElement(); + + reader.ReadStartElement("value"); + TValue value = (TValue)valueSerializer.Deserialize(reader); + reader.ReadEndElement(); + + this.Add(key, value); + + reader.ReadEndElement(); + reader.MoveToContent(); + } + reader.ReadEndElement(); + } + + public void WriteXml(System.Xml.XmlWriter writer) + { + XmlSerializer keySerializer = new XmlSerializer(typeof(TKey)); + XmlSerializer valueSerializer = new XmlSerializer(typeof(TValue)); + + foreach (TKey key in this.Keys) + { + writer.WriteStartElement("item"); + + writer.WriteStartElement("key"); + keySerializer.Serialize(writer, key); + writer.WriteEndElement(); + + writer.WriteStartElement("value"); + TValue value = this[key]; + valueSerializer.Serialize(writer, value); + writer.WriteEndElement(); + + writer.WriteEndElement(); + } + } + #endregion + } +#endif +} diff --git a/source/dotnet/Test/AdaptiveCards.Test/XmlSerializationTests.cs b/source/dotnet/Test/AdaptiveCards.Test/XmlSerializationTests.cs index eef7450794..cbad8c6074 100644 --- a/source/dotnet/Test/AdaptiveCards.Test/XmlSerializationTests.cs +++ b/source/dotnet/Test/AdaptiveCards.Test/XmlSerializationTests.cs @@ -21,7 +21,7 @@ namespace AdaptiveCards.Test public class XmlSerializationTests { [TestMethod] - public void SerializeAllScenarios() + public void VerifySerializationForAllScenarioFiles() { CompareLogic compareLogic = new CompareLogic(new ComparisonConfig() { @@ -34,20 +34,34 @@ public void SerializeAllScenarios() }); XmlSerializer serializer = new XmlSerializer(typeof(AdaptiveCard)); - foreach (var file in Directory.EnumerateFiles(@"..\..\..\..\..\..\..\samples\v1.0\Scenarios")) + foreach (var version in Directory.EnumerateDirectories(@"..\..\..\..\..\..\..\samples\", "v*")) { - string json = File.ReadAllText(file); - var card = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + var folder = Path.Combine($"{version}\\scenarios"); + if (Directory.Exists(folder)) { - Converters = { new StrictIntConverter() } - }); - StringBuilder sb = new StringBuilder(); - serializer.Serialize(new StringWriter(sb), card); - string xml = sb.ToString(); - var card2 = (AdaptiveCard)serializer.Deserialize(new StringReader(xml)); + foreach (var file in Directory.EnumerateFiles(folder, "*.json")) + { + string json = File.ReadAllText(file); + var card = JsonConvert.DeserializeObject(json, new JsonSerializerSettings + { + Converters = { new StrictIntConverter() } + }); - var result = compareLogic.Compare(card, card2); - Assert.IsTrue(result.AreEqual, result.DifferencesString); + // test XML serialization round-trips + StringBuilder sb = new StringBuilder(); + serializer.Serialize(new StringWriter(sb), card); + string xml = sb.ToString(); + var card2 = (AdaptiveCard)serializer.Deserialize(new StringReader(xml)); + + var result = compareLogic.Compare(card, card2); + Assert.IsTrue(result.AreEqual, $"XML serialization different: {Path.GetFullPath(file)}: {result.DifferencesString}"); + + // test JSON serialization round-trips + var card3 = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(card)); + result = compareLogic.Compare(card, card3); + Assert.IsTrue(result.AreEqual, $"JSON Serialization different: {Path.GetFullPath(file)}: {result.DifferencesString}"); + } + } } } }