2 // System.Configuration.ConfigurationElement.cs
5 // Duncan Mak (duncan@ximian.com)
6 // Lluis Sanchez Gual (lluis@novell.com)
7 // Martin Baulig <martin.baulig@xamarin.com>
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
29 // Copyright (c) 2012 Xamarin Inc. (http://www.xamarin.com)
32 using System.Collections;
34 using System.Reflection;
36 using System.ComponentModel;
38 namespace System.Configuration
40 public abstract class ConfigurationElement
45 ConfigurationPropertyCollection keyProps;
46 ConfigurationElementCollection defaultCollection;
48 ElementInformation elementInfo;
49 ConfigurationElementProperty elementProperty;
50 Configuration _configuration;
53 internal Configuration Configuration {
54 get { return _configuration; }
55 set { _configuration = value; }
58 protected ConfigurationElement ()
62 internal virtual void InitFromProperty (PropertyInformation propertyInfo)
64 elementInfo = new ElementInformation (this, propertyInfo);
68 public ElementInformation ElementInformation {
70 if (elementInfo == null)
71 elementInfo = new ElementInformation (this, null);
76 internal string RawXml {
77 get { return rawXml; }
79 // FIXME: this hack is nasty. We should make
80 // some refactory on the entire assembly.
81 if (rawXml == null || value != null)
86 protected internal virtual void Init ()
90 protected internal virtual ConfigurationElementProperty ElementProperty {
92 if (elementProperty == null)
93 elementProperty = new ConfigurationElementProperty (ElementInformation.Validator);
94 return elementProperty;
98 protected ContextInformation EvaluationContext {
100 if (Configuration != null)
101 return Configuration.EvaluationContext;
102 throw new ConfigurationErrorsException (
103 "This element is not currently associated with any context.");
107 ConfigurationLockCollection lockAllAttributesExcept;
108 public ConfigurationLockCollection LockAllAttributesExcept {
110 if (lockAllAttributesExcept == null) {
111 lockAllAttributesExcept = new ConfigurationLockCollection (this, ConfigurationLockType.Attribute | ConfigurationLockType.Exclude);
114 return lockAllAttributesExcept;
118 ConfigurationLockCollection lockAllElementsExcept;
119 public ConfigurationLockCollection LockAllElementsExcept {
121 if (lockAllElementsExcept == null) {
122 lockAllElementsExcept = new ConfigurationLockCollection (this, ConfigurationLockType.Element | ConfigurationLockType.Exclude);
125 return lockAllElementsExcept;
129 ConfigurationLockCollection lockAttributes;
130 public ConfigurationLockCollection LockAttributes {
132 if (lockAttributes == null) {
133 lockAttributes = new ConfigurationLockCollection (this, ConfigurationLockType.Attribute);
136 return lockAttributes;
140 ConfigurationLockCollection lockElements;
141 public ConfigurationLockCollection LockElements {
143 if (lockElements == null) {
144 lockElements = new ConfigurationLockCollection (this, ConfigurationLockType.Element);
152 public bool LockItem {
153 get { return lockItem; }
154 set { lockItem = value; }
158 protected virtual void ListErrors (IList list)
160 throw new NotImplementedException ();
164 protected void SetPropertyValue (ConfigurationProperty prop, object value, bool ignoreLocks)
168 /* XXX all i know for certain is that Validation happens here */
169 prop.Validate (value);
171 /* XXX presumably the actual setting of the
172 * property happens here instead of in the
173 * set_Item code below, but that would mean
174 * the Value needs to be stuffed in the
175 * property, not the propertyinfo (or else the
176 * property needs a ref to the property info
177 * to correctly set the value). */
180 catch (Exception e) {
181 throw new ConfigurationErrorsException (String.Format ("The value for the property '{0}' on type {1} is not valid.", prop.Name, this.ElementInformation.Type), e);
185 internal ConfigurationPropertyCollection GetKeyProperties ()
187 if (keyProps != null) return keyProps;
189 ConfigurationPropertyCollection tmpkeyProps = new ConfigurationPropertyCollection ();
190 foreach (ConfigurationProperty prop in Properties) {
192 tmpkeyProps.Add (prop);
195 return keyProps = tmpkeyProps;
198 internal ConfigurationElementCollection GetDefaultCollection ()
200 if (defaultCollection != null) return defaultCollection;
202 ConfigurationProperty defaultCollectionProp = null;
204 foreach (ConfigurationProperty prop in Properties) {
205 if (prop.IsDefaultCollection) {
206 defaultCollectionProp = prop;
211 if (defaultCollectionProp != null) {
212 defaultCollection = this [defaultCollectionProp] as ConfigurationElementCollection;
215 return defaultCollection;
218 protected internal object this [ConfigurationProperty property] {
219 get { return this [property.Name]; }
220 set { this [property.Name] = value; }
223 protected internal object this [string property_name] {
225 PropertyInformation pi = ElementInformation.Properties [property_name];
227 throw new InvalidOperationException ("Property '" + property_name + "' not found in configuration element");
233 PropertyInformation pi = ElementInformation.Properties [property_name];
235 throw new InvalidOperationException ("Property '" + property_name + "' not found in configuration element");
237 SetPropertyValue (pi.Property, value, false);
244 protected internal virtual ConfigurationPropertyCollection Properties {
247 map = ElementMap.GetMap (GetType());
248 return map.Properties;
252 public override bool Equals (object compareTo)
254 ConfigurationElement other = compareTo as ConfigurationElement;
255 if (other == null) return false;
256 if (GetType() != other.GetType()) return false;
258 foreach (ConfigurationProperty prop in Properties) {
259 if (!object.Equals (this [prop], other [prop]))
265 public override int GetHashCode ()
270 foreach (ConfigurationProperty prop in Properties) {
275 code += o.GetHashCode ();
281 internal virtual bool HasLocalModifications ()
283 foreach (PropertyInformation pi in ElementInformation.Properties)
284 if (pi.ValueOrigin == PropertyValueOrigin.SetHere && pi.IsModified)
290 protected internal virtual void DeserializeElement (XmlReader reader, bool serializeCollectionKey)
292 Hashtable readProps = new Hashtable ();
294 reader.MoveToContent ();
295 elementPresent = true;
297 while (reader.MoveToNextAttribute ())
299 PropertyInformation prop = ElementInformation.Properties [reader.LocalName];
300 if (prop == null || (serializeCollectionKey && !prop.IsKey)) {
301 /* handle the built in ConfigurationElement attributes here */
302 if (reader.LocalName == "lockAllAttributesExcept") {
303 LockAllAttributesExcept.SetFromList (reader.Value);
305 else if (reader.LocalName == "lockAllElementsExcept") {
306 LockAllElementsExcept.SetFromList (reader.Value);
308 else if (reader.LocalName == "lockAttributes") {
309 LockAttributes.SetFromList (reader.Value);
311 else if (reader.LocalName == "lockElements") {
312 LockElements.SetFromList (reader.Value);
314 else if (reader.LocalName == "lockItem") {
315 LockItem = (reader.Value.ToLowerInvariant () == "true");
317 else if (reader.LocalName == "xmlns") {
319 } else if (this is ConfigurationSection && reader.LocalName == "configSource") {
321 } else if (!OnDeserializeUnrecognizedAttribute (reader.LocalName, reader.Value))
322 throw new ConfigurationErrorsException ("Unrecognized attribute '" + reader.LocalName + "'.", reader);
327 if (readProps.ContainsKey (prop))
328 throw new ConfigurationErrorsException ("The attribute '" + prop.Name + "' may only appear once in this element.", reader);
332 value = reader.Value;
333 ValidateValue (prop.Property, value);
334 prop.SetStringValue (value);
335 } catch (ConfigurationErrorsException) {
337 } catch (ConfigurationException) {
339 } catch (Exception ex) {
340 string msg = String.Format ("The value for the property '{0}' is not valid. The error is: {1}", prop.Name, ex.Message);
341 throw new ConfigurationErrorsException (msg, reader);
343 readProps [prop] = prop.Name;
345 ConfigXmlTextReader _reader = reader as ConfigXmlTextReader;
346 if (_reader != null){
347 prop.Source = _reader.Filename;
348 prop.LineNumber = _reader.LineNumber;
352 reader.MoveToElement ();
353 if (reader.IsEmptyElement) {
356 int depth = reader.Depth;
358 reader.ReadStartElement ();
359 reader.MoveToContent ();
362 if (reader.NodeType != XmlNodeType.Element) {
367 PropertyInformation prop = ElementInformation.Properties [reader.LocalName];
368 if (prop == null || (serializeCollectionKey && !prop.IsKey)) {
369 if (!OnDeserializeUnrecognizedElement (reader.LocalName, reader)) {
371 ConfigurationElementCollection c = GetDefaultCollection ();
372 if (c != null && c.OnDeserializeUnrecognizedElement (reader.LocalName, reader))
375 throw new ConfigurationErrorsException ("Unrecognized element '" + reader.LocalName + "'.", reader);
381 throw new ConfigurationErrorsException ("Property '" + prop.Name + "' is not a ConfigurationElement.");
383 if (readProps.Contains (prop))
384 throw new ConfigurationErrorsException ("The element <" + prop.Name + "> may only appear once in this section.", reader);
386 ConfigurationElement val = (ConfigurationElement) prop.Value;
387 val.DeserializeElement (reader, serializeCollectionKey);
388 readProps [prop] = prop.Name;
390 if(depth == reader.Depth)
393 } while (depth < reader.Depth);
398 foreach (PropertyInformation prop in ElementInformation.Properties)
399 if (!String.IsNullOrEmpty(prop.Name) && prop.IsRequired && !readProps.ContainsKey (prop)) {
400 PropertyInformation p = ElementInformation.Properties [prop.Name];
402 object val = OnRequiredPropertyNotFound (prop.Name);
403 if (!object.Equals (val, prop.DefaultValue)) {
405 prop.IsModified = false;
413 protected virtual bool OnDeserializeUnrecognizedAttribute (string name, string value)
418 protected virtual bool OnDeserializeUnrecognizedElement (string element, XmlReader reader)
423 protected virtual object OnRequiredPropertyNotFound (string name)
425 throw new ConfigurationErrorsException ("Required attribute '" + name + "' not found.");
428 protected virtual void PreSerialize (XmlWriter writer)
432 protected virtual void PostDeserialize ()
436 protected internal virtual void InitializeDefault ()
440 protected internal virtual bool IsModified ()
445 foreach (PropertyInformation prop in ElementInformation.Properties) {
448 var element = prop.Value as ConfigurationElement;
449 if ((element == null) || !element.IsModified ())
459 protected internal virtual void SetReadOnly ()
464 public virtual bool IsReadOnly ()
469 protected internal virtual void Reset (ConfigurationElement parentElement)
471 elementPresent = false;
473 if (parentElement != null)
474 ElementInformation.Reset (parentElement.ElementInformation);
476 InitializeDefault ();
479 protected internal virtual void ResetModified ()
483 foreach (PropertyInformation p in ElementInformation.Properties) {
484 p.IsModified = false;
486 var element = p.Value as ConfigurationElement;
488 element.ResetModified ();
492 protected internal virtual bool SerializeElement (XmlWriter writer, bool serializeCollectionKey)
494 PreSerialize (writer);
496 if (serializeCollectionKey) {
497 ConfigurationPropertyCollection props = GetKeyProperties ();
498 foreach (ConfigurationProperty prop in props)
499 writer.WriteAttributeString (prop.Name, prop.ConvertToString (this[prop.Name]));
500 return props.Count > 0;
503 bool wroteData = false;
505 foreach (PropertyInformation prop in ElementInformation.Properties)
510 if (saveContext == null)
511 throw new InvalidOperationException ();
512 if (!saveContext.HasValue (prop))
515 writer.WriteAttributeString (prop.Name, prop.GetStringValue ());
519 foreach (PropertyInformation prop in ElementInformation.Properties)
524 ConfigurationElement val = (ConfigurationElement) prop.Value;
526 wroteData = val.SerializeToXmlElement (writer, prop.Name) || wroteData;
531 protected internal virtual bool SerializeToXmlElement (
532 XmlWriter writer, string elementName)
534 if (saveContext == null)
535 throw new InvalidOperationException ();
536 if (!saveContext.HasValues ())
539 if (elementName != null && elementName != "")
540 writer.WriteStartElement (elementName);
541 bool res = SerializeElement (writer, false);
542 if (elementName != null && elementName != "")
543 writer.WriteEndElement ();
547 protected internal virtual void Unmerge (
548 ConfigurationElement source, ConfigurationElement parent,
549 ConfigurationSaveMode updateMode)
551 if (parent != null && source.GetType() != parent.GetType())
552 throw new ConfigurationErrorsException ("Can't unmerge two elements of different type");
554 bool isMinimalOrModified = updateMode == ConfigurationSaveMode.Minimal ||
555 updateMode == ConfigurationSaveMode.Modified;
557 foreach (PropertyInformation prop in source.ElementInformation.Properties)
559 if (prop.ValueOrigin == PropertyValueOrigin.Default)
562 PropertyInformation unmergedProp = ElementInformation.Properties [prop.Name];
564 object sourceValue = prop.Value;
565 if (parent == null || !parent.HasValue (prop.Name)) {
566 unmergedProp.Value = sourceValue;
570 if (sourceValue == null)
573 object parentValue = parent [prop.Name];
574 if (!prop.IsElement) {
575 if (!object.Equals (sourceValue, parentValue) ||
576 (updateMode == ConfigurationSaveMode.Full) ||
577 (updateMode == ConfigurationSaveMode.Modified && prop.ValueOrigin == PropertyValueOrigin.SetHere))
578 unmergedProp.Value = sourceValue;
582 var sourceElement = (ConfigurationElement) sourceValue;
583 if (isMinimalOrModified && !sourceElement.IsModified ())
585 if (parentValue == null) {
586 unmergedProp.Value = sourceValue;
590 var parentElement = (ConfigurationElement) parentValue;
591 ConfigurationElement copy = (ConfigurationElement) unmergedProp.Value;
592 copy.Unmerge (sourceElement, parentElement, updateMode);
596 internal bool HasValue (string propName)
598 PropertyInformation info = ElementInformation.Properties [propName];
599 return info != null && info.ValueOrigin != PropertyValueOrigin.Default;
602 internal bool IsReadFromConfig (string propName)
604 PropertyInformation info = ElementInformation.Properties [propName];
605 return info != null && info.ValueOrigin == PropertyValueOrigin.SetHere;
608 internal bool IsElementPresent
610 get { return elementPresent; }
613 void ValidateValue (ConfigurationProperty p, string value)
615 ConfigurationValidatorBase validator;
616 if (p == null || (validator = p.Validator) == null)
619 if (!validator.CanValidate (p.Type))
620 throw new ConfigurationErrorsException (
621 String.Format ("Validator does not support type {0}", p.Type));
622 validator.Validate (p.ConvertFromString (value));
628 * SerializeElement() and SerializeToXmlElement() need to emit different output
629 * based on the ConfigurationSaveMode that's being used. Unfortunately, neither
630 * of these methods take it as an argument and there seems to be no documented way
633 * The parent element is needed because the element could be set to a different
634 * than the default value in a parent configuration file, then set locally to that
635 * same value. This makes the element appear locally modified (so it's included
636 * with ConfigurationSaveMode.Modified), but it should not be emitted with
637 * ConfigurationSaveMode.Minimal.
639 * In theory, we could save it into some private field in Unmerge(), but the
640 * problem is that Unmerge() is kinda expensive and we also need a way of
641 * determining whether or not the configuration has changed in Configuration.Save(),
642 * prior to opening the output file for writing.
644 * There are two places from where HasValues() is called:
645 * a) From Configuration.Save() / SaveAs() to check whether the configuration needs
646 * to be saved. This check is done prior to opening the file for writing.
647 * b) From SerializeToXmlElement() to check whether to emit the element, using the
648 * parent and mode values from the cached 'SaveContext'.
653 * Check whether property 'prop' should be included in the serialized XML
654 * based on the current ConfigurationSaveMode.
656 internal bool HasValue (ConfigurationElement parent, PropertyInformation prop,
657 ConfigurationSaveMode mode)
659 if (prop.ValueOrigin == PropertyValueOrigin.Default)
662 if (mode == ConfigurationSaveMode.Modified &&
663 prop.ValueOrigin == PropertyValueOrigin.SetHere && prop.IsModified) {
664 // Value has been modified locally, so we always emit it
665 // with ConfigurationSaveMode.Modified.
670 * Ok, now we have to check whether we're different from the inherited
671 * value - which could either be a value that's set in a parent
672 * configuration file or the default value.
675 var hasParentValue = parent != null && parent.HasValue (prop.Name);
676 var parentOrDefault = hasParentValue ? parent [prop.Name] : prop.DefaultValue;
679 return !object.Equals (prop.Value, parentOrDefault);
682 * Ok, it's an element that has been set in a parent configuration file. *
683 * Recursively call HasValues() to check whether it's been locally modified.
685 var element = (ConfigurationElement) prop.Value;
686 var parentElement = (ConfigurationElement) parentOrDefault;
688 return element.HasValues (parentElement, mode);
692 * Check whether this element should be included in the serialized XML
693 * based on the current ConfigurationSaveMode.
695 * The 'parent' value is needed to determine whether the element currently
696 * has a different value from what's been set in the parent configuration
699 internal virtual bool HasValues (ConfigurationElement parent, ConfigurationSaveMode mode)
701 if (mode == ConfigurationSaveMode.Full)
703 if (modified && (mode == ConfigurationSaveMode.Modified))
706 foreach (PropertyInformation prop in ElementInformation.Properties) {
707 if (HasValue (parent, prop, mode))
715 * Cache the current 'parent' and 'mode' values for later use in SerializeToXmlElement()
716 * and SerializeElement().
718 * Make sure to call base when overriding this in a derived class.
720 internal virtual void PrepareSave (ConfigurationElement parent, ConfigurationSaveMode mode)
722 saveContext = new SaveContext (this, parent, mode);
724 foreach (PropertyInformation prop in ElementInformation.Properties)
729 var elem = (ConfigurationElement)prop.Value;
730 if (parent == null || !parent.HasValue (prop.Name))
731 elem.PrepareSave (null, mode);
733 var parentValue = (ConfigurationElement)parent [prop.Name];
734 elem.PrepareSave (parentValue, mode);
739 SaveContext saveContext;
742 public readonly ConfigurationElement Element;
743 public readonly ConfigurationElement Parent;
744 public readonly ConfigurationSaveMode Mode;
746 public SaveContext (ConfigurationElement element, ConfigurationElement parent,
747 ConfigurationSaveMode mode)
749 this.Element = element;
750 this.Parent = parent;
754 public bool HasValues ()
756 if (Mode == ConfigurationSaveMode.Full)
758 return Element.HasValues (Parent, Mode);
761 public bool HasValue (PropertyInformation prop)
763 if (Mode == ConfigurationSaveMode.Full)
765 return Element.HasValue (Parent, prop, Mode);
770 internal class ElementMap
772 static readonly Hashtable elementMaps = Hashtable.Synchronized (new Hashtable ());
774 readonly ConfigurationPropertyCollection properties;
775 readonly ConfigurationCollectionAttribute collectionAttribute;
777 public static ElementMap GetMap (Type t)
779 ElementMap map = elementMaps [t] as ElementMap;
780 if (map != null) return map;
781 map = new ElementMap (t);
782 elementMaps [t] = map;
786 public ElementMap (Type t)
788 properties = new ConfigurationPropertyCollection ();
790 collectionAttribute = Attribute.GetCustomAttribute (t, typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;
792 PropertyInfo[] props = t.GetProperties (BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance);
793 foreach (PropertyInfo prop in props)
795 ConfigurationPropertyAttribute at = Attribute.GetCustomAttribute (prop, typeof(ConfigurationPropertyAttribute)) as ConfigurationPropertyAttribute;
796 if (at == null) continue;
797 string name = at.Name != null ? at.Name : prop.Name;
799 ConfigurationValidatorAttribute validatorAttr = Attribute.GetCustomAttribute (prop, typeof (ConfigurationValidatorAttribute)) as ConfigurationValidatorAttribute;
800 ConfigurationValidatorBase validator = validatorAttr != null ? validatorAttr.ValidatorInstance : null;
802 TypeConverterAttribute convertAttr = (TypeConverterAttribute) Attribute.GetCustomAttribute (prop, typeof (TypeConverterAttribute));
803 TypeConverter converter = convertAttr != null ? (TypeConverter) Activator.CreateInstance (Type.GetType (convertAttr.ConverterTypeName), true) : null;
804 ConfigurationProperty cp = new ConfigurationProperty (name, prop.PropertyType, at.DefaultValue, converter, validator, at.Options);
806 cp.CollectionAttribute = Attribute.GetCustomAttribute (prop, typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;
811 public ConfigurationCollectionAttribute CollectionAttribute
813 get { return collectionAttribute; }
816 public bool HasProperties
818 get { return properties.Count > 0; }
821 public ConfigurationPropertyCollection Properties