. NET Core adopts a new configuration system [9]: Why is XML not well supported? How to Improve ?, Corexml
Physical files are the carriers of the most common original configurations. The best configuration file formats are JSON, XML, and INI, the configuration source types are JsonConfigurationSource, XmlConfigurationSource, and IniConfigurationSource. But. for the Configuration System of NET Core, XML, which we are accustomed to, is not an ideal configuration source. At least compared with JSON, XML has an inherent disadvantage, that is, the support for the collection data structure is not satisfactory. [This Article has been synchronized to ASP. NET Core framework secrets]
I. Why is it difficult to use elegant XML for collection configuration?
In the configuration model design details article, we provide a detailed introduction to the design and implementation of the configuration model. In this article, we will say that the configuration in the application is embodied in a tree-based hierarchy, which is called a "configuration tree". The specific configuration data is carried by the "leaf node" of the configuration tree. After the configuration data is loaded from different sources, it is converted into a dictionary ". To enable the "configuration Dictionary" to store all the data and structure of the "configuration Tree", we need to store all the leaf nodes in the configuration dictionary, the path and Value of the leaf node are directly used as the Key and Value of the dictionary element. Because the dictionary Key is unique, each node in the configuration tree must have a unique path. XmlConfigurationSource/XmlConfigurationProvider does not support the collection data structure well.
1: public class Profile
2: {
3: public Gender Gender { get; set; }
4: public int Age { get; set; }
5: public ContactInfo ContactInfo { get; set; }
6: }
7:
8: public class ContactInfo
9: {
10: public string EmailAddress { get; set; }
11: public string PhoneNo { get; set; }
12: }
13:
14: public enum Gender
15: {
16: Male,
17: Female
18: }
For a simple example, suppose we need to use XML to represent a collection of Profile objects (the Profile type has the definition shown above), then we naturally adopt the following structure.
1: <Profiles>
2: <Profile Gender="Male" Age="18">
3: <ContactInfo EmailAddress ="foobar@outlook.com" PhoneNo="123"/>
4: </Profile>
5: <Profile Gender="Male" Age="25">
6: <ContactInfo EmailAddress ="bar@outlook.com" PhoneNo="456"/>
7: </Profile>
8: <Profile Gender="Male" Age="40">
9: <ContactInfo EmailAddress ="baz@outlook.com" PhoneNo="789"/>
10: </Profile>
For this XML structure, XmlConfigurationProvider maps it to the configuration tree shown below in a "simple and crude" way ". Because the tree directly uses the name of the XML element as the configuration node name, the root nodes of the three Profile objects in the tree are named "Profile". There is no doubt that, this tree cannot be represented by a dictionary because it cannot ensure that all nodes have different paths.
Ii. Slightly convert the XML structure according to the requirements of the configuration tree
The reason why XML cannot represent a set or array in a natural form like JSON format, the latter provides a clear way to define the two data types (using brackets). However, XML only has the concept of child elements, so we cannot determine whether its child element is a set. If we assume that all the child elements under the same XML element have the same name, we can regard it as a set. Based on this assumption, a slight transformation to XmlConfigurationSource can solve the problem that XML cannot represent the collection data structure.
Create a new ConfigurationSource type by deriving XmlConfigurationSource, and name it ExtendedXmlConfigurationSource. The ConfigurationProvdier type provided by XmlConfigurationSource is ExtendedXmlConfigurationProvider, which is derived from XmlConfigurationProvider. In the overwrite Load method, ExtendedXmlConfigurationProvider modifies the original XML structure to make the originally invalid XML (the XML element has the same name) it can be converted into a configuration dictionary for the set. Demonstrate the rules and steps for XML structure conversion.
As shown in, the structure conversion for the set to the original XML consists of two steps. The first step is to add an Attribute named "append_index" to the XML element of the collection element. We use a zero-base index as the value of this Attribute. In step 2, a new XML with the same name set element (such as <profile>) will be created based on the Conversion Result of step 1) the new name (for example, <profile_index_0>) is based on the added index value ). Undoubtedly, the converted XML can represent a collection object. The following is the definition of ExtendedXmlConfigurationProvider. The preceding conversion logic is embodied in the overwritable Load method.
1: public class ExtendedXmlConfigurationProvider : XmlConfigurationProvider
2: {
3: public ExtendedXmlConfigurationProvider(XmlConfigurationSource source) : base(source)
4: {}
5:
6: public override void Load(Stream stream)
7: {
8: // load the source file and create an XmlDocument
9: XmlDocument sourceDoc = new XmlDocument();
10: sourceDoc.Load(stream);
11:
12: // Add an index
13: this.AddIndexes(sourceDoc.DocumentElement);
14:
15: // create a new XmlDocument Based on the added Index
16: XmlDocument newDoc = new XmlDocument();
17: XmlElement documentElement = newDoc.CreateElement(sourceDoc.DocumentElement.Name);
18: newDoc.AppendChild(documentElement);
19:
20: foreach (XmlElement element in sourceDoc.DocumentElement.ChildNodes)
21: {
22: this.Rebuild(element, documentElement,
23: name => newDoc.CreateElement(name));
24: }
25:
26: // initialize the configuration dictionary based on the new XmlDocument
27: using (Stream newStream = new MemoryStream())
28: {
29: using (XmlWriter writer = XmlWriter.Create(newStream))
30: {
31: newDoc.WriteTo(writer);
32: }
33: newStream.Position = 0;
34: base.Load(newStream);
35: }
36: }
37:
38: private void AddIndexes(XmlElement element)
39: {
40: if (element.ChildNodes.OfType<XmlElement>().Count() > 1)
41: {
42: if (element.ChildNodes.OfType<XmlElement>().GroupBy(it => it.Name).Count() == 1)
43: {
44: int index = 0;
45: foreach (XmlElement subElement in element.ChildNodes)
46: {
47: subElement.SetAttribute("append_index", (index++).ToString());
48: AddIndexes(subElement);
49: }
50: }
51: }
52: }
53:
54: private void Rebuild(XmlElement source, XmlElement destParent, Func<string, XmlElement> creator)
55: {
56: string index = source.GetAttribute("append_index");
57: string elementName = string.IsNullOrEmpty(index) ? source.Name : $"{source.Name}_index_{index}";
58: XmlElement element = creator(elementName);
59: destParent.AppendChild(element);
60: foreach (XmlAttribute attribute in source.Attributes)
61: {
62: if (attribute.Name != "append_index")
63: {
64: element.SetAttribute(attribute.Name, attribute.Value);
65: }
66: }
67:
68: foreach (XmlElement subElement in source.ChildNodes)
69: {
70: Rebuild(subElement, element, creator);
71: }
72: }
73: }