2 // System.Configuration.Configuration.cs
5 // Duncan Mak (duncan@ximian.com)
6 // Lluis Sanchez Gual (lluis@novell.com)
8 // Permission is hereby granted, free of charge, to any person obtaining
9 // a copy of this software and associated documentation files (the
10 // "Software"), to deal in the Software without restriction, including
11 // without limitation the rights to use, copy, modify, merge, publish,
12 // distribute, sublicense, and/or sell copies of the Software, and to
13 // permit persons to whom the Software is furnished to do so, subject to
14 // the following conditions:
16 // The above copyright notice and this permission notice shall be
17 // included in all copies or substantial portions of the Software.
19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
22 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
23 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
31 using System.Collections;
32 using System.Collections.Specialized;
33 using System.Configuration.Internal;
34 using System.ComponentModel;
35 using System.Reflection;
39 namespace System.Configuration {
41 // For configuration document, use this XmlDocument instead of the standard one. This ignores xmlns attribute for MS.
42 internal class ConfigurationXmlDocument : XmlDocument
44 public override XmlElement CreateElement (string prefix, string localName, string namespaceURI)
46 if (namespaceURI == "http://schemas.microsoft.com/.NetConfiguration/v2.0")
47 return base.CreateElement (String.Empty, localName, String.Empty);
49 return base.CreateElement (prefix, localName, namespaceURI);
53 public sealed class Configuration
56 Hashtable elementData = new Hashtable ();
58 ConfigurationSectionGroup rootSectionGroup;
59 ConfigurationLocationCollection locations;
60 SectionGroupInfo rootGroup;
66 string locationConfigPath;
67 string locationSubPath;
69 internal static event ConfigurationSaveEventHandler SaveStart;
70 internal static event ConfigurationSaveEventHandler SaveEnd;
72 internal Configuration (Configuration parent, string locationSubPath)
75 this.system = parent.system;
76 this.rootGroup = parent.rootGroup;
77 this.locationSubPath = locationSubPath;
78 this.configPath = parent.ConfigPath;
81 internal Configuration (InternalConfigurationSystem system, string locationSubPath)
86 system.InitForConfiguration (ref locationSubPath, out configPath, out locationConfigPath);
88 Configuration parent = null;
90 if (locationSubPath != null) {
91 parent = new Configuration (system, locationSubPath);
92 if (locationConfigPath != null)
93 parent = parent.FindLocationConfiguration (locationConfigPath, parent);
96 Init (system, configPath, parent);
99 internal Configuration FindLocationConfiguration (string relativePath, Configuration defaultConfiguration)
101 Configuration parentConfig = defaultConfiguration;
103 if (!String.IsNullOrEmpty (LocationConfigPath)) {
104 Configuration parentFile = GetParentWithFile ();
105 if (parentFile != null) {
106 string parentRelativePath = system.Host.GetConfigPathFromLocationSubPath (configPath, relativePath);
107 parentConfig = parentFile.FindLocationConfiguration (parentRelativePath, defaultConfiguration);
111 string relConfigPath = configPath.Substring (1) + "/";
112 if (relativePath.StartsWith (relConfigPath, StringComparison.Ordinal))
113 relativePath = relativePath.Substring (relConfigPath.Length);
115 ConfigurationLocation loc = Locations.FindBest (relativePath);
119 loc.SetParentConfiguration (parentConfig);
120 return loc.OpenConfiguration ();
123 internal void Init (IConfigSystem system, string configPath, Configuration parent)
125 this.system = system;
126 this.configPath = configPath;
127 streamName = system.Host.GetStreamName (configPath);
128 this.parent = parent;
130 rootGroup = parent.rootGroup;
132 rootGroup = new SectionGroupInfo ();
133 rootGroup.StreamName = streamName;
137 if (streamName != null)
139 } catch (XmlException ex) {
140 throw new ConfigurationErrorsException (ex.Message, ex, streamName, 0);
144 internal Configuration Parent {
145 get { return parent; }
146 set { parent = value; }
149 internal Configuration GetParentWithFile ()
151 Configuration parentFile = Parent;
152 while (parentFile != null && !parentFile.HasFile)
153 parentFile = parentFile.Parent;
157 internal string FileName {
158 get { return streamName; }
161 internal IInternalConfigHost ConfigHost {
162 get { return system.Host; }
165 internal string LocationConfigPath {
166 get { return locationConfigPath; }
169 internal string GetLocationSubPath ()
171 Configuration confg = parent;
173 while (confg != null) {
174 path = confg.locationSubPath;
175 if (!String.IsNullOrEmpty (path))
177 confg = confg.parent;
182 internal string ConfigPath {
183 get { return configPath; }
186 public AppSettingsSection AppSettings {
187 get { return (AppSettingsSection) GetSection ("appSettings"); }
190 public ConnectionStringsSection ConnectionStrings {
191 get { return (ConnectionStringsSection) GetSection ("connectionStrings"); }
194 // MSDN: If the value for this FilePath property represents a merged view and
195 // no actual file exists for the application, the path to the parent configuration
197 public string FilePath {
199 if (streamName == null && parent != null)
200 return parent.FilePath;
205 public bool HasFile {
211 ContextInformation evaluationContext;
212 public ContextInformation EvaluationContext {
214 if (evaluationContext == null) {
215 object ctx = system.Host.CreateConfigurationContext (configPath, GetLocationSubPath() );
216 evaluationContext = new ContextInformation (this, ctx);
220 return evaluationContext;
224 public ConfigurationLocationCollection Locations {
226 if (locations == null) locations = new ConfigurationLocationCollection ();
231 public bool NamespaceDeclared {
232 get { return rootNamespace != null; }
233 set { rootNamespace = value ? "http://schemas.microsoft.com/.NetConfiguration/v2.0" : null; }
236 public ConfigurationSectionGroup RootSectionGroup {
238 if (rootSectionGroup == null) {
239 rootSectionGroup = new ConfigurationSectionGroup ();
240 rootSectionGroup.Initialize (this, rootGroup);
242 return rootSectionGroup;
246 public ConfigurationSectionGroupCollection SectionGroups {
247 get { return RootSectionGroup.SectionGroups; }
250 public ConfigurationSectionCollection Sections {
251 get { return RootSectionGroup.Sections; }
254 public ConfigurationSection GetSection (string path)
256 string[] parts = path.Split ('/');
257 if (parts.Length == 1)
258 return Sections [parts[0]];
260 ConfigurationSectionGroup group = SectionGroups [parts[0]];
261 for (int n=1; group != null && n<parts.Length-1; n++)
262 group = group.SectionGroups [parts [n]];
265 return group.Sections [parts [parts.Length - 1]];
270 public ConfigurationSectionGroup GetSectionGroup (string path)
272 string[] parts = path.Split ('/');
273 ConfigurationSectionGroup group = SectionGroups [parts[0]];
274 for (int n=1; group != null && n<parts.Length; n++)
275 group = group.SectionGroups [parts [n]];
279 internal ConfigurationSection GetSectionInstance (SectionInfo config, bool createDefaultInstance)
281 object data = elementData [config];
282 ConfigurationSection sec = data as ConfigurationSection;
283 if (sec != null || !createDefaultInstance) return sec;
285 object secObj = config.CreateInstance ();
286 sec = secObj as ConfigurationSection;
288 DefaultSection ds = new DefaultSection ();
289 ds.SectionHandler = secObj as IConfigurationSectionHandler;
292 sec.Configuration = this;
294 ConfigurationSection parentSection = null;
295 if (parent != null) {
296 parentSection = parent.GetSectionInstance (config, true);
297 sec.SectionInformation.SetParentSection (parentSection);
299 sec.SectionInformation.ConfigFilePath = FilePath;
301 sec.ConfigContext = system.Host.CreateDeprecatedConfigContext(configPath);
303 string xml = data as string;
305 sec.Reset (parentSection);
308 XmlTextReader r = new ConfigXmlTextReader (new StringReader (xml), FilePath);
309 sec.DeserializeSection (r);
312 if (!String.IsNullOrEmpty (sec.SectionInformation.ConfigSource) && !String.IsNullOrEmpty (FilePath))
313 sec.DeserializeConfigSource (Path.GetDirectoryName (FilePath));
316 elementData [config] = sec;
320 internal ConfigurationSectionGroup GetSectionGroupInstance (SectionGroupInfo group)
322 ConfigurationSectionGroup gr = group.CreateInstance () as ConfigurationSectionGroup;
323 if (gr != null) gr.Initialize (this, group);
327 internal void SetConfigurationSection (SectionInfo config, ConfigurationSection sec)
329 elementData [config] = sec;
332 internal void SetSectionXml (SectionInfo config, string data)
334 elementData [config] = data;
337 internal string GetSectionXml (SectionInfo config)
339 return elementData [config] as string;
342 internal void CreateSection (SectionGroupInfo group, string name, ConfigurationSection sec)
344 if (group.HasChild (name))
345 throw new ConfigurationErrorsException ("Cannot add a ConfigurationSection. A section or section group already exists with the name '" + name + "'");
347 if (!HasFile && !sec.SectionInformation.AllowLocation)
348 throw new ConfigurationErrorsException ("The configuration section <" + name + "> cannot be defined inside a <location> element.");
350 if (!system.Host.IsDefinitionAllowed (configPath, sec.SectionInformation.AllowDefinition, sec.SectionInformation.AllowExeDefinition)) {
351 object ctx = sec.SectionInformation.AllowExeDefinition != ConfigurationAllowExeDefinition.MachineToApplication ? (object) sec.SectionInformation.AllowExeDefinition : (object) sec.SectionInformation.AllowDefinition;
352 throw new ConfigurationErrorsException ("The section <" + name + "> can't be defined in this configuration file (the allowed definition context is '" + ctx + "').");
355 if (sec.SectionInformation.Type == null)
356 sec.SectionInformation.Type = system.Host.GetConfigTypeName (sec.GetType ());
358 SectionInfo section = new SectionInfo (name, sec.SectionInformation);
359 section.StreamName = streamName;
360 section.ConfigHost = system.Host;
361 group.AddChild (section);
362 elementData [section] = sec;
363 sec.Configuration = this;
366 internal void CreateSectionGroup (SectionGroupInfo parentGroup, string name, ConfigurationSectionGroup sec)
368 if (parentGroup.HasChild (name)) throw new ConfigurationErrorsException ("Cannot add a ConfigurationSectionGroup. A section or section group already exists with the name '" + name + "'");
369 if (sec.Type == null) sec.Type = system.Host.GetConfigTypeName (sec.GetType ());
372 SectionGroupInfo section = new SectionGroupInfo (name, sec.Type);
373 section.StreamName = streamName;
374 section.ConfigHost = system.Host;
375 parentGroup.AddChild (section);
376 elementData [section] = sec;
378 sec.Initialize (this, section);
381 internal void RemoveConfigInfo (ConfigInfo config)
383 elementData.Remove (config);
388 Save (ConfigurationSaveMode.Modified, false);
391 public void Save (ConfigurationSaveMode mode)
396 public void Save (ConfigurationSaveMode mode, bool forceUpdateAll)
398 if (!forceUpdateAll && (mode != ConfigurationSaveMode.Full) && !HasValues (mode)) {
403 ConfigurationSaveEventHandler saveStart = SaveStart;
404 ConfigurationSaveEventHandler saveEnd = SaveEnd;
407 Exception saveEx = null;
408 Stream stream = system.Host.OpenStreamForWrite (streamName, null, ref ctx);
410 if (saveStart != null)
411 saveStart (this, new ConfigurationSaveEventArgs (streamName, true, null, ctx));
413 Save (stream, mode, forceUpdateAll);
414 system.Host.WriteCompleted (streamName, true, ctx);
415 } catch (Exception ex) {
417 system.Host.WriteCompleted (streamName, false, ctx);
422 saveEnd (this, new ConfigurationSaveEventArgs (streamName, false, saveEx, ctx));
426 public void SaveAs (string filename)
428 SaveAs (filename, ConfigurationSaveMode.Modified, false);
431 public void SaveAs (string filename, ConfigurationSaveMode mode)
433 SaveAs (filename, mode, false);
436 [MonoInternalNote ("Detect if file has changed")]
437 public void SaveAs (string filename, ConfigurationSaveMode mode, bool forceUpdateAll)
439 if (!forceUpdateAll && (mode != ConfigurationSaveMode.Full) && !HasValues (mode)) {
444 string dir = Path.GetDirectoryName (Path.GetFullPath (filename));
445 if (!Directory.Exists (dir))
446 Directory.CreateDirectory (dir);
447 Save (new FileStream (filename, FileMode.OpenOrCreate, FileAccess.Write), mode, forceUpdateAll);
450 void Save (Stream stream, ConfigurationSaveMode mode, bool forceUpdateAll)
452 XmlTextWriter tw = new XmlTextWriter (new StreamWriter (stream));
453 tw.Formatting = Formatting.Indented;
455 tw.WriteStartDocument ();
456 if (rootNamespace != null)
457 tw.WriteStartElement ("configuration", rootNamespace);
459 tw.WriteStartElement ("configuration");
460 if (rootGroup.HasConfigContent (this)) {
461 rootGroup.WriteConfig (this, tw, mode);
464 foreach (ConfigurationLocation loc in Locations) {
465 if (loc.OpenedConfiguration == null) {
467 tw.WriteRaw (loc.XmlContent);
470 tw.WriteStartElement ("location");
471 tw.WriteAttributeString ("path", loc.Path);
472 if (!loc.AllowOverride)
473 tw.WriteAttributeString ("allowOverride", "false");
474 loc.OpenedConfiguration.SaveData (tw, mode, forceUpdateAll);
475 tw.WriteEndElement ();
479 SaveData (tw, mode, forceUpdateAll);
480 tw.WriteEndElement ();
489 void SaveData (XmlTextWriter tw, ConfigurationSaveMode mode, bool forceUpdateAll)
491 rootGroup.WriteRootData (tw, this, mode);
494 bool HasValues (ConfigurationSaveMode mode)
496 foreach (ConfigurationLocation loc in Locations) {
497 if (loc.OpenedConfiguration == null)
499 if (loc.OpenedConfiguration.HasValues (mode))
503 return rootGroup.HasValues (this, mode);
506 void ResetModified ()
508 foreach (ConfigurationLocation loc in Locations) {
509 if (loc.OpenedConfiguration == null)
511 loc.OpenedConfiguration.ResetModified ();
514 rootGroup.ResetModified (this);
519 if (String.IsNullOrEmpty (streamName))
522 Stream stream = null;
524 stream = system.Host.OpenStreamForRead (streamName);
531 using (XmlTextReader reader = new ConfigXmlTextReader (stream, streamName)) {
532 ReadConfigFile (reader, streamName);
538 void ReadConfigFile (XmlReader reader, string fileName)
540 reader.MoveToContent ();
542 if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration")
543 ThrowException ("Configuration file does not have a valid root element", reader);
545 if (reader.HasAttributes) {
546 while (reader.MoveToNextAttribute ()) {
547 if (reader.LocalName == "xmlns") {
548 rootNamespace = reader.Value;
551 ThrowException (String.Format ("Unrecognized attribute '{0}' in root element", reader.LocalName), reader);
555 reader.MoveToElement ();
557 if (reader.IsEmptyElement) {
562 reader.ReadStartElement ();
563 reader.MoveToContent ();
565 if (reader.LocalName == "configSections") {
566 if (reader.HasAttributes)
567 ThrowException ("Unrecognized attribute in <configSections>.", reader);
569 rootGroup.ReadConfig (this, fileName, reader);
572 rootGroup.ReadRootData (reader, this, true);
575 internal void ReadData (XmlReader reader, bool allowOverride)
577 rootGroup.ReadData (this, reader, allowOverride);
581 private void ThrowException (string text, XmlReader reader)
583 IXmlLineInfo li = reader as IXmlLineInfo;
584 throw new ConfigurationErrorsException (text, streamName, li != null ? li.LineNumber : 0);