1 // 2 // System.Configuration.Configuration.cs 3 // 4 // Authors: 5 // Duncan Mak (duncan@ximian.com) 6 // Lluis Sanchez Gual (lluis@novell.com) 7 // 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: 15 // 16 // The above copyright notice and this permission notice shall be 17 // included in all copies or substantial portions of the Software. 18 // 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. 26 // 27 // Copyright (C) 2004 Novell, Inc (http://www.novell.com) 28 // 29 30 using System; 31 using System.Collections; 32 using System.Collections.Specialized; 33 using System.Configuration.Internal; 34 using System.ComponentModel; 35 using System.Reflection; 36 using System.Xml; 37 using System.IO; 38 39 namespace System.Configuration { 40 41 // For configuration document, use this XmlDocument instead of the standard one. This ignores xmlns attribute for MS. 42 internal class ConfigurationXmlDocument : XmlDocument 43 { CreateElement(string prefix, string localName, string namespaceURI)44 public override XmlElement CreateElement (string prefix, string localName, string namespaceURI) 45 { 46 if (namespaceURI == "http://schemas.microsoft.com/.NetConfiguration/v2.0") 47 return base.CreateElement (String.Empty, localName, String.Empty); 48 else 49 return base.CreateElement (prefix, localName, namespaceURI); 50 } 51 } 52 53 public sealed class Configuration 54 { 55 Configuration parent; 56 Hashtable elementData = new Hashtable (); 57 string streamName; 58 ConfigurationSectionGroup rootSectionGroup; 59 ConfigurationLocationCollection locations; 60 SectionGroupInfo rootGroup; 61 IConfigSystem system; 62 bool hasFile; 63 string rootNamespace; 64 65 string configPath; 66 string locationConfigPath; 67 string locationSubPath; 68 69 internal static event ConfigurationSaveEventHandler SaveStart; 70 internal static event ConfigurationSaveEventHandler SaveEnd; 71 Configuration(Configuration parent, string locationSubPath)72 internal Configuration (Configuration parent, string locationSubPath) 73 { 74 this.parent = parent; 75 this.system = parent.system; 76 this.rootGroup = parent.rootGroup; 77 this.locationSubPath = locationSubPath; 78 this.configPath = parent.ConfigPath; 79 } 80 Configuration(InternalConfigurationSystem system, string locationSubPath)81 internal Configuration (InternalConfigurationSystem system, string locationSubPath) 82 { 83 hasFile = true; 84 this.system = system; 85 86 system.InitForConfiguration (ref locationSubPath, out configPath, out locationConfigPath); 87 88 Configuration parent = null; 89 90 if (locationSubPath != null) { 91 parent = new Configuration (system, locationSubPath); 92 if (locationConfigPath != null) 93 parent = parent.FindLocationConfiguration (locationConfigPath, parent); 94 } 95 96 Init (system, configPath, parent); 97 } 98 FindLocationConfiguration(string relativePath, Configuration defaultConfiguration)99 internal Configuration FindLocationConfiguration (string relativePath, Configuration defaultConfiguration) 100 { 101 Configuration parentConfig = defaultConfiguration; 102 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); 108 } 109 } 110 111 string relConfigPath = configPath.Substring (1) + "/"; 112 if (relativePath.StartsWith (relConfigPath, StringComparison.Ordinal)) 113 relativePath = relativePath.Substring (relConfigPath.Length); 114 115 ConfigurationLocation loc = Locations.FindBest (relativePath); 116 if (loc == null) 117 return parentConfig; 118 119 loc.SetParentConfiguration (parentConfig); 120 return loc.OpenConfiguration (); 121 } 122 Init(IConfigSystem system, string configPath, Configuration parent)123 internal void Init (IConfigSystem system, string configPath, Configuration parent) 124 { 125 this.system = system; 126 this.configPath = configPath; 127 streamName = system.Host.GetStreamName (configPath); 128 this.parent = parent; 129 if (parent != null) 130 rootGroup = parent.rootGroup; 131 else { 132 rootGroup = new SectionGroupInfo (); 133 rootGroup.StreamName = streamName; 134 } 135 136 try { 137 if (streamName != null) 138 Load (); 139 } catch (XmlException ex) { 140 throw new ConfigurationErrorsException (ex.Message, ex, streamName, 0); 141 } 142 } 143 144 internal Configuration Parent { 145 get { return parent; } 146 set { parent = value; } 147 } 148 GetParentWithFile()149 internal Configuration GetParentWithFile () 150 { 151 Configuration parentFile = Parent; 152 while (parentFile != null && !parentFile.HasFile) 153 parentFile = parentFile.Parent; 154 return parentFile; 155 } 156 157 internal string FileName { 158 get { return streamName; } 159 } 160 161 internal IInternalConfigHost ConfigHost { 162 get { return system.Host; } 163 } 164 165 internal string LocationConfigPath { 166 get { return locationConfigPath; } 167 } 168 GetLocationSubPath()169 internal string GetLocationSubPath () 170 { 171 Configuration confg = parent; 172 string path = null; 173 while (confg != null) { 174 path = confg.locationSubPath; 175 if (!String.IsNullOrEmpty (path)) 176 return path; 177 confg = confg.parent; 178 } 179 return path; 180 } 181 182 internal string ConfigPath { 183 get { return configPath; } 184 } 185 186 public AppSettingsSection AppSettings { 187 get { return (AppSettingsSection) GetSection ("appSettings"); } 188 } 189 190 public ConnectionStringsSection ConnectionStrings { 191 get { return (ConnectionStringsSection) GetSection ("connectionStrings"); } 192 } 193 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 196 // file is returned. 197 public string FilePath { 198 get { 199 if (streamName == null && parent != null) 200 return parent.FilePath; 201 return streamName; 202 } 203 } 204 205 public bool HasFile { 206 get { 207 return hasFile; 208 } 209 } 210 211 ContextInformation evaluationContext; 212 public ContextInformation EvaluationContext { 213 get { 214 if (evaluationContext == null) { 215 object ctx = system.Host.CreateConfigurationContext (configPath, GetLocationSubPath() ); 216 evaluationContext = new ContextInformation (this, ctx); 217 } 218 219 220 return evaluationContext; 221 } 222 } 223 224 public ConfigurationLocationCollection Locations { 225 get { 226 if (locations == null) locations = new ConfigurationLocationCollection (); 227 return locations; 228 } 229 } 230 231 public bool NamespaceDeclared { 232 get { return rootNamespace != null; } 233 set { rootNamespace = value ? "http://schemas.microsoft.com/.NetConfiguration/v2.0" : null; } 234 } 235 236 public ConfigurationSectionGroup RootSectionGroup { 237 get { 238 if (rootSectionGroup == null) { 239 rootSectionGroup = new ConfigurationSectionGroup (); 240 rootSectionGroup.Initialize (this, rootGroup); 241 } 242 return rootSectionGroup; 243 } 244 } 245 246 public ConfigurationSectionGroupCollection SectionGroups { 247 get { return RootSectionGroup.SectionGroups; } 248 } 249 250 public ConfigurationSectionCollection Sections { 251 get { return RootSectionGroup.Sections; } 252 } 253 GetSection(string sectionName)254 public ConfigurationSection GetSection (string sectionName) 255 { 256 string[] parts = sectionName.Split ('/'); 257 if (parts.Length == 1) 258 return Sections [parts[0]]; 259 260 ConfigurationSectionGroup group = SectionGroups [parts[0]]; 261 for (int n=1; group != null && n<parts.Length-1; n++) 262 group = group.SectionGroups [parts [n]]; 263 264 if (group != null) 265 return group.Sections [parts [parts.Length - 1]]; 266 else 267 return null; 268 } 269 GetSectionGroup(string sectionGroupName)270 public ConfigurationSectionGroup GetSectionGroup (string sectionGroupName) 271 { 272 string[] parts = sectionGroupName.Split ('/'); 273 ConfigurationSectionGroup group = SectionGroups [parts[0]]; 274 for (int n=1; group != null && n<parts.Length; n++) 275 group = group.SectionGroups [parts [n]]; 276 return group; 277 } 278 GetSectionInstance(SectionInfo config, bool createDefaultInstance)279 internal ConfigurationSection GetSectionInstance (SectionInfo config, bool createDefaultInstance) 280 { 281 object data = elementData [config]; 282 ConfigurationSection sec = data as ConfigurationSection; 283 if (sec != null || !createDefaultInstance) return sec; 284 285 object secObj = config.CreateInstance (); 286 sec = secObj as ConfigurationSection; 287 if (sec == null) { 288 DefaultSection ds = new DefaultSection (); 289 ds.SectionHandler = secObj as IConfigurationSectionHandler; 290 sec = ds; 291 } 292 sec.Configuration = this; 293 294 ConfigurationSection parentSection = null; 295 if (parent != null) { 296 parentSection = parent.GetSectionInstance (config, true); 297 sec.SectionInformation.SetParentSection (parentSection); 298 } 299 sec.SectionInformation.ConfigFilePath = FilePath; 300 301 sec.ConfigContext = system.Host.CreateDeprecatedConfigContext(configPath); 302 303 string xml = data as string; 304 sec.RawXml = xml; 305 sec.Reset (parentSection); 306 307 if (xml != null) { 308 XmlTextReader r = new ConfigXmlTextReader (new StringReader (xml), FilePath); 309 sec.DeserializeSection (r); 310 r.Close (); 311 312 if (!String.IsNullOrEmpty (sec.SectionInformation.ConfigSource) && !String.IsNullOrEmpty (FilePath)) 313 sec.DeserializeConfigSource (Path.GetDirectoryName (FilePath)); 314 } 315 316 elementData [config] = sec; 317 return sec; 318 } 319 GetSectionGroupInstance(SectionGroupInfo group)320 internal ConfigurationSectionGroup GetSectionGroupInstance (SectionGroupInfo group) 321 { 322 ConfigurationSectionGroup gr = group.CreateInstance () as ConfigurationSectionGroup; 323 if (gr != null) gr.Initialize (this, group); 324 return gr; 325 } 326 SetConfigurationSection(SectionInfo config, ConfigurationSection sec)327 internal void SetConfigurationSection (SectionInfo config, ConfigurationSection sec) 328 { 329 elementData [config] = sec; 330 } 331 SetSectionXml(SectionInfo config, string data)332 internal void SetSectionXml (SectionInfo config, string data) 333 { 334 elementData [config] = data; 335 } 336 GetSectionXml(SectionInfo config)337 internal string GetSectionXml (SectionInfo config) 338 { 339 return elementData [config] as string; 340 } 341 CreateSection(SectionGroupInfo group, string name, ConfigurationSection sec)342 internal void CreateSection (SectionGroupInfo group, string name, ConfigurationSection sec) 343 { 344 if (group.HasChild (name)) 345 throw new ConfigurationErrorsException ("Cannot add a ConfigurationSection. A section or section group already exists with the name '" + name + "'"); 346 347 if (!HasFile && !sec.SectionInformation.AllowLocation) 348 throw new ConfigurationErrorsException ("The configuration section <" + name + "> cannot be defined inside a <location> element."); 349 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 + "')."); 353 } 354 355 if (sec.SectionInformation.Type == null) 356 sec.SectionInformation.Type = system.Host.GetConfigTypeName (sec.GetType ()); 357 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; 364 } 365 CreateSectionGroup(SectionGroupInfo parentGroup, string name, ConfigurationSectionGroup sec)366 internal void CreateSectionGroup (SectionGroupInfo parentGroup, string name, ConfigurationSectionGroup sec) 367 { 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 ()); 370 sec.SetName (name); 371 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; 377 378 sec.Initialize (this, section); 379 } 380 RemoveConfigInfo(ConfigInfo config)381 internal void RemoveConfigInfo (ConfigInfo config) 382 { 383 elementData.Remove (config); 384 } 385 Save()386 public void Save () 387 { 388 Save (ConfigurationSaveMode.Modified, false); 389 } 390 Save(ConfigurationSaveMode saveMode)391 public void Save (ConfigurationSaveMode saveMode) 392 { 393 Save (saveMode, false); 394 } 395 Save(ConfigurationSaveMode saveMode, bool forceSaveAll)396 public void Save (ConfigurationSaveMode saveMode, bool forceSaveAll) 397 { 398 if (!forceSaveAll && (saveMode != ConfigurationSaveMode.Full) && !HasValues (saveMode)) { 399 ResetModified (); 400 return; 401 } 402 403 ConfigurationSaveEventHandler saveStart = SaveStart; 404 ConfigurationSaveEventHandler saveEnd = SaveEnd; 405 406 object ctx = null; 407 Exception saveEx = null; 408 Stream stream = system.Host.OpenStreamForWrite (streamName, null, ref ctx); 409 try { 410 if (saveStart != null) 411 saveStart (this, new ConfigurationSaveEventArgs (streamName, true, null, ctx)); 412 413 Save (stream, saveMode, forceSaveAll); 414 system.Host.WriteCompleted (streamName, true, ctx); 415 } catch (Exception ex) { 416 saveEx = ex; 417 system.Host.WriteCompleted (streamName, false, ctx); 418 throw; 419 } finally { 420 stream.Close (); 421 if (saveEnd != null) 422 saveEnd (this, new ConfigurationSaveEventArgs (streamName, false, saveEx, ctx)); 423 } 424 } 425 SaveAs(string filename)426 public void SaveAs (string filename) 427 { 428 SaveAs (filename, ConfigurationSaveMode.Modified, false); 429 } 430 SaveAs(string filename, ConfigurationSaveMode saveMode)431 public void SaveAs (string filename, ConfigurationSaveMode saveMode) 432 { 433 SaveAs (filename, saveMode, false); 434 } 435 436 [MonoInternalNote ("Detect if file has changed")] SaveAs(string filename, ConfigurationSaveMode saveMode, bool forceSaveAll)437 public void SaveAs (string filename, ConfigurationSaveMode saveMode, bool forceSaveAll) 438 { 439 if (!forceSaveAll && (saveMode != ConfigurationSaveMode.Full) && !HasValues (saveMode)) { 440 ResetModified (); 441 return; 442 } 443 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), saveMode, forceSaveAll); 448 } 449 Save(Stream stream, ConfigurationSaveMode mode, bool forceUpdateAll)450 void Save (Stream stream, ConfigurationSaveMode mode, bool forceUpdateAll) 451 { 452 XmlTextWriter tw = new XmlTextWriter (new StreamWriter (stream)); 453 tw.Formatting = Formatting.Indented; 454 try { 455 tw.WriteStartDocument (); 456 if (rootNamespace != null) 457 tw.WriteStartElement ("configuration", rootNamespace); 458 else 459 tw.WriteStartElement ("configuration"); 460 if (rootGroup.HasConfigContent (this)) { 461 rootGroup.WriteConfig (this, tw, mode); 462 } 463 464 foreach (ConfigurationLocation loc in Locations) { 465 if (loc.OpenedConfiguration == null) { 466 tw.WriteRaw ("\n"); 467 tw.WriteRaw (loc.XmlContent); 468 } 469 else { 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 (); 476 } 477 } 478 479 SaveData (tw, mode, forceUpdateAll); 480 tw.WriteEndElement (); 481 ResetModified (); 482 } 483 finally { 484 tw.Flush (); 485 tw.Close (); 486 } 487 } 488 SaveData(XmlTextWriter tw, ConfigurationSaveMode mode, bool forceUpdateAll)489 void SaveData (XmlTextWriter tw, ConfigurationSaveMode mode, bool forceUpdateAll) 490 { 491 rootGroup.WriteRootData (tw, this, mode); 492 } 493 HasValues(ConfigurationSaveMode mode)494 bool HasValues (ConfigurationSaveMode mode) 495 { 496 foreach (ConfigurationLocation loc in Locations) { 497 if (loc.OpenedConfiguration == null) 498 continue; 499 if (loc.OpenedConfiguration.HasValues (mode)) 500 return true; 501 } 502 503 return rootGroup.HasValues (this, mode); 504 } 505 ResetModified()506 void ResetModified () 507 { 508 foreach (ConfigurationLocation loc in Locations) { 509 if (loc.OpenedConfiguration == null) 510 continue; 511 loc.OpenedConfiguration.ResetModified (); 512 } 513 514 rootGroup.ResetModified (this); 515 } 516 Load()517 bool Load () 518 { 519 if (String.IsNullOrEmpty (streamName)) 520 return true; 521 522 Stream stream = null; 523 try { 524 stream = system.Host.OpenStreamForRead (streamName); 525 if (stream == null) 526 return false; 527 } catch { 528 return false; 529 } 530 531 using (XmlTextReader reader = new ConfigXmlTextReader (stream, streamName)) { 532 ReadConfigFile (reader, streamName); 533 } 534 ResetModified (); 535 return true; 536 } 537 ReadConfigFile(XmlReader reader, string fileName)538 void ReadConfigFile (XmlReader reader, string fileName) 539 { 540 reader.MoveToContent (); 541 542 if (reader.NodeType != XmlNodeType.Element || reader.Name != "configuration") 543 ThrowException ("Configuration file does not have a valid root element", reader); 544 545 if (reader.HasAttributes) { 546 while (reader.MoveToNextAttribute ()) { 547 if (reader.LocalName == "xmlns") { 548 rootNamespace = reader.Value; 549 continue; 550 } 551 ThrowException (String.Format ("Unrecognized attribute '{0}' in root element", reader.LocalName), reader); 552 } 553 } 554 555 reader.MoveToElement (); 556 557 if (reader.IsEmptyElement) { 558 reader.Skip (); 559 return; 560 } 561 562 reader.ReadStartElement (); 563 reader.MoveToContent (); 564 565 if (reader.LocalName == "configSections") { 566 if (reader.HasAttributes) 567 ThrowException ("Unrecognized attribute in <configSections>.", reader); 568 569 rootGroup.ReadConfig (this, fileName, reader); 570 } 571 572 rootGroup.ReadRootData (reader, this, true); 573 } 574 ReadData(XmlReader reader, bool allowOverride)575 internal void ReadData (XmlReader reader, bool allowOverride) 576 { 577 rootGroup.ReadData (this, reader, allowOverride); 578 } 579 580 ThrowException(string text, XmlReader reader)581 private void ThrowException (string text, XmlReader reader) 582 { 583 IXmlLineInfo li = reader as IXmlLineInfo; 584 throw new ConfigurationErrorsException (text, streamName, li != null ? li.LineNumber : 0); 585 } 586 } 587 } 588 589