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)
33 using System.Collections;
35 using System.Reflection;
37 using System.ComponentModel;
39 namespace System.Configuration
41 public abstract class ConfigurationElement
46 ConfigurationPropertyCollection keyProps;
47 ConfigurationElementCollection defaultCollection;
49 ElementInformation elementInfo;
50 ConfigurationElementProperty elementProperty;
51 Configuration _configuration;
54 internal Configuration Configuration {
55 get { return _configuration; }
56 set { _configuration = value; }
59 protected ConfigurationElement ()
63 internal virtual void InitFromProperty (PropertyInformation propertyInfo)
65 elementInfo = new ElementInformation (this, propertyInfo);
69 public ElementInformation ElementInformation {
71 if (elementInfo == null)
72 elementInfo = new ElementInformation (this, null);
77 internal string RawXml {
78 get { return rawXml; }
80 // FIXME: this hack is nasty. We should make
81 // some refactory on the entire assembly.
82 if (rawXml == null || value != null)
87 protected internal virtual void Init ()
91 protected internal virtual ConfigurationElementProperty ElementProperty {
93 if (elementProperty == null)
94 elementProperty = new ConfigurationElementProperty (ElementInformation.Validator);
95 return elementProperty;
99 protected ContextInformation EvaluationContext {
101 if (Configuration != null)
102 return Configuration.EvaluationContext;
103 throw new ConfigurationErrorsException (
104 "This element is not currently associated with any context.");
108 ConfigurationLockCollection lockAllAttributesExcept;
109 public ConfigurationLockCollection LockAllAttributesExcept {
111 if (lockAllAttributesExcept == null) {
112 lockAllAttributesExcept = new ConfigurationLockCollection (this, ConfigurationLockType.Attribute | ConfigurationLockType.Exclude);
115 return lockAllAttributesExcept;
119 ConfigurationLockCollection lockAllElementsExcept;
120 public ConfigurationLockCollection LockAllElementsExcept {
122 if (lockAllElementsExcept == null) {
123 lockAllElementsExcept = new ConfigurationLockCollection (this, ConfigurationLockType.Element | ConfigurationLockType.Exclude);
126 return lockAllElementsExcept;
130 ConfigurationLockCollection lockAttributes;
131 public ConfigurationLockCollection LockAttributes {
133 if (lockAttributes == null) {
134 lockAttributes = new ConfigurationLockCollection (this, ConfigurationLockType.Attribute);
137 return lockAttributes;
141 ConfigurationLockCollection lockElements;
142 public ConfigurationLockCollection LockElements {
144 if (lockElements == null) {
145 lockElements = new ConfigurationLockCollection (this, ConfigurationLockType.Element);
153 public bool LockItem {
154 get { return lockItem; }
155 set { lockItem = value; }
159 protected virtual void ListErrors (IList list)
161 throw new NotImplementedException ();
165 protected void SetPropertyValue (ConfigurationProperty prop, object value, bool ignoreLocks)
169 /* XXX all i know for certain is that Validation happens here */
170 prop.Validate (value);
172 /* XXX presumably the actual setting of the
173 * property happens here instead of in the
174 * set_Item code below, but that would mean
175 * the Value needs to be stuffed in the
176 * property, not the propertyinfo (or else the
177 * property needs a ref to the property info
178 * to correctly set the value). */
181 catch (Exception e) {
182 throw new ConfigurationErrorsException (String.Format ("The value for the property '{0}' on type {1} is not valid.", prop.Name, this.ElementInformation.Type), e);
186 internal ConfigurationPropertyCollection GetKeyProperties ()
188 if (keyProps != null) return keyProps;
190 ConfigurationPropertyCollection tmpkeyProps = new ConfigurationPropertyCollection ();
191 foreach (ConfigurationProperty prop in Properties) {
193 tmpkeyProps.Add (prop);
196 return keyProps = tmpkeyProps;
199 internal ConfigurationElementCollection GetDefaultCollection ()
201 if (defaultCollection != null) return defaultCollection;
203 ConfigurationProperty defaultCollectionProp = null;
205 foreach (ConfigurationProperty prop in Properties) {
206 if (prop.IsDefaultCollection) {
207 defaultCollectionProp = prop;
212 if (defaultCollectionProp != null) {
213 defaultCollection = this [defaultCollectionProp] as ConfigurationElementCollection;
216 return defaultCollection;
219 protected internal object this [ConfigurationProperty property] {
220 get { return this [property.Name]; }
221 set { this [property.Name] = value; }
224 protected internal object this [string property_name] {
226 PropertyInformation pi = ElementInformation.Properties [property_name];
228 throw new InvalidOperationException ("Property '" + property_name + "' not found in configuration element");
234 PropertyInformation pi = ElementInformation.Properties [property_name];
236 throw new InvalidOperationException ("Property '" + property_name + "' not found in configuration element");
238 SetPropertyValue (pi.Property, value, false);
245 protected internal virtual ConfigurationPropertyCollection Properties {
248 map = ElementMap.GetMap (GetType());
249 return map.Properties;
253 public override bool Equals (object compareTo)
255 ConfigurationElement other = compareTo as ConfigurationElement;
256 if (other == null) return false;
257 if (GetType() != other.GetType()) return false;
259 foreach (ConfigurationProperty prop in Properties) {
260 if (!object.Equals (this [prop], other [prop]))
266 public override int GetHashCode ()
271 foreach (ConfigurationProperty prop in Properties) {
276 code += o.GetHashCode ();
282 internal virtual bool HasLocalModifications ()
284 foreach (PropertyInformation pi in ElementInformation.Properties)
285 if (pi.ValueOrigin == PropertyValueOrigin.SetHere && pi.IsModified)
291 protected internal virtual void DeserializeElement (XmlReader reader, bool serializeCollectionKey)
293 Hashtable readProps = new Hashtable ();
295 reader.MoveToContent ();
296 elementPresent = true;
298 while (reader.MoveToNextAttribute ())
300 PropertyInformation prop = ElementInformation.Properties [reader.LocalName];
301 if (prop == null || (serializeCollectionKey && !prop.IsKey)) {
302 /* handle the built in ConfigurationElement attributes here */
303 if (reader.LocalName == "lockAllAttributesExcept") {
304 LockAllAttributesExcept.SetFromList (reader.Value);
306 else if (reader.LocalName == "lockAllElementsExcept") {
307 LockAllElementsExcept.SetFromList (reader.Value);
309 else if (reader.LocalName == "lockAttributes") {
310 LockAttributes.SetFromList (reader.Value);
312 else if (reader.LocalName == "lockElements") {
313 LockElements.SetFromList (reader.Value);
315 else if (reader.LocalName == "lockItem") {
316 LockItem = (reader.Value.ToLowerInvariant () == "true");
318 else if (reader.LocalName == "xmlns") {
320 } else if (this is ConfigurationSection && reader.LocalName == "configSource") {
322 } else if (!OnDeserializeUnrecognizedAttribute (reader.LocalName, reader.Value))
323 throw new ConfigurationErrorsException ("Unrecognized attribute '" + reader.LocalName + "'.", reader);
328 if (readProps.ContainsKey (prop))
329 throw new ConfigurationErrorsException ("The attribute '" + prop.Name + "' may only appear once in this element.", reader);
333 value = reader.Value;
334 ValidateValue (prop.Property, value);
335 prop.SetStringValue (value);
336 } catch (ConfigurationErrorsException) {
338 } catch (ConfigurationException) {
340 } catch (Exception ex) {
341 string msg = String.Format ("The value for the property '{0}' is not valid. The error is: {1}", prop.Name, ex.Message);
342 throw new ConfigurationErrorsException (msg, reader);
344 readProps [prop] = prop.Name;
346 ConfigXmlTextReader _reader = reader as ConfigXmlTextReader;
347 if (_reader != null){
348 prop.Source = _reader.Filename;
349 prop.LineNumber = _reader.LineNumber;
353 reader.MoveToElement ();
354 if (reader.IsEmptyElement) {
357 int depth = reader.Depth;
359 reader.ReadStartElement ();
360 reader.MoveToContent ();
363 if (reader.NodeType != XmlNodeType.Element) {
368 PropertyInformation prop = ElementInformation.Properties [reader.LocalName];
369 if (prop == null || (serializeCollectionKey && !prop.IsKey)) {
370 if (!OnDeserializeUnrecognizedElement (reader.LocalName, reader)) {
372 ConfigurationElementCollection c = GetDefaultCollection ();
373 if (c != null && c.OnDeserializeUnrecognizedElement (reader.LocalName, reader))
376 throw new ConfigurationErrorsException ("Unrecognized element '" + reader.LocalName + "'.", reader);
382 throw new ConfigurationErrorsException ("Property '" + prop.Name + "' is not a ConfigurationElement.");
384 if (readProps.Contains (prop))
385 throw new ConfigurationErrorsException ("The element <" + prop.Name + "> may only appear once in this section.", reader);
387 ConfigurationElement val = (ConfigurationElement) prop.Value;
388 val.DeserializeElement (reader, serializeCollectionKey);
389 readProps [prop] = prop.Name;
391 if(depth == reader.Depth)
394 } while (depth < reader.Depth);
399 foreach (PropertyInformation prop in ElementInformation.Properties)
400 if (!String.IsNullOrEmpty(prop.Name) && prop.IsRequired && !readProps.ContainsKey (prop)) {
401 PropertyInformation p = ElementInformation.Properties [prop.Name];
403 object val = OnRequiredPropertyNotFound (prop.Name);
404 if (!object.Equals (val, prop.DefaultValue)) {
406 prop.IsModified = false;
414 protected virtual bool OnDeserializeUnrecognizedAttribute (string name, string value)
419 protected virtual bool OnDeserializeUnrecognizedElement (string element, XmlReader reader)
424 protected virtual object OnRequiredPropertyNotFound (string name)
426 throw new ConfigurationErrorsException ("Required attribute '" + name + "' not found.");
429 protected virtual void PreSerialize (XmlWriter writer)
433 protected virtual void PostDeserialize ()
437 protected internal virtual void InitializeDefault ()
441 protected internal virtual bool IsModified ()
446 foreach (PropertyInformation prop in ElementInformation.Properties) {
449 var element = prop.Value as ConfigurationElement;
450 if ((element == null) || !element.IsModified ())
460 protected internal virtual void SetReadOnly ()
465 public virtual bool IsReadOnly ()
470 protected internal virtual void Reset (ConfigurationElement parentElement)
472 elementPresent = false;
474 if (parentElement != null)
475 ElementInformation.Reset (parentElement.ElementInformation);
477 InitializeDefault ();
480 protected internal virtual void ResetModified ()
484 foreach (PropertyInformation p in ElementInformation.Properties) {
485 p.IsModified = false;
487 var element = p.Value as ConfigurationElement;
489 element.ResetModified ();
493 protected internal virtual bool SerializeElement (XmlWriter writer, bool serializeCollectionKey)
495 PreSerialize (writer);
497 if (serializeCollectionKey) {
498 ConfigurationPropertyCollection props = GetKeyProperties ();
499 foreach (ConfigurationProperty prop in props)
500 writer.WriteAttributeString (prop.Name, prop.ConvertToString (this[prop.Name]));
501 return props.Count > 0;
504 bool wroteData = false;
506 foreach (PropertyInformation prop in ElementInformation.Properties)
511 if (saveContext == null)
512 throw new InvalidOperationException ();
513 if (!saveContext.HasValue (prop))
516 writer.WriteAttributeString (prop.Name, prop.GetStringValue ());
520 foreach (PropertyInformation prop in ElementInformation.Properties)
525 ConfigurationElement val = (ConfigurationElement) prop.Value;
527 wroteData = val.SerializeToXmlElement (writer, prop.Name) || wroteData;
532 protected internal virtual bool SerializeToXmlElement (
533 XmlWriter writer, string elementName)
535 if (saveContext == null)
536 throw new InvalidOperationException ();
537 if (!saveContext.HasValues ())
540 if (elementName != null && elementName != "")
541 writer.WriteStartElement (elementName);
542 bool res = SerializeElement (writer, false);
543 if (elementName != null && elementName != "")
544 writer.WriteEndElement ();
548 protected internal virtual void Unmerge (
549 ConfigurationElement source, ConfigurationElement parent,
550 ConfigurationSaveMode updateMode)
552 if (parent != null && source.GetType() != parent.GetType())
553 throw new ConfigurationErrorsException ("Can't unmerge two elements of different type");
555 bool isMinimalOrModified = updateMode == ConfigurationSaveMode.Minimal ||
556 updateMode == ConfigurationSaveMode.Modified;
558 foreach (PropertyInformation prop in source.ElementInformation.Properties)
560 if (prop.ValueOrigin == PropertyValueOrigin.Default)
563 PropertyInformation unmergedProp = ElementInformation.Properties [prop.Name];
565 object sourceValue = prop.Value;
566 if (parent == null || !parent.HasValue (prop.Name)) {
567 unmergedProp.Value = sourceValue;
571 if (sourceValue == null)
574 object parentValue = parent [prop.Name];
575 if (!prop.IsElement) {
576 if (!object.Equals (sourceValue, parentValue) ||
577 (updateMode == ConfigurationSaveMode.Full) ||
578 (updateMode == ConfigurationSaveMode.Modified && prop.ValueOrigin == PropertyValueOrigin.SetHere))
579 unmergedProp.Value = sourceValue;
583 var sourceElement = (ConfigurationElement) sourceValue;
584 if (isMinimalOrModified && !sourceElement.IsModified ())
586 if (parentValue == null) {
587 unmergedProp.Value = sourceValue;
591 var parentElement = (ConfigurationElement) parentValue;
592 ConfigurationElement copy = (ConfigurationElement) unmergedProp.Value;
593 copy.Unmerge (sourceElement, parentElement, updateMode);
597 internal bool HasValue (string propName)
599 PropertyInformation info = ElementInformation.Properties [propName];
600 return info != null && info.ValueOrigin != PropertyValueOrigin.Default;
603 internal bool IsReadFromConfig (string propName)
605 PropertyInformation info = ElementInformation.Properties [propName];
606 return info != null && info.ValueOrigin == PropertyValueOrigin.SetHere;
609 internal bool IsElementPresent
611 get { return elementPresent; }
614 void ValidateValue (ConfigurationProperty p, string value)
616 ConfigurationValidatorBase validator;
617 if (p == null || (validator = p.Validator) == null)
620 if (!validator.CanValidate (p.Type))
621 throw new ConfigurationErrorsException (
622 String.Format ("Validator does not support type {0}", p.Type));
623 validator.Validate (p.ConvertFromString (value));
629 * SerializeElement() and SerializeToXmlElement() need to emit different output
630 * based on the ConfigurationSaveMode that's being used. Unfortunately, neither
631 * of these methods take it as an argument and there seems to be no documented way
634 * The parent element is needed because the element could be set to a different
635 * than the default value in a parent configuration file, then set locally to that
636 * same value. This makes the element appear locally modified (so it's included
637 * with ConfigurationSaveMode.Modified), but it should not be emitted with
638 * ConfigurationSaveMode.Minimal.
640 * In theory, we could save it into some private field in Unmerge(), but the
641 * problem is that Unmerge() is kinda expensive and we also need a way of
642 * determining whether or not the configuration has changed in Configuration.Save(),
643 * prior to opening the output file for writing.
645 * There are two places from where HasValues() is called:
646 * a) From Configuration.Save() / SaveAs() to check whether the configuration needs
647 * to be saved. This check is done prior to opening the file for writing.
648 * b) From SerializeToXmlElement() to check whether to emit the element, using the
649 * parent and mode values from the cached 'SaveContext'.
654 * Check whether property 'prop' should be included in the serialized XML
655 * based on the current ConfigurationSaveMode.
657 internal bool HasValue (ConfigurationElement parent, PropertyInformation prop,
658 ConfigurationSaveMode mode)
660 if (prop.ValueOrigin == PropertyValueOrigin.Default)
663 if (mode == ConfigurationSaveMode.Modified &&
664 prop.ValueOrigin == PropertyValueOrigin.SetHere && prop.IsModified) {
665 // Value has been modified locally, so we always emit it
666 // with ConfigurationSaveMode.Modified.
671 * Ok, now we have to check whether we're different from the inherited
672 * value - which could either be a value that's set in a parent
673 * configuration file or the default value.
676 var hasParentValue = parent != null && parent.HasValue (prop.Name);
677 var parentOrDefault = hasParentValue ? parent [prop.Name] : prop.DefaultValue;
680 return !object.Equals (prop.Value, parentOrDefault);
683 * Ok, it's an element that has been set in a parent configuration file. *
684 * Recursively call HasValues() to check whether it's been locally modified.
686 var element = (ConfigurationElement) prop.Value;
687 var parentElement = (ConfigurationElement) parentOrDefault;
689 return element.HasValues (parentElement, mode);
693 * Check whether this element should be included in the serialized XML
694 * based on the current ConfigurationSaveMode.
696 * The 'parent' value is needed to determine whether the element currently
697 * has a different value from what's been set in the parent configuration
700 internal virtual bool HasValues (ConfigurationElement parent, ConfigurationSaveMode mode)
702 if (mode == ConfigurationSaveMode.Full)
704 if (modified && (mode == ConfigurationSaveMode.Modified))
707 foreach (PropertyInformation prop in ElementInformation.Properties) {
708 if (HasValue (parent, prop, mode))
716 * Cache the current 'parent' and 'mode' values for later use in SerializeToXmlElement()
717 * and SerializeElement().
719 * Make sure to call base when overriding this in a derived class.
721 internal virtual void PrepareSave (ConfigurationElement parent, ConfigurationSaveMode mode)
723 saveContext = new SaveContext (this, parent, mode);
725 foreach (PropertyInformation prop in ElementInformation.Properties)
730 var elem = (ConfigurationElement)prop.Value;
731 if (parent == null || !parent.HasValue (prop.Name))
732 elem.PrepareSave (null, mode);
734 var parentValue = (ConfigurationElement)parent [prop.Name];
735 elem.PrepareSave (parentValue, mode);
740 SaveContext saveContext;
743 public readonly ConfigurationElement Element;
744 public readonly ConfigurationElement Parent;
745 public readonly ConfigurationSaveMode Mode;
747 public SaveContext (ConfigurationElement element, ConfigurationElement parent,
748 ConfigurationSaveMode mode)
750 this.Element = element;
751 this.Parent = parent;
755 public bool HasValues ()
757 if (Mode == ConfigurationSaveMode.Full)
759 return Element.HasValues (Parent, Mode);
762 public bool HasValue (PropertyInformation prop)
764 if (Mode == ConfigurationSaveMode.Full)
766 return Element.HasValue (Parent, prop, Mode);
771 internal class ElementMap
774 const string elementMapsKey = "ElementMap_elementMaps";
775 static Hashtable elementMaps
779 Hashtable tbl = (Hashtable) AppDomain.CurrentDomain.GetData (elementMapsKey);
781 lock (typeof (ElementMap)) {
782 tbl = (Hashtable) AppDomain.CurrentDomain.GetData (elementMapsKey);
784 tbl = Hashtable.Synchronized (new Hashtable ());
785 AppDomain.CurrentDomain.SetData (elementMapsKey, tbl);
793 static readonly Hashtable elementMaps = Hashtable.Synchronized (new Hashtable ());
796 readonly ConfigurationPropertyCollection properties;
797 readonly ConfigurationCollectionAttribute collectionAttribute;
799 public static ElementMap GetMap (Type t)
801 ElementMap map = elementMaps [t] as ElementMap;
802 if (map != null) return map;
803 map = new ElementMap (t);
804 elementMaps [t] = map;
808 public ElementMap (Type t)
810 properties = new ConfigurationPropertyCollection ();
812 collectionAttribute = Attribute.GetCustomAttribute (t, typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;
814 PropertyInfo[] props = t.GetProperties (BindingFlags.Public|BindingFlags.NonPublic|BindingFlags.Static|BindingFlags.Instance);
815 foreach (PropertyInfo prop in props)
817 ConfigurationPropertyAttribute at = Attribute.GetCustomAttribute (prop, typeof(ConfigurationPropertyAttribute)) as ConfigurationPropertyAttribute;
818 if (at == null) continue;
819 string name = at.Name != null ? at.Name : prop.Name;
821 ConfigurationValidatorAttribute validatorAttr = Attribute.GetCustomAttribute (prop, typeof (ConfigurationValidatorAttribute)) as ConfigurationValidatorAttribute;
822 ConfigurationValidatorBase validator = validatorAttr != null ? validatorAttr.ValidatorInstance : null;
824 TypeConverterAttribute convertAttr = (TypeConverterAttribute) Attribute.GetCustomAttribute (prop, typeof (TypeConverterAttribute));
825 TypeConverter converter = convertAttr != null ? (TypeConverter) Activator.CreateInstance (Type.GetType (convertAttr.ConverterTypeName), true) : null;
826 ConfigurationProperty cp = new ConfigurationProperty (name, prop.PropertyType, at.DefaultValue, converter, validator, at.Options);
828 cp.CollectionAttribute = Attribute.GetCustomAttribute (prop, typeof(ConfigurationCollectionAttribute)) as ConfigurationCollectionAttribute;
833 public ConfigurationCollectionAttribute CollectionAttribute
835 get { return collectionAttribute; }
838 public bool HasProperties
840 get { return properties.Count > 0; }
843 public ConfigurationPropertyCollection Properties