using System;
using System.Collections.Generic;
+using System.Globalization;
using System.IO;
-using System.Linq;
+using System.Text;
using System.Xml;
namespace Microsoft.Extensions.Configuration.Xml
/// <returns>The <see cref="IDictionary{String, String}"/> which was read from the stream.</returns>
public static IDictionary<string, string> Read(Stream stream, XmlDocumentDecryptor decryptor)
{
- var data = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
var readerSettings = new XmlReaderSettings()
{
CloseInput = false, // caller will close the stream
IgnoreWhitespace = true
};
+ XmlConfigurationElement root = null;
+
using (XmlReader reader = decryptor.CreateDecryptingXmlReader(stream, readerSettings))
{
- var prefixStack = new Stack<string>();
-
- SkipUntilRootElement(reader);
-
- // We process the root element individually since it doesn't contribute to prefix
- ProcessAttributes(reader, prefixStack, data, AddNamePrefix);
- ProcessAttributes(reader, prefixStack, data, AddAttributePair);
+ // keep track of the tree we followed to get where we are (breadcrumb style)
+ var currentPath = new Stack<XmlConfigurationElement>();
XmlNodeType preNodeType = reader.NodeType;
+
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
- prefixStack.Push(reader.LocalName);
- ProcessAttributes(reader, prefixStack, data, AddNamePrefix);
- ProcessAttributes(reader, prefixStack, data, AddAttributePair);
+ var element = new XmlConfigurationElement(reader.LocalName, GetName(reader));
+
+ if (currentPath.Count == 0)
+ {
+ root = element;
+ }
+ else
+ {
+ var parent = currentPath.Peek();
+
+ // If parent already has a dictionary of children, update the collection accordingly
+ if (parent.ChildrenBySiblingName != null)
+ {
+ // check if this element has appeared before, elements are considered siblings if their SiblingName properties match
+ if (!parent.ChildrenBySiblingName.TryGetValue(element.SiblingName, out var siblings))
+ {
+ siblings = new List<XmlConfigurationElement>();
+ parent.ChildrenBySiblingName.Add(element.SiblingName, siblings);
+ }
+ siblings.Add(element);
+ }
+ else
+ {
+ // Performance optimization: parents with a single child don't even initialize a dictionary
+ if (parent.SingleChild == null)
+ {
+ parent.SingleChild = element;
+ }
+ else
+ {
+ // If we encounter a second child after assigning "SingleChild", we clear SingleChild and initialize the dictionary
+ var children = new Dictionary<string, List<XmlConfigurationElement>>(StringComparer.OrdinalIgnoreCase);
+
+ // Special case: the first and second child have the same sibling name
+ if (string.Equals(parent.SingleChild.SiblingName, element.SiblingName, StringComparison.OrdinalIgnoreCase))
+ {
+ children.Add(element.SiblingName, new List<XmlConfigurationElement>
+ {
+ parent.SingleChild,
+ element
+ });
+ }
+ else
+ {
+ children.Add(parent.SingleChild.SiblingName, new List<XmlConfigurationElement> { parent.SingleChild });
+ children.Add(element.SiblingName, new List<XmlConfigurationElement> { element });
+ }
+
+ parent.ChildrenBySiblingName = children;
+ parent.SingleChild = null;
+ }
+
+ }
+ }
+
+ currentPath.Push(element);
+
+ ReadAttributes(reader, element);
// If current element is self-closing
if (reader.IsEmptyElement)
{
- prefixStack.Pop();
+ currentPath.Pop();
}
break;
-
case XmlNodeType.EndElement:
- if (prefixStack.Count != 0)
+ if (currentPath.Count != 0)
{
+ XmlConfigurationElement parent = currentPath.Pop();
+
// If this EndElement node comes right after an Element node,
// it means there is no text/CDATA node in current element
if (preNodeType == XmlNodeType.Element)
{
- string key = ConfigurationPath.Combine(prefixStack.Reverse());
- data[key] = string.Empty;
+ var lineInfo = reader as IXmlLineInfo;
+ var lineNumber = lineInfo?.LineNumber;
+ var linePosition = lineInfo?.LinePosition;
+ parent.TextContent = new XmlConfigurationElementTextContent(string.Empty, lineNumber, linePosition);
}
-
- prefixStack.Pop();
}
break;
case XmlNodeType.CDATA:
case XmlNodeType.Text:
+ if (currentPath.Count != 0)
{
- string key = ConfigurationPath.Combine(prefixStack.Reverse());
- if (data.ContainsKey(key))
- {
- throw new FormatException(SR.Format(SR.Error_KeyIsDuplicated, key,
- GetLineInfo(reader)));
- }
+ var lineInfo = reader as IXmlLineInfo;
+ var lineNumber = lineInfo?.LineNumber;
+ var linePosition = lineInfo?.LinePosition;
- data[key] = reader.Value;
- break;
+ XmlConfigurationElement parent = currentPath.Peek();
+
+ parent.TextContent = new XmlConfigurationElementTextContent(reader.Value, lineNumber, linePosition);
}
+ break;
case XmlNodeType.XmlDeclaration:
case XmlNodeType.ProcessingInstruction:
case XmlNodeType.Comment:
break;
default:
- throw new FormatException(SR.Format(SR.Error_UnsupportedNodeType, reader.NodeType,
- GetLineInfo(reader)));
+ throw new FormatException(SR.Format(SR.Error_UnsupportedNodeType, reader.NodeType, GetLineInfo(reader)));
}
preNodeType = reader.NodeType;
+
// If this element is a self-closing element,
// we pretend that we just processed an EndElement node
// because a self-closing element contains an end within itself
- if (preNodeType == XmlNodeType.Element &&
- reader.IsEmptyElement)
+ if (preNodeType == XmlNodeType.Element && reader.IsEmptyElement)
{
preNodeType = XmlNodeType.EndElement;
}
}
}
- return data;
+
+ return ProvideConfiguration(root);
}
/// <summary>
Data = Read(stream, XmlDocumentDecryptor.Instance);
}
- private static void SkipUntilRootElement(XmlReader reader)
- {
- while (reader.Read())
- {
- if (reader.NodeType != XmlNodeType.XmlDeclaration &&
- reader.NodeType != XmlNodeType.ProcessingInstruction)
- {
- break;
- }
- }
- }
-
private static string GetLineInfo(XmlReader reader)
{
var lineInfo = reader as IXmlLineInfo;
SR.Format(SR.Msg_LineInfo, lineInfo.LineNumber, lineInfo.LinePosition);
}
- private static void ProcessAttributes(XmlReader reader, Stack<string> prefixStack, IDictionary<string, string> data,
- Action<XmlReader, Stack<string>, IDictionary<string, string>, XmlWriter> act, XmlWriter writer = null)
+ private static void ReadAttributes(XmlReader reader, XmlConfigurationElement element)
{
+ if (reader.AttributeCount > 0)
+ {
+ element.Attributes = new List<XmlConfigurationElementAttributeValue>();
+ }
+
+ var lineInfo = reader as IXmlLineInfo;
+
for (int i = 0; i < reader.AttributeCount; i++)
{
reader.MoveToAttribute(i);
+ var lineNumber = lineInfo?.LineNumber;
+ var linePosition = lineInfo?.LinePosition;
+
// If there is a namespace attached to current attribute
if (!string.IsNullOrEmpty(reader.NamespaceURI))
{
throw new FormatException(SR.Format(SR.Error_NamespaceIsNotSupported, GetLineInfo(reader)));
}
- act(reader, prefixStack, data, writer);
+ element.Attributes.Add(new XmlConfigurationElementAttributeValue(reader.LocalName, reader.Value, lineNumber, linePosition));
}
// Go back to the element containing the attributes we just processed
}
// The special attribute "Name" only contributes to prefix
- // This method adds a prefix if current node in reader represents a "Name" attribute
- private static void AddNamePrefix(XmlReader reader, Stack<string> prefixStack,
- IDictionary<string, string> data, XmlWriter writer)
+ // This method retrieves the Name of the element, if the attribute is present
+ // Unfortunately XmlReader.GetAttribute cannot be used, as it does not support looking for attributes in a case insensitive manner
+ private static string GetName(XmlReader reader)
{
- if (!string.Equals(reader.LocalName, NameAttributeKey, StringComparison.OrdinalIgnoreCase))
+ string name = null;
+
+ while (reader.MoveToNextAttribute())
{
- return;
+ if (string.Equals(reader.LocalName, NameAttributeKey, StringComparison.OrdinalIgnoreCase))
+ {
+ // If there is a namespace attached to current attribute
+ if (!string.IsNullOrEmpty(reader.NamespaceURI))
+ {
+ throw new FormatException(SR.Format(SR.Error_NamespaceIsNotSupported, GetLineInfo(reader)));
+ }
+ name = reader.Value;
+ break;
+ }
}
- // If current element is not root element
- if (prefixStack.Count != 0)
+ // Go back to the element containing the name we just processed
+ reader.MoveToElement();
+
+ return name;
+ }
+
+ private static IDictionary<string, string> ProvideConfiguration(XmlConfigurationElement root)
+ {
+ var configuration = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+
+ if (root == null)
{
- string lastPrefix = prefixStack.Pop();
- prefixStack.Push(ConfigurationPath.Combine(lastPrefix, reader.Value));
+ return configuration;
}
- else
+
+ var rootPrefix = new Prefix();
+
+ // The root element only contributes to the prefix via its Name attribute
+ if (!string.IsNullOrEmpty(root.Name))
{
- prefixStack.Push(reader.Value);
+ rootPrefix.Push(root.Name);
+ }
+
+ ProcessElementAttributes(rootPrefix, root);
+ ProcessElementContent(rootPrefix, root);
+ ProcessElementChildren(rootPrefix, root);
+
+ return configuration;
+
+ void ProcessElement(Prefix prefix, XmlConfigurationElement element)
+ {
+ ProcessElementAttributes(prefix, element);
+
+ ProcessElementContent(prefix, element);
+
+ ProcessElementChildren(prefix, element);
+ }
+
+ void ProcessElementAttributes(Prefix prefix, XmlConfigurationElement element)
+ {
+ // Add attributes to configuration values
+ if (element.Attributes != null)
+ {
+ for (var i = 0; i < element.Attributes.Count; i++)
+ {
+ var attribute = element.Attributes[i];
+
+ prefix.Push(attribute.Attribute);
+
+ AddToConfiguration(prefix.AsString, attribute.Value, attribute.LineNumber, attribute.LinePosition);
+
+ prefix.Pop();
+ }
+ }
+ }
+
+ void ProcessElementContent(Prefix prefix, XmlConfigurationElement element)
+ {
+ // Add text content to configuration values
+ if (element.TextContent != null)
+ {
+ var textContent = element.TextContent;
+ AddToConfiguration(prefix.AsString, textContent.TextContent, textContent.LineNumber, textContent.LinePosition);
+ }
+ }
+
+ void ProcessElementChildren(Prefix prefix, XmlConfigurationElement element)
+ {
+ if (element.SingleChild != null)
+ {
+ var child = element.SingleChild;
+
+ ProcessElementChild(prefix, child, null);
+
+ return;
+ }
+
+ if (element.ChildrenBySiblingName == null)
+ {
+ return;
+ }
+
+ // Recursively walk through the children of this element
+ foreach (var childrenWithSameSiblingName in element.ChildrenBySiblingName.Values)
+ {
+ if (childrenWithSameSiblingName.Count == 1)
+ {
+ var child = childrenWithSameSiblingName[0];
+
+ ProcessElementChild(prefix, child, null);
+ }
+ else
+ {
+ // Multiple children with the same sibling name. Add the current index to the prefix
+ for (int i = 0; i < childrenWithSameSiblingName.Count; i++)
+ {
+ var child = childrenWithSameSiblingName[i];
+
+ ProcessElementChild(prefix, child, i);
+ }
+ }
+ }
+ }
+
+ void ProcessElementChild(Prefix prefix, XmlConfigurationElement child, int? index)
+ {
+ // Add element name to prefix
+ prefix.Push(child.ElementName);
+
+ // Add value of name attribute to prefix
+ var hasName = !string.IsNullOrEmpty(child.Name);
+ if (hasName)
+ {
+ prefix.Push(child.Name);
+ }
+
+ // Add index to the prefix
+ if (index != null)
+ {
+ prefix.Push(index.Value.ToString(CultureInfo.InvariantCulture));
+ }
+
+ ProcessElement(prefix, child);
+
+ // Remove index
+ if (index != null)
+ {
+ prefix.Pop();
+ }
+
+ // Remove 'Name' attribute
+ if (hasName)
+ {
+ prefix.Pop();
+ }
+
+ // Remove element name
+ prefix.Pop();
+ }
+
+ void AddToConfiguration(string key, string value, int? lineNumber, int? linePosition)
+ {
+#if NETSTANDARD2_1
+ if (!configuration.TryAdd(key, value))
+ {
+ var lineInfo = lineNumber == null || linePosition == null
+ ? string.Empty
+ : SR.Format(SR.Msg_LineInfo, lineNumber.Value, linePosition.Value);
+ throw new FormatException(SR.Format(SR.Error_KeyIsDuplicated, key, lineInfo));
+ }
+#else
+ if (configuration.ContainsKey(key))
+ {
+ var lineInfo = lineNumber == null || linePosition == null
+ ? string.Empty
+ : SR.Format(SR.Msg_LineInfo, lineNumber.Value, linePosition.Value);
+ throw new FormatException(SR.Format(SR.Error_KeyIsDuplicated, key, lineInfo));
+ }
+
+ configuration.Add(key, value);
+#endif
}
}
+ }
- // Common attributes contribute to key-value pairs
- // This method adds a key-value pair if current node in reader represents a common attribute
- private static void AddAttributePair(XmlReader reader, Stack<string> prefixStack,
- IDictionary<string, string> data, XmlWriter writer)
+ /// <summary>
+ /// Helper class to build the configuration keys in a way that does not require string.Join
+ /// </summary>
+ internal class Prefix
+ {
+ private readonly StringBuilder _sb;
+ private readonly Stack<int> _lengths;
+
+ public Prefix()
{
- prefixStack.Push(reader.LocalName);
- string key = ConfigurationPath.Combine(prefixStack.Reverse());
- if (data.ContainsKey(key))
+ _sb = new StringBuilder();
+ _lengths = new Stack<int>();
+ }
+
+ public string AsString => _sb.ToString();
+
+ public void Push(string value)
+ {
+ if (_sb.Length != 0)
+ {
+ _sb.Append(ConfigurationPath.KeyDelimiter);
+ _sb.Append(value);
+ _lengths.Push(value.Length + ConfigurationPath.KeyDelimiter.Length);
+ }
+ else
{
- throw new FormatException(SR.Format(SR.Error_KeyIsDuplicated, key, GetLineInfo(reader)));
+ _sb.Append(value);
+ _lengths.Push(value.Length);
}
+ }
+
+ public void Pop()
+ {
+ var length = _lengths.Pop();
- data[key] = reader.Value;
- prefixStack.Pop();
+ _sb.Remove(_sb.Length - length, length);
}
}
}
}
[Fact]
+ public void LowercaseNameAttributeContributesToPrefix()
+ {
+ var xml =
+ @"<settings>
+ <Data name='DefaultConnection'>
+ <ConnectionString>TestConnectionString</ConnectionString>
+ <Provider>SqlClient</Provider>
+ </Data>
+ <Data name='Inventory'>
+ <ConnectionString>AnotherTestConnectionString</ConnectionString>
+ <Provider>MySql</Provider>
+ </Data>
+ </settings>";
+ var xmlConfigSrc = new XmlConfigurationProvider(new XmlConfigurationSource());
+
+ xmlConfigSrc.Load(TestStreamHelpers.StringToStream(xml));
+
+ Assert.Equal("DefaultConnection", xmlConfigSrc.Get("Data:DefaultConnection:Name"));
+ Assert.Equal("TestConnectionString", xmlConfigSrc.Get("Data:DefaultConnection:ConnectionString"));
+ Assert.Equal("SqlClient", xmlConfigSrc.Get("Data:DefaultConnection:Provider"));
+ Assert.Equal("Inventory", xmlConfigSrc.Get("Data:Inventory:Name"));
+ Assert.Equal("AnotherTestConnectionString", xmlConfigSrc.Get("Data:Inventory:ConnectionString"));
+ Assert.Equal("MySql", xmlConfigSrc.Get("Data:Inventory:Provider"));
+ }
+
+ [Fact]
public void NameAttributeInRootElementContributesToPrefix()
{
var xml =
}
[Fact]
+ public void NameAttributeCanBeUsedToSimulateArrays()
+ {
+ var xml =
+ @"<settings>
+ <DefaultConnection Name='0'>
+ <ConnectionString>TestConnectionString1</ConnectionString>
+ <Provider>SqlClient1</Provider>
+ </DefaultConnection>
+ <DefaultConnection Name='1'>
+ <ConnectionString>TestConnectionString2</ConnectionString>
+ <Provider>SqlClient2</Provider>
+ </DefaultConnection>
+ </settings>";
+ var xmlConfigSrc = new XmlConfigurationProvider(new XmlConfigurationSource());
+ xmlConfigSrc.Load(TestStreamHelpers.StringToStream(xml));
+
+ Assert.Equal("TestConnectionString1", xmlConfigSrc.Get("DefaultConnection:0:ConnectionString"));
+ Assert.Equal("SqlClient1", xmlConfigSrc.Get("DefaultConnection:0:Provider"));
+ Assert.Equal("TestConnectionString2", xmlConfigSrc.Get("DefaultConnection:1:ConnectionString"));
+ Assert.Equal("SqlClient2", xmlConfigSrc.Get("DefaultConnection:1:Provider"));
+ }
+
+ [Fact]
+ public void RepeatedElementsContributeToPrefix()
+ {
+ var xml =
+ @"<settings>
+ <DefaultConnection>
+ <ConnectionString>TestConnectionString1</ConnectionString>
+ <Provider>SqlClient1</Provider>
+ </DefaultConnection>
+ <DefaultConnection>
+ <ConnectionString>TestConnectionString2</ConnectionString>
+ <Provider>SqlClient2</Provider>
+ </DefaultConnection>
+ </settings>";
+ var xmlConfigSrc = new XmlConfigurationProvider(new XmlConfigurationSource());
+ xmlConfigSrc.Load(TestStreamHelpers.StringToStream(xml));
+
+ Assert.Equal("TestConnectionString1", xmlConfigSrc.Get("DefaultConnection:0:ConnectionString"));
+ Assert.Equal("SqlClient1", xmlConfigSrc.Get("DefaultConnection:0:Provider"));
+ Assert.Equal("TestConnectionString2", xmlConfigSrc.Get("DefaultConnection:1:ConnectionString"));
+ Assert.Equal("SqlClient2", xmlConfigSrc.Get("DefaultConnection:1:Provider"));
+ }
+
+ [Fact]
+ public void RepeatedElementDetectionIsCaseInsensitive()
+ {
+ var xml =
+ @"<settings>
+ <DefaultConnection>
+ <ConnectionString>TestConnectionString1</ConnectionString>
+ <Provider>SqlClient1</Provider>
+ </DefaultConnection>
+ <defaultconnection>
+ <ConnectionString>TestConnectionString2</ConnectionString>
+ <Provider>SqlClient2</Provider>
+ </defaultconnection>
+ </settings>";
+ var xmlConfigSrc = new XmlConfigurationProvider(new XmlConfigurationSource());
+ xmlConfigSrc.Load(TestStreamHelpers.StringToStream(xml));
+
+ Assert.Equal("TestConnectionString1", xmlConfigSrc.Get("DefaultConnection:0:ConnectionString"));
+ Assert.Equal("SqlClient1", xmlConfigSrc.Get("DefaultConnection:0:Provider"));
+ Assert.Equal("TestConnectionString2", xmlConfigSrc.Get("DefaultConnection:1:ConnectionString"));
+ Assert.Equal("SqlClient2", xmlConfigSrc.Get("DefaultConnection:1:Provider"));
+ }
+
+ [Fact]
+ public void RepeatedElementsUnderNameContributeToPrefix()
+ {
+ var xml =
+ @"<settings Name='Data'>
+ <DefaultConnection>
+ <ConnectionString>TestConnectionString1</ConnectionString>
+ <Provider>SqlClient1</Provider>
+ </DefaultConnection>
+ <DefaultConnection>
+ <ConnectionString>TestConnectionString2</ConnectionString>
+ <Provider>SqlClient2</Provider>
+ </DefaultConnection>
+ </settings>";
+ var xmlConfigSrc = new XmlConfigurationProvider(new XmlConfigurationSource());
+
+ xmlConfigSrc.Load(TestStreamHelpers.StringToStream(xml));
+
+ Assert.Equal("TestConnectionString1", xmlConfigSrc.Get("Data:DefaultConnection:0:ConnectionString"));
+ Assert.Equal("SqlClient1", xmlConfigSrc.Get("Data:DefaultConnection:0:Provider"));
+ Assert.Equal("TestConnectionString2", xmlConfigSrc.Get("Data:DefaultConnection:1:ConnectionString"));
+ Assert.Equal("SqlClient2", xmlConfigSrc.Get("Data:DefaultConnection:1:Provider"));
+ }
+
+ [Fact]
+ public void RepeatedElementsWithSameNameContributeToPrefix()
+ {
+ var xml =
+ @"<settings>
+ <DefaultConnection Name='Data'>
+ <ConnectionString>TestConnectionString1</ConnectionString>
+ <Provider>SqlClient1</Provider>
+ </DefaultConnection>
+ <DefaultConnection Name='Data'>
+ <ConnectionString>TestConnectionString2</ConnectionString>
+ <Provider>SqlClient2</Provider>
+ </DefaultConnection>
+ </settings>";
+ var xmlConfigSrc = new XmlConfigurationProvider(new XmlConfigurationSource());
+
+ xmlConfigSrc.Load(TestStreamHelpers.StringToStream(xml));
+
+ Assert.Equal("TestConnectionString1", xmlConfigSrc.Get("DefaultConnection:Data:0:ConnectionString"));
+ Assert.Equal("SqlClient1", xmlConfigSrc.Get("DefaultConnection:Data:0:Provider"));
+ Assert.Equal("TestConnectionString2", xmlConfigSrc.Get("DefaultConnection:Data:1:ConnectionString"));
+ Assert.Equal("SqlClient2", xmlConfigSrc.Get("DefaultConnection:Data:1:Provider"));
+ }
+
+ [Fact]
+ public void RepeatedElementsWithDifferentNamesContributeToPrefix()
+ {
+ var xml =
+ @"<settings>
+ <DefaultConnection Name='Data1'>
+ <ConnectionString>TestConnectionString1</ConnectionString>
+ <Provider>SqlClient1</Provider>
+ </DefaultConnection>
+ <DefaultConnection Name='Data2'>
+ <ConnectionString>TestConnectionString2</ConnectionString>
+ <Provider>SqlClient2</Provider>
+ </DefaultConnection>
+ </settings>";
+ var xmlConfigSrc = new XmlConfigurationProvider(new XmlConfigurationSource());
+
+ xmlConfigSrc.Load(TestStreamHelpers.StringToStream(xml));
+
+ Assert.Equal("TestConnectionString1", xmlConfigSrc.Get("DefaultConnection:Data1:ConnectionString"));
+ Assert.Equal("SqlClient1", xmlConfigSrc.Get("DefaultConnection:Data1:Provider"));
+ Assert.Equal("TestConnectionString2", xmlConfigSrc.Get("DefaultConnection:Data2:ConnectionString"));
+ Assert.Equal("SqlClient2", xmlConfigSrc.Get("DefaultConnection:Data2:Provider"));
+ }
+
+ [Fact]
+ public void NestedRepeatedElementsContributeToPrefix()
+ {
+ var xml =
+ @"<settings>
+ <DefaultConnection>
+ <ConnectionString>TestConnectionString1</ConnectionString>
+ <ConnectionString>TestConnectionString2</ConnectionString>
+ </DefaultConnection>
+ <DefaultConnection>
+ <ConnectionString>TestConnectionString3</ConnectionString>
+ <ConnectionString>TestConnectionString4</ConnectionString>
+ </DefaultConnection>
+ </settings>";
+ var xmlConfigSrc = new XmlConfigurationProvider(new XmlConfigurationSource());
+
+ xmlConfigSrc.Load(TestStreamHelpers.StringToStream(xml));
+
+ Assert.Equal("TestConnectionString1", xmlConfigSrc.Get("DefaultConnection:0:ConnectionString:0"));
+ Assert.Equal("TestConnectionString2", xmlConfigSrc.Get("DefaultConnection:0:ConnectionString:1"));
+ Assert.Equal("TestConnectionString3", xmlConfigSrc.Get("DefaultConnection:1:ConnectionString:0"));
+ Assert.Equal("TestConnectionString4", xmlConfigSrc.Get("DefaultConnection:1:ConnectionString:1"));
+ }
+
+ [Fact]
+ public void SupportMixingRepeatedElementsWithNonRepeatedElements()
+ {
+ var xml =
+ @"<settings>
+ <DefaultConnection>
+ <ConnectionString>TestConnectionString1</ConnectionString>
+ <Provider>SqlClient1</Provider>
+ </DefaultConnection>
+ <DefaultConnection>
+ <ConnectionString>TestConnectionString2</ConnectionString>
+ <Provider>SqlClient2</Provider>
+ </DefaultConnection>
+ <OtherValue>
+ <Value>MyValue</Value>
+ </OtherValue>
+ <DefaultConnection>
+ <ConnectionString>TestConnectionString3</ConnectionString>
+ <Provider>SqlClient3</Provider>
+ </DefaultConnection>
+ </settings>";
+ var xmlConfigSrc = new XmlConfigurationProvider(new XmlConfigurationSource());
+
+ xmlConfigSrc.Load(TestStreamHelpers.StringToStream(xml));
+
+ Assert.Equal("TestConnectionString1", xmlConfigSrc.Get("DefaultConnection:0:ConnectionString"));
+ Assert.Equal("TestConnectionString2", xmlConfigSrc.Get("DefaultConnection:1:ConnectionString"));
+ Assert.Equal("TestConnectionString3", xmlConfigSrc.Get("DefaultConnection:2:ConnectionString"));
+ Assert.Equal("SqlClient1", xmlConfigSrc.Get("DefaultConnection:0:Provider"));
+ Assert.Equal("SqlClient2", xmlConfigSrc.Get("DefaultConnection:1:Provider"));
+ Assert.Equal("SqlClient3", xmlConfigSrc.Get("DefaultConnection:2:Provider"));
+ Assert.Equal("MyValue", xmlConfigSrc.Get("OtherValue:Value"));
+ }
+
+ [Fact]
public void SupportMixingNameAttributesAndCommonAttributes()
{
var xml =
}
[Fact]
+ public void KeysAreCaseInsensitive()
+ {
+ var xml =
+ @"<settings>
+ <Data Name='DefaultConnection'
+ ConnectionString='TestConnectionString'
+ Provider='SqlClient' />
+ </settings>";
+ var xmlConfigSrc = new XmlConfigurationProvider(new XmlConfigurationSource());
+
+ xmlConfigSrc.Load(TestStreamHelpers.StringToStream(xml));
+
+ Assert.Equal("DefaultConnection", xmlConfigSrc.Get("data:defaultconnection:name"));
+ Assert.Equal("TestConnectionString", xmlConfigSrc.Get("data:defaultconnection:connectionstring"));
+ Assert.Equal("SqlClient", xmlConfigSrc.Get("data:defaultconnection:provider"));
+ }
+
+ [Fact]
public void SupportCDATAAsTextNode()
{
var xml =
}
[Fact]
+ public void ThrowExceptionWhenKeyIsDuplicatedWithDifferentCasing()
+ {
+ var xml =
+ @"<settings>
+ <Data>
+ <DefaultConnection>
+ <ConnectionString>TestConnectionString</ConnectionString>
+ <Provider>SqlClient</Provider>
+ </DefaultConnection>
+ </Data>
+ <data name='defaultconnection' connectionstring='NewConnectionString'>
+ <provider>NewProvider</provider>
+ </data>
+ </settings>";
+ var xmlConfigSrc = new XmlConfigurationProvider(new XmlConfigurationSource());
+ var expectedMsg = SR.Format(SR.Error_KeyIsDuplicated, "data:defaultconnection:connectionstring",
+ SR.Format(SR.Msg_LineInfo, 8, 52));
+
+ var exception = Assert.Throws<FormatException>(() => xmlConfigSrc.Load(TestStreamHelpers.StringToStream(xml)));
+
+ Assert.Equal(expectedMsg, exception.Message);
+ }
+
+ [Fact]
public void XmlConfiguration_Throws_On_Missing_Configuration_File()
{
var ex = Assert.Throws<FileNotFoundException>(() => new ConfigurationBuilder().AddXmlFile("NotExistingConfig.xml", optional: false).Build());