简体   繁体   中英

Serializing custom configuration

I need some help on implementing a way to serialize a custom configuration. I started with the content of following example: Polymorphic custom configuration section

Reading the configuration works fine as expected but I can't save modifications of some configuration properties (eg changing property P1 to contain another string). While debugging the content of the different objects looks fine (section contains collection which contains three proxy items which itself contain an instance of Parent class). The item which has been changed (P1="") has the isModified flag set to true (as expected).

When calling config.Save() some strange behaviour comes up and after three days of investigation (even the Microsoft base classes) I can't manage to find out where the problem is. Here are some of my conclusions:

I added an override for each of the SerializeX methods (SerializeSection, SerializeElement and SerializeToXmlElement) and debbuged step by step through the code.

  • SerializeSection is called (as expected) with parameter parentElement which is not the section I want to serialize as the Collection property is empty (I would expect it to have the three instances which are part of the configuration file). Calling base.SerializeSection with this instead of parentElement resolves the problem

  • SerializeToXmlElement is called before SerializeElement and does contain an instance of XmlWriter (as expected)

  • SerializeElement is called right after SerializeToXmlElement and does not contain the instance of XmlWriter anymore

  • When entering the serialize methods of the collection object I would expect that the three elements of the collections are serialized. But instead of the three items the collection only contains one item which has been freshly initialized and thus has a Parent property returning null.

I know that there needs to be a custom SerializeElement method (propably on the Proxy class) which then calls _Parent.ProxySerializeElement(writer, serializeCollectionKey) for each element as it does for deserializing. But I can't manage to get it work. An override of SerializeElement doesn't work as the XmlWriter instance is always null (even though the Proxy class has some IsModified method to check if the Parent object has changed). In addition the Parent object is always null too, as soon as I enter this custom SerializeElement method.

Here are the code fragments I added to the example:

Parent.cs

new public bool IsModified { get { return IsModified(); } }

public virtual bool ProxySerializeElement(XmlWriter writer, bool serializeCollectionKey)
{
    return SerializeElement(writer, serializeCollectionKey);
}

Proxy.cs

protected override bool IsModified()
{
    bool isModified = base.IsModified();
    return isModified || (Parent == null ? false : Parent.IsModified);
}

protected override bool SerializeElement(XmlWriter writer, bool serializeCollectionKey)
{
    bool serialize = base.SerializeElement(writer, serializeCollectionKey);
    return serialize || (_Parent == null ? false : _Parent.ProxySerializeElement(writer, serializeCollectionKey));
}

It's driving me nuts that I can't get it to work. Maybe someone else can help me out.

Thx in advance!

Greetings, Stefi

Finally this worked for me. Maybe it helps someone else who's having the same problem. I'll post the complete code to keep it simple. It might not be the firstclass solution but it's working. I would appreciate if someone else could have a look at it and propose a better way.

What helped me to get it working is this article: https://www.codeproject.com/Articles/16466/Unraveling-the-Mysteries-of-NET-2-0-Configuration

My code was missing the elements collection (see ThingElement.cs).

thing.config

<configuration>
  <configSections>
    <section name="ExampleSection" type="ConsoleApplication1.Things.ExampleSection, ConsoleApplication1" />
  </configSections>

  <ExampleSection>
    <things>
      <thing type="one" name="one-1" color="green" />
      <thing type="one" name="one-2" color="red" />
      <thing type="two" name="two-1" />
    </things>
  </ExampleSection>
</configuration>

ExampleSection.cs

    public class ExampleSection : ConfigurationSection
    {
        static ExampleSection() { }

        [ConfigurationProperty("things")]
        [ConfigurationCollection(typeof(ThingElement), AddItemName = "thing",
            CollectionType = ConfigurationElementCollectionType.BasicMap)]
        public ExampleThingElementCollection Things
        {
            get { return (ExampleThingElementCollection)this["things"]; }
            set { this["things"] = value; }
        }
    }

ExampleThingElementCollection.cs

    [ConfigurationCollection(typeof(ThingElement), AddItemName = "thing",
       CollectionType = ConfigurationElementCollectionType.BasicMap)]
    public class ExampleThingElementCollection : ConfigurationElementCollection
    {
        #region Constructors
        public ExampleThingElementCollection()
        {
        }
        #endregion

        #region Properties
        public override ConfigurationElementCollectionType CollectionType
        {
            get { return ConfigurationElementCollectionType.BasicMap; }
        }
        protected override string ElementName
        {
            get { return "thing"; }
        }
        #endregion

        #region Indexers
        public ThingElement this[int index]
        {
            get { return (ThingElement)base.BaseGet(index); }
            set
            {
                if (base.BaseGet(index) != null)
                {
                    base.BaseRemoveAt(index);
                }
                base.BaseAdd(index, value);
            }
        }
        new public ThingElement this[string name]
        {
            get { return (ThingElement)base.BaseGet(name); }
        }
        #endregion

        #region Overrides
        protected override ConfigurationElement CreateNewElement()
        {
            return new ThingElement();
        }
        protected override object GetElementKey(ConfigurationElement element)
        {
            return (element as ThingElement).Name;
        }
        #endregion

        #region Methods
        public void Add(ThingElement thing)
        {
            base.BaseAdd(thing);
        }
        public void Remove(string name)
        {
            base.BaseRemove(name);
        }
        public void Remove(ThingElement thing)
        {
            base.BaseRemove(GetElementKey(thing));
        }
        public void Clear()
        {
            base.BaseClear();
        }
        public void RemoveAt(int index)
        {
            base.BaseRemoveAt(index);
        }
        public string GetKey(int index)
        {
            return (string)base.BaseGetKey(index);
        }
        #endregion
    }

ThingElement.cs (this class acts as proxy element)

    public class ThingElement : ConfigurationElement
    {
        #region Constructors
        /// <summary>
        /// Predefines the valid properties and prepares
        /// the property collection.
        /// </summary>
        static ThingElement()
        {
            // Predefine properties here
            s_propName = new ConfigurationProperty(
                "name",
                typeof(string),
                null,
                ConfigurationPropertyOptions.IsRequired
            );
        }
        #endregion

        #region Static Fields
        private static ConfigurationProperty s_propName;
        private static Dictionary<string, SpecialThing> elements = new Dictionary<string, SpecialThing>();
        #endregion


        #region Properties
        /// <summary>
        /// Gets the Name setting.
        /// </summary>
        [ConfigurationProperty("name", IsRequired = true)]
        public string Name
        {
            get { return (string)base[s_propName]; }
        }

        public SpecialThing Thing { get { return elements[Name]; } }

        protected override bool SerializeElement(XmlWriter writer, bool serializeCollectionKey)
        {
            return Thing.ProxySerializeElement(writer, serializeCollectionKey);
        }

        protected override void DeserializeElement(XmlReader reader, bool serializeCollectionKey)
        {
            SpecialThing obj = null;
            string name = reader.GetAttribute("name");
            string type = reader.GetAttribute("type");
            switch (type)
            {
                case "one":
                    obj = new One();
                    break;
                case "two":
                    obj = new Two();
                    break;
                default:
                    throw new ArgumentException(string.Format("Invalid type: {0}", type));
            }

            base[s_propName] = name;
            if (!elements.ContainsKey(name))
                elements.Add(name, obj);
            obj.ProxyDeserializeElement(reader, serializeCollectionKey);
        }
        private Hashtable attributes;

        public Hashtable Attributes
        {
            get
            {
                if (attributes == null)
                    attributes = new Hashtable(StringComparer.OrdinalIgnoreCase);
                return attributes;
            }
        }

        protected override bool OnDeserializeUnrecognizedAttribute(String name, String value)
        {
            Attributes.Add(name, value);
            return true;
        }

        protected override void PreSerialize(XmlWriter writer)
        {
            if (attributes != null)
            {
                IDictionaryEnumerator e = attributes.GetEnumerator();
                while (e.MoveNext())
                {
                    string xmlValue = (string)e.Value;
                    string xmlName = (string)e.Key;

                    if ((xmlValue != null) && (writer != null))
                    {
                        writer.WriteAttributeString(xmlName, xmlValue);
                    }
                }
            }
        }

        #endregion
    }

SpecialThing.cs (parent class, eg base class if you have other which derive)

    public class SpecialThing : ConfigurationElement
    {
        [ConfigurationProperty("name", IsRequired = true)]
        public string Name
        {
            get
            {
                return (string)this["name"];
            }

            set
            {
                this["name"] = value;
            }
        }

        [ConfigurationProperty("type", IsRequired = true)]
        public string Type
        {
            get
            {
                return (string)this["type"];
            }

            set
            {
                this["type"] = value;
            }
        }

        public virtual bool ProxySerializeElement(XmlWriter writer, bool serializeCollectionKey)
        {
            return SerializeElement(writer, serializeCollectionKey);
        }

        public void ProxyDeserializeElement(XmlReader reader, bool serializeCollectionKey)
        {
            DeserializeElement(reader, serializeCollectionKey);
        }
    }

One.cs (parent class, eg base class if you have other which derive

    public class One : SpecialThing
    {
        public One() { }

        public One(string name, string type, string color)
        {
            base.Name = name;
            base.Type = type;
            Color = color;
        }

        [ConfigurationProperty("color")]
        public string Color
        {
            get { return (string)this["color"]; }
            set { this["color"] = value; }
        }
    }

Two.cs

    public class Two : SpecialThing
    {
        public Two() { }

        public Two(string name, string type)
        {
            base.Name = name;
            base.Type = type;
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM