History

Added support for GSF configuration files on Mono

ritchiecarroll committed 12 years ago

Commit 2bbd6cd1 parent ef6432e13 files · +615 −136Browse files

Unified · Split

13 files
  1. Source/GridSolutionsFramework.sln +17 −1
  2. Source/Libraries/GSF.Core/Common.cs +14 −0
  3. Source/Libraries/GSF.Core/Configuration/CategorizedSettingsElement.cs +17 −0
  4. Source/Libraries/GSF.Core/Configuration/CategorizedSettingsElementCollection.cs +18 −6
  5. Source/Libraries/GSF.Core/Configuration/CategorizedSettingsSection.cs +174 −14
  6. Source/Libraries/GSF.Core/Configuration/ConfigurationFile.cs +49 −33
  7. Source/Libraries/GSF.Core/Identity/UnixUserInfo.cs +113 −57
  8. Source/Libraries/GSF.Core/Xml/XmlExtensions.cs +35 −25
  9. Source/Tools/ConfigTest/App.config +14 −0
  10. Source/Tools/ConfigTest/ConfigTest.csproj +71 −0
  11. Source/Tools/ConfigTest/ConfigTest.csproj.vspscc +10 −0
  12. Source/Tools/ConfigTest/Program.cs +47 −0
  13. Source/Tools/ConfigTest/Properties/AssemblyInfo.cs +36 −0
Source/GridSolutionsFramework.sln+17−1
@@ -297,9 +297,11 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnixTools", "Tools\UnixTool · 296 unmodified lines
297297 EndProject
298298 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UserInfoTest", "Tools\UserInfoTest\UserInfoTest.csproj", "{CD74CE4C-6319-4946-889A-413DC51B03F6}"
299299 EndProject
300+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConfigTest", "Tools\ConfigTest\ConfigTest.csproj", "{B38AC5E5-B880-43E9-8F88-107294F3E69F}"
301+EndProject
300302 Global
301303 GlobalSection(TeamFoundationVersionControl) = preSolution
302− SccNumberOfProjects = 86
304+ SccNumberOfProjects = 87
303305 SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
304306 SccTeamFoundationServer = https://tfs.codeplex.com/tfs/tfs07
305307 SccLocalPath0 = .
@@ -643,6 +645,10 @@ Global · 337 unmodified lines
643645 SccProjectTopLevelParentUniqueName85 = GridSolutionsFramework.sln
644646 SccProjectName85 = Tools/UserInfoTest
645647 SccLocalPath85 = Tools\\UserInfoTest
648+ SccProjectUniqueName86 = Tools\\ConfigTest\\ConfigTest.csproj
649+ SccProjectTopLevelParentUniqueName86 = GridSolutionsFramework.sln
650+ SccProjectName86 = Tools/ConfigTest
651+ SccLocalPath86 = Tools\\ConfigTest
646652 EndGlobalSection
647653 GlobalSection(TestCaseManagementSettings) = postSolution
648654 CategoryFile = GSF.Core.Tests.vsmdi
@@ -1542,6 +1548,15 @@ Global · 893 unmodified lines
15421548 {CD74CE4C-6319-4946-889A-413DC51B03F6}.Release|Any CPU.ActiveCfg = Release|Any CPU
15431549 {CD74CE4C-6319-4946-889A-413DC51B03F6}.Release|Any CPU.Build.0 = Release|Any CPU
15441550 {CD74CE4C-6319-4946-889A-413DC51B03F6}.Release|x86.ActiveCfg = Release|Any CPU
1551+ {B38AC5E5-B880-43E9-8F88-107294F3E69F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
1552+ {B38AC5E5-B880-43E9-8F88-107294F3E69F}.Debug|Any CPU.Build.0 = Debug|Any CPU
1553+ {B38AC5E5-B880-43E9-8F88-107294F3E69F}.Debug|x86.ActiveCfg = Debug|Any CPU
1554+ {B38AC5E5-B880-43E9-8F88-107294F3E69F}.Mono|Any CPU.ActiveCfg = Release|Any CPU
1555+ {B38AC5E5-B880-43E9-8F88-107294F3E69F}.Mono|Any CPU.Build.0 = Release|Any CPU
1556+ {B38AC5E5-B880-43E9-8F88-107294F3E69F}.Mono|x86.ActiveCfg = Release|Any CPU
1557+ {B38AC5E5-B880-43E9-8F88-107294F3E69F}.Release|Any CPU.ActiveCfg = Release|Any CPU
1558+ {B38AC5E5-B880-43E9-8F88-107294F3E69F}.Release|Any CPU.Build.0 = Release|Any CPU
1559+ {B38AC5E5-B880-43E9-8F88-107294F3E69F}.Release|x86.ActiveCfg = Release|Any CPU
15451560 EndGlobalSection
15461561 GlobalSection(SolutionProperties) = preSolution
15471562 HideSolutionNode = FALSE
@@ -1638,6 +1653,7 @@ Global · 90 unmodified lines
16381653 {4508009C-7011-4981-B35F-594F42CC0D1F} = {B48642A1-1163-4E1D-948A-50EE19DCD38D}
16391654 {5A047659-4C24-4B72-A9F3-6553D91F95DC} = {B48642A1-1163-4E1D-948A-50EE19DCD38D}
16401655 {CD74CE4C-6319-4946-889A-413DC51B03F6} = {B48642A1-1163-4E1D-948A-50EE19DCD38D}
1656+ {B38AC5E5-B880-43E9-8F88-107294F3E69F} = {B48642A1-1163-4E1D-948A-50EE19DCD38D}
16411657 {AEB62271-270D-4F3E-B596-2297B382A95F} = {99F344EA-D35D-42C8-882B-B495527B5F40}
16421658 {4F46CF24-163D-4541-9EBF-C291053C0948} = {99F344EA-D35D-42C8-882B-B495527B5F40}
16431659 {94F36768-E9A0-4783-BB0F-8C2C1771352E} = {AEB62271-270D-4F3E-B596-2297B382A95F}
Source/Libraries/GSF.Core/Common.cs+14−0
@@ -135,12 +135,26 @@ namespace GSF · 134 unmodified lines
135135 /// Determines if the current system is a POSIX style environment.
136136 /// </summary>
137137 /// <remarks>
138+ /// <para>
139+ /// Since a .NET application compiled under Mono can run under both Windows and Unix style platforms,
140+ /// you can use this property to easily determine the current operating environment.
141+ /// </para>
142+ /// <para>
138143 /// This property will return <c>true</c> for both MacOSX and Unix environments. Use the Platform property
139144 /// of the <see cref="System.Environment.OSVersion"/> to determine more specific platform type, e.g.,
140145 /// MacOSX or Unix. Note that all flavors of Linux will show up as <see cref="PlatformID.Unix"/>.
146+ /// </para>
141147 /// </remarks>
142148 public static readonly bool IsPosixEnvironment = (Path.DirectorySeparatorChar == '/'); // This is how Mono source often checks this
143149
150+ /// <summary>
151+ /// Determines if the code base is currently running under Mono.
152+ /// </summary>
153+ /// <remarks>
154+ /// You can use this property to make a run-time determination if Windows or Mono based .NET is being used.
155+ /// </remarks>
156+ public static bool IsMono = ((object)Type.GetType("Mono.Runtime") != null);
157+
144158 /// <summary>Returns one of two strongly-typed objects.</summary>
145159 /// <returns>One of two objects, depending on the evaluation of given expression.</returns>
146160 /// <param name="expression">The expression you want to evaluate.</param>
Source/Libraries/GSF.Core/Configuration/CategorizedSettingsElement.cs+17−0
@@ -170,6 +170,7 @@ namespace GSF.Configuration · 169 unmodified lines
170170 set
171171 {
172172 base["name"] = value;
173+ Modified = true;
173174 }
174175 }
175176
@@ -229,6 +230,8 @@ namespace GSF.Configuration · 53 unmodified lines
229230 else
230231 // Setting is user specific so update setting in user settings store.
231232 Category.Section.File.UserSettings.Write(Category.Name, Name, value);
233+
234+ Modified = true;
232235 }
233236 }
234237
@@ -250,6 +253,7 @@ namespace GSF.Configuration · 15 unmodified lines
250253 return;
251254
252255 base["description"] = value;
256+ Modified = true;
253257 }
254258 }
255259
@@ -271,6 +275,7 @@ namespace GSF.Configuration · 15 unmodified lines
271275 return;
272276
273277 base["encrypted"] = value;
278+ Modified = true;
274279 }
275280 }
276281
@@ -291,6 +296,16 @@ namespace GSF.Configuration · 14 unmodified lines
291296 return;
292297
293298 base["scope"] = value;
299+ Modified = true;
300+ }
301+ }
302+
303+ internal bool Modified
304+ {
305+ set
306+ {
307+ if ((object)m_category != null)
308+ m_category.Modified = value;
294309 }
295310 }
296311
@@ -311,6 +326,7 @@ namespace GSF.Configuration · 14 unmodified lines
311326 string decryptedValue = Value;
312327 m_cryptoKey = cryptoKey;
313328 Value = decryptedValue;
329+ Modified = true;
314330 }
315331 }
316332
@@ -357,6 +373,7 @@ namespace GSF.Configuration · 40 unmodified lines
357373 this.Encrypted = encrypted;
358374 this.Value = Common.TypeConvertToString(value, m_category.Section.File.Culture);
359375 this.Description = description;
376+ Modified = true;
360377 }
361378
362379 /// <summary>
Source/Libraries/GSF.Core/Configuration/CategorizedSettingsElementCollection.cs+18−6
@@ -111,6 +111,7 @@ namespace GSF.Configuration · 110 unmodified lines
111111 base.BaseRemoveAt(index);
112112
113113 base.BaseAdd(index, value);
114+ Modified = true;
114115 }
115116 }
116117
@@ -137,23 +138,29 @@ namespace GSF.Configuration · 20 unmodified lines
137138 {
138139 get
139140 {
141+ // Add setting since it's not there
140142 if (ensureExistance && (object)base.BaseGet(name) == null)
141− {
142− // Add setting since it's not there.
143143 Add(name, string.Empty);
144− }
145144
146145 CategorizedSettingsElement setting = (CategorizedSettingsElement)base.BaseGet(name);
146+
147+ // Set the crypto key for the setting
147148 if ((object)setting != null)
148− {
149− // Set the crypto key for the setting.
150149 setting.SetCryptoKey(m_cryptoKey);
151− }
152150
153151 return setting;
154152 }
155153 }
156154
155+ internal bool Modified
156+ {
157+ set
158+ {
159+ if ((object)m_section != null)
160+ m_section.Modified = value;
161+ }
162+ }
163+
157164 #endregion
158165
159166 #region [ Methods ]
@@ -242,6 +249,7 @@ namespace GSF.Configuration · 82 unmodified lines
242249 setting.Category = this;
243250 setting.SetCryptoKey(m_cryptoKey);
244251 base.BaseAdd(setting);
252+ Modified = true;
245253 }
246254 }
247255
@@ -252,6 +260,7 @@ namespace GSF.Configuration · 4 unmodified lines
252260 public void Remove(string name)
253261 {
254262 base.BaseRemove(name);
263+ Modified = true;
255264 }
256265
257266 /// <summary>
@@ -263,6 +272,7 @@ namespace GSF.Configuration · 5 unmodified lines
263272 if (base.BaseIndexOf(setting) >= 0)
264273 {
265274 Remove(setting.Name);
275+ Modified = true;
266276 }
267277 }
268278
@@ -273,6 +283,7 @@ namespace GSF.Configuration · 4 unmodified lines
273283 public void RemoveAt(int index)
274284 {
275285 base.BaseRemoveAt(index);
286+ Modified = true;
276287 }
277288
278289 /// <summary>
@@ -281,6 +292,7 @@ namespace GSF.Configuration · 2 unmodified lines
281292 public void Clear()
282293 {
283294 base.BaseClear();
295+ Modified = true;
284296 }
285297
286298 /// <summary>
Source/Libraries/GSF.Core/Configuration/CategorizedSettingsSection.cs+174−14
@@ -38,10 +38,14 @@ · 37 unmodified lines
3838 //******************************************************************************************************
3939
4040 using System;
41+using System.Collections.Generic;
4142 using System.Configuration;
4243 using System.IO;
44+using System.Text;
4345 using System.Xml;
46+using GSF.Collections;
4447 using GSF.IO;
48+using GSF.Xml;
4549
4650 namespace GSF.Configuration
4751 {
@@ -59,9 +63,19 @@ namespace GSF.Configuration · 11 unmodified lines
5963 // Fields
6064 private string m_cryptoKey;
6165 private ConfigurationFile m_file;
66+ private readonly Dictionary<string, CategorizedSettingsElementCollection> m_sections;
6267
6368 #endregion
6469
70+ /// <summary>
71+ /// Creates a new <see cref="CategorizedSettingsSection"/>.
72+ /// </summary>
73+ public CategorizedSettingsSection()
74+ {
75+ if (Common.IsMono)
76+ m_sections = new Dictionary<string, CategorizedSettingsElementCollection>();
77+ }
78+
6579 #region [ Properties ]
6680
6781 /// <summary>
@@ -99,18 +113,42 @@ namespace GSF.Configuration · 31 unmodified lines
99113 // Make the first letter of category name lower case, if not already.
100114 char[] nameChars = name.ToCharArray();
101115 nameChars[0] = char.ToLower(nameChars[0]);
116+
102117 // Do not allow spaces in the name so that underlying .Net configuration API does not break.
103118 name = (new string(nameChars)).RemoveWhiteSpace();
104− ConfigurationProperty configProperty = new ConfigurationProperty(name, typeof(CategorizedSettingsElementCollection));
105119
106− base.Properties.Add(configProperty);
120+ ConfigurationProperty configProperty = new ConfigurationProperty(name, typeof(CategorizedSettingsElementCollection));
107121 CategorizedSettingsElementCollection settingsCategory = null;
108− if ((object)base[configProperty] != null)
122+
123+ if ((object)m_sections == null)
109124 {
110− settingsCategory = (CategorizedSettingsElementCollection)base[configProperty];
111− settingsCategory.Name = name;
112− settingsCategory.Section = this;
113− settingsCategory.SetCryptoKey(m_cryptoKey);
125+ base.Properties.Add(configProperty);
126+
127+ if ((object)base[configProperty] != null)
128+ {
129+ settingsCategory = (CategorizedSettingsElementCollection)base[configProperty];
130+ settingsCategory.Name = name;
131+ settingsCategory.Section = this;
132+ settingsCategory.SetCryptoKey(m_cryptoKey);
133+ }
134+ }
135+ else
136+ {
137+ settingsCategory = m_sections.GetOrAdd(name, sectionName =>
138+ {
139+ base.Properties.Add(configProperty);
140+
141+ CategorizedSettingsElementCollection settings = new CategorizedSettingsElementCollection
142+ {
143+ Name = sectionName,
144+ Section = this,
145+ };
146+
147+ settings.SetCryptoKey(m_cryptoKey);
148+ Modified = true;
149+
150+ return settings;
151+ });
114152 }
115153
116154 return settingsCategory;
@@ -129,6 +167,15 @@ namespace GSF.Configuration · 12 unmodified lines
129167 }
130168 }
131169
170+ internal bool Modified
171+ {
172+ set
173+ {
174+ if ((object)m_sections != null && (object)m_file != null && value)
175+ m_file.m_forceSave = true;
176+ }
177+ }
178+
132179 #endregion
133180
134181 #region [ Methods ]
@@ -159,7 +206,10 @@ namespace GSF.Configuration · 24 unmodified lines
159206 }
160207
161208 // Remove category from property bag.
162− base.Properties.Remove(settingsCategory.Name);
209+ if ((object)m_sections == null)
210+ base.Properties.Remove(settingsCategory.Name);
211+ else
212+ m_sections.Remove(settingsCategory.Name);
163213 }
164214
165215 /// <summary>
@@ -181,19 +231,129 @@ namespace GSF.Configuration · 15 unmodified lines
181231 // when the category elements are being deserialized.
182232 if ((object)configSection.DocumentElement != null)
183233 {
184− XmlNodeList nodeList = configSection.DocumentElement.SelectNodes("*");
234+ XmlNodeList categories = configSection.DocumentElement.SelectNodes("*");
185235
186− if ((object)nodeList != null)
236+ if ((object)categories != null)
187237 {
188− foreach (XmlNode category in nodeList)
189− base.Properties.Add(new ConfigurationProperty(category.Name, typeof(CategorizedSettingsElementCollection)));
238+ foreach (XmlNode category in categories)
239+ {
240+ ConfigurationProperty configProperty = new ConfigurationProperty(category.Name, typeof(CategorizedSettingsElementCollection));
241+
242+ base.Properties.Add(configProperty);
243+
244+ if ((object)m_sections != null)
245+ {
246+ CategorizedSettingsElementCollection settingsCategory = new CategorizedSettingsElementCollection
247+ {
248+ Name = category.Name,
249+ Section = this,
250+ };
251+
252+ settingsCategory.SetCryptoKey(m_cryptoKey);
253+ m_sections.Add(category.Name, settingsCategory);
254+
255+ // Read all elements within this category section
256+ XmlNodeList elements = category.SelectNodes("*");
257+ SettingScope scope;
258+
259+ if ((object)elements != null)
260+ {
261+ foreach (XmlNode element in elements)
262+ {
263+ CategorizedSettingsElement categorySetting = new CategorizedSettingsElement(settingsCategory);
264+
265+ categorySetting.Name = element.GetAttributeValue("name");
266+ categorySetting.Value = element.GetAttributeValue("value");
267+ categorySetting.Description = element.GetAttributeValue("description") ?? "";
268+ categorySetting.Encrypted = element.GetAttributeValue("encrypted").ToNonNullNorWhiteSpace("false").ParseBoolean();
269+
270+ if (Enum.TryParse(element.GetAttributeValue("scope").ToNonNullNorWhiteSpace("Application"), out scope))
271+ categorySetting.Scope = scope;
272+ else
273+ categorySetting.Scope = SettingScope.Application;
274+
275+ settingsCategory.Add(categorySetting);
276+ }
277+ }
278+ }
279+ }
190280 }
191281 }
192282
193− configSectionStream.Seek(0, SeekOrigin.Begin);
283+ if ((object)m_sections == null)
284+ {
285+ configSectionStream.Seek(0, SeekOrigin.Begin);
286+ base.DeserializeSection(XmlReader.Create(configSectionStream));
287+ }
288+ }
289+ }
290+
291+ /// <summary>
292+ /// Creates an XML string containing an unmerged view of the <see cref="ConfigurationSection"/> object as a single section to write to a file.
293+ /// </summary>
294+ /// <returns>
295+ /// An XML string containing an unmerged view of the <see cref="ConfigurationSection"/> object.
296+ /// </returns>
297+ /// <param name="parentElement">The <see cref="ConfigurationElement"/> instance to use as the parent when performing the un-merge.</param>
298+ /// <param name="name">The name of the section to create.</param>
299+ /// <param name="saveMode">The <see cref="ConfigurationSaveMode"/> instance to use when writing to a string.</param>
300+ protected override string SerializeSection(ConfigurationElement parentElement, string name, ConfigurationSaveMode saveMode)
301+ {
302+ if ((object)m_sections != null)
303+ {
304+ const string tempRoot = "__tempRoot__";
305+
306+ using (BlockAllocatedMemoryStream stream = new BlockAllocatedMemoryStream())
307+ {
308+ XmlTextWriter writer = new XmlTextWriter(stream, Encoding.UTF8);
309+
310+ writer.Indentation = 2;
311+ writer.Formatting = Formatting.Indented;
312+
313+ // Add a temporary root so that indentation is at the desired level
314+ writer.WriteStartElement(tempRoot);
315+ writer.WriteStartElement(name);
194316
195− base.DeserializeSection(XmlReader.Create(configSectionStream));
317+ foreach (string section in m_sections.Keys)
318+ {
319+ CategorizedSettingsElementCollection categorySettings = this[section];
320+
321+ // Add category section
322+ writer.WriteStartElement(section);
323+
324+ // Write each category value
325+ foreach (CategorizedSettingsElement categorySetting in categorySettings)
326+ {
327+ // <add name="TestConfigParam" value="-1.0" description="Parameter description." encrypted="false" scope="User"/>
328+ writer.WriteStartElement("add");
329+
330+ writer.WriteAttributeString("name", categorySetting.Name);
331+ writer.WriteAttributeString("value", categorySetting.Value);
332+ writer.WriteAttributeString("description", categorySetting.Description ?? "");
333+ writer.WriteAttributeString("encrypted", categorySetting.Encrypted.ToString());
334+
335+ if (categorySetting.Scope == SettingScope.User)
336+ writer.WriteAttributeString("scope", categorySetting.Scope.ToString());
337+
338+ writer.WriteEndElement();
339+ }
340+
341+ writer.WriteEndElement();
342+ }
343+
344+ writer.WriteEndElement();
345+ writer.WriteEndElement();
346+
347+ writer.Flush();
348+
349+ string settings = Encoding.UTF8.GetString(stream.ToArray());
350+
351+ // Remove temporary root
352+ return settings.Replace(string.Format("<{0}>", tempRoot), "").Replace(string.Format("</{0}>", tempRoot), "");
353+ }
196354 }
355+
356+ return base.SerializeSection(parentElement, name, ConfigurationSaveMode.Full);
197357 }
198358
199359 #endregion
Source/Libraries/GSF.Core/Configuration/ConfigurationFile.cs+49−33
@@ -64,7 +64,6 @@ using System.Configuration; · 63 unmodified lines
6464 using System.Globalization;
6565 using System.IO;
6666 using System.Text;
67−using System.Threading;
6867 using System.Web.Configuration;
6968 using System.Web.Hosting;
7069 using System.Xml;
@@ -177,7 +176,6 @@ namespace GSF.Configuration · 106 unmodified lines
177176 #region [ Members ]
178177
179178 // Nested Types
180−
181179 internal class UserConfigurationFile
182180 {
183181 private readonly string m_fileName;
@@ -229,6 +227,7 @@ namespace GSF.Configuration · 45 unmodified lines
229227 public void Write(string category, string name, string value)
230228 {
231229 XmlNode node = GetSetting(category, name);
230+
232231 if ((object)node != null)
233232 {
234233 // Setting exists so update it.
@@ -242,18 +241,20 @@ namespace GSF.Configuration · 7 unmodified lines
242241 setting.SetAttributeValue("value", value);
243242 m_settings.GetXmlNode(category).AppendChild(setting);
244243 }
244+
245245 m_settingsModified = true;
246246 }
247247
248248 public string Read(string category, string name, string defaultValue)
249249 {
250250 XmlNode node = GetSetting(category, name);
251− if ((object)node != null)
252− // Setting exists so return its value.
251+
252+ // If setting exists, return its value
253+ if ((object)node != null && (object)node.Attributes != null)
253254 return node.Attributes["value"].Value;
254− else
255− // Setting doesn't exist so return the default.
256− return defaultValue;
255+
256+ // Setting doesn't exist so return the default
257+ return defaultValue;
257258 }
258259
259260 public XmlNode GetSetting(string category, string name)
@@ -272,7 +273,8 @@ namespace GSF.Configuration · 12 unmodified lines
272273 private System.Configuration.Configuration m_configuration;
273274 private UserConfigurationFile m_userConfiguration;
274275 private volatile ConfigurationSaveMode m_saveMode;
275− private LongSynchronizedOperation m_saveOperation;
276+ private readonly LongSynchronizedOperation m_saveOperation;
277+ internal bool m_forceSave;
276278
277279 #endregion
278280
@@ -288,7 +290,7 @@ namespace GSF.Configuration · 9 unmodified lines
288290
289291 m_configuration = GetConfiguration(configFilePath);
290292
291− if (m_configuration.HasFile)
293+ if (m_configuration.HasFile && File.Exists(m_configuration.FilePath))
292294 ValidateConfigurationFile(m_configuration.FilePath);
293295 else
294296 CreateConfigurationFile(m_configuration.FilePath);
@@ -364,7 +366,7 @@ namespace GSF.Configuration · 69 unmodified lines
364366 /// </summary>
365367 public void Save()
366368 {
367− Save(ConfigurationSaveMode.Modified);
369+ Save(m_forceSave ? ConfigurationSaveMode.Full : ConfigurationSaveMode.Modified);
368370 }
369371
370372 /// <summary>
@@ -373,8 +375,7 @@ namespace GSF.Configuration · 2 unmodified lines
373375 /// <param name="saveMode">One of the <see cref="ConfigurationSaveMode"/> values.</param>
374376 public void Save(ConfigurationSaveMode saveMode)
375377 {
376− if (m_saveMode == ConfigurationSaveMode.Minimal || saveMode == ConfigurationSaveMode.Full)
377− m_saveMode = saveMode;
378+ m_saveMode = saveMode;
378379
379380 // Since the static open method always returns the same instance for the same configuration files,
380381 // synchronizing the instance will sync all saves.
@@ -385,9 +386,10 @@ namespace GSF.Configuration · 4 unmodified lines
385386 {
386387 // Perform configuration save
387388 ConfigurationSaveMode saveMode = m_saveMode;
388− m_configuration.Save(saveMode);
389+ m_configuration.Save(saveMode, m_forceSave);
389390 m_userConfiguration.Save(saveMode == ConfigurationSaveMode.Full);
390391 m_saveMode = ConfigurationSaveMode.Minimal;
392+ m_forceSave = false;
391393 }
392394
393395 /// <summary>
@@ -436,7 +438,7 @@ namespace GSF.Configuration · 42 unmodified lines
436438 private System.Configuration.Configuration GetConfiguration(string configFilePath)
437439 {
438440 ApplicationType appType = Common.GetApplicationType();
439− System.Configuration.Configuration configuration = null;
441+ System.Configuration.Configuration configuration;
440442
441443 if ((object)configFilePath != null)
442444 {
@@ -475,13 +477,15 @@ namespace GSF.Configuration · 32 unmodified lines
475477 if (!string.IsNullOrEmpty(configFilePath))
476478 {
477479 XmlTextWriter configFileWriter = new XmlTextWriter(configFilePath, Encoding.UTF8);
478− configFileWriter.Indentation = 4;
480+ configFileWriter.Indentation = 2;
479481 configFileWriter.Formatting = Formatting.Indented;
482+
480483 // Populates the very basic information required in a config file.
481484 configFileWriter.WriteStartDocument();
482485 configFileWriter.WriteStartElement("configuration");
483486 configFileWriter.WriteEndElement();
484487 configFileWriter.WriteEndDocument();
488+
485489 // Closes the config file.
486490 configFileWriter.Close();
487491
@@ -505,32 +509,44 @@ namespace GSF.Configuration · 17 unmodified lines
505509 // Make sure that the config file has the necessary section information under <customSections />
506510 // that is required by the .Net configuration API to process our custom <categorizedSettings />
507511 // section. The configuration API will raise an exception if it doesn't find this section.
508− if (configFile.DocumentElement.SelectNodes("configSections").Count == 0)
512+ if ((object)configFile.DocumentElement != null)
509513 {
510− // Adds a <configSections> node, if one is not present.
511− configFile.DocumentElement.InsertBefore(configFile.CreateElement("configSections"), configFile.DocumentElement.FirstChild);
514+ XmlNodeList configSections = configFile.DocumentElement.SelectNodes("configSections");
512515
513− configFileModified = true;
514− }
515− XmlNode configSectionsNode = configFile.DocumentElement.SelectSingleNode("configSections");
516− if (configSectionsNode.SelectNodes("section[@name = \'" + CustomSectionName + "\']").Count == 0)
517− {
518− // Adds the <section> node that specifies the DLL that handles the <categorizedSettings> node in
519− // the config file, if one is not present.
520− XmlNode node = configFile.CreateElement("section");
521− node.SetAttributeValue("name", CustomSectionName);
522− node.SetAttributeValue("type", CustomSectionType);
523− configSectionsNode.AppendChild(node);
524−
525− configFileModified = true;
516+ if ((object)configSections != null && configSections.Count == 0)
517+ {
518+ // Add a <configSections> node, if one is not present.
519+ if ((object)configFile.DocumentElement != null)
520+ {
521+ // ReSharper disable once PossibleNullReferenceException
522+ configFile.DocumentElement.InsertBefore(configFile.CreateElement("configSections"), configFile.DocumentElement.FirstChild);
523+ configFileModified = true;
524+ }
525+ }
526+
527+ XmlNode configSectionsNode = configFile.DocumentElement.SelectSingleNode("configSections");
528+
529+ if ((object)configSectionsNode != null)
530+ {
531+ XmlNodeList section = configSectionsNode.SelectNodes("section[@name = \'" + CustomSectionName + "\']");
532+
533+ if ((object)section != null && section.Count == 0)
534+ {
535+ // Adds the <section> node that specifies the DLL that handles the <categorizedSettings> node in
536+ // the config file, if one is not present.
537+ XmlNode node = configFile.CreateElement("section");
538+ node.SetAttributeValue("name", CustomSectionName);
539+ node.SetAttributeValue("type", CustomSectionType);
540+ configSectionsNode.AppendChild(node);
541+ configFileModified = true;
542+ }
543+ }
526544 }
527545
528546 // 11/14/2006 - PCP: We'll save the config file only it was modified. This will prevent ASP.Net
529547 // web sites from restarting every time a configuration element is accessed.
530548 if (configFileModified)
531− {
532549 configFile.Save(configFilePath);
533− }
534550 }
535551 else
536552 {
Source/Libraries/GSF.Core/Identity/UnixUserInfo.cs+113−57
@@ -54,11 +54,15 @@ namespace GSF.Identity · 53 unmodified lines
5454 #region [ Members ]
5555
5656 // Nested Types
57+
58+ // ReSharper disable UnusedMember.Local
5759 private class UnixIdentity : WindowsIdentity
5860 {
5961 #region [ Members ]
6062
6163 // Fields
64+ private readonly string m_domain;
65+ private readonly string m_userName;
6266 private LdapConnection m_connection;
6367 private string m_providerType;
6468 private string m_ldapRoot;
@@ -70,22 +74,51 @@ namespace GSF.Identity · 5 unmodified lines
7074
7175 #region [ Constructors ]
7276
73− public UnixIdentity(string userName, LdapConnection connection = null)
74− : base(GetUserIDAsToken(userName), null, WindowsAccountType.Normal, true)
77+ public UnixIdentity(string domain, string userName, LdapConnection connection = null)
78+ : base(GetUserIDAsToken(domain, userName), null, WindowsAccountType.Normal, true)
7579 {
80+ m_domain = domain;
81+ m_userName = userName;
7682 Connection = connection;
7783
78− // Cache shadow information before possible reduction in privileges
79− if (GetLocalUserPasswordInformation(userName, ref m_userPasswordInformation, out m_accountStatus) == 0)
80− m_loadedUserPasswordInformation = true;
81− else
82− m_accountStatus = AccountStatus.Disabled;
84+ if (UserInfo.IsLocalDomain(domain))
85+ {
86+ // Cache shadow information for local user before possible reduction in privileges
87+ if (GetLocalUserPasswordInformation(userName, ref m_userPasswordInformation, out m_accountStatus) == 0)
88+ m_loadedUserPasswordInformation = true;
89+ else
90+ m_accountStatus = AccountStatus.Disabled;
91+ }
8392 }
8493
8594 #endregion
8695
8796 #region [ Properties ]
8897
98+ public string Domain
99+ {
100+ get
101+ {
102+ return m_domain;
103+ }
104+ }
105+
106+ public string UserName
107+ {
108+ get
109+ {
110+ return m_userName;
111+ }
112+ }
113+
114+ public string LoginID
115+ {
116+ get
117+ {
118+ return string.Format("{0}\\{1}", m_domain, m_userName);
119+ }
120+ }
121+
89122 public string ProviderType
90123 {
91124 get
@@ -115,7 +148,7 @@ namespace GSF.Identity · 23 unmodified lines
115148
116149 if ((object)m_connection != null)
117150 {
118− // Extract LDAP root distinguished name
151+ // Extract LDAP search root distinguished name
119152 StringBuilder ldapRoot = new StringBuilder();
120153 string[] elements = m_connection.GetSchemaDN().Split(',');
121154
@@ -169,9 +202,14 @@ namespace GSF.Identity · 47 unmodified lines
169202
170203 #region [ Static ]
171204
172− private static IntPtr GetUserIDAsToken(string userName)
205+ private static IntPtr GetUserIDAsToken(string domain, string userName)
173206 {
174207 uint userID;
208+
209+ // TODO: What to do if Linux stores mapped LDAP users as userName@domain? Format setting in config file?
210+ if (!UserInfo.IsLocalDomain(domain))
211+ userName = string.Format("{0}\\{1}", domain, userName);
212+
175213 return GetLocalUserID(userName, out userID) == 0 ? new IntPtr(userID) : IntPtr.Zero;
176214 }
177215
@@ -707,13 +745,14 @@ namespace GSF.Identity · 529 unmodified lines
707745
708746 // Pick up current user or impersonated user principal
709747 WindowsPrincipal principal = Thread.CurrentPrincipal as WindowsPrincipal;
748+ UnixIdentity identity = null;
710749
711750 if ((object)principal != null)
712751 {
713− UnixIdentity identity = principal.Identity as UnixIdentity;
752+ identity = principal.Identity as UnixIdentity;
714753
715754 // If domain user has already been authenticated, we should already have an active LDAP connection
716− if ((object)identity != null)
755+ if ((object)identity != null && identity.LoginID.Equals(m_parent.LoginID, StringComparison.InvariantCultureIgnoreCase))
717756 {
718757 m_connection = identity.Connection;
719758 m_ldapRoot = identity.LdapRoot ?? m_parent.Domain;
@@ -741,7 +780,18 @@ namespace GSF.Identity · 21 unmodified lines
741780 }
742781 else
743782 {
744− throw new InvalidOperationException("Failed to get a valid LDAP connection");
783+ // If PAM authentication succeeded but no LDAP connection can be found, we attempt to treat this user as local
784+ if ((object)identity != null && identity.LoginID.Equals(m_parent.LoginID, StringComparison.InvariantCultureIgnoreCase))
785+ {
786+ m_isLocalAccount = true;
787+ m_enabled = true;
788+ m_domainRespondsForUser = false;
789+ m_parent.UserAccountControl = -1;
790+ }
791+ else
792+ {
793+ throw new InvalidOperationException("No valid LDAP connection was found or user is not authenticated");
794+ }
745795 }
746796 }
747797 catch (Exception ex)
@@ -749,7 +799,7 @@ namespace GSF.Identity · 1 unmodified line
749799 m_userEntry = null;
750800 m_domainRespondsForUser = false;
751801
752− throw new InitializationException(string.Format("Failed to initialize directory entry for domain user '{0}'", m_parent.LoginID), ex);
802+ throw new InitializationException(string.Format("Failed to initialize LDAP entry for domain user '{0}': {1}", m_parent.LoginID, ex.Message), ex);
753803 }
754804 finally
755805 {
@@ -880,7 +930,7 @@ namespace GSF.Identity · 124 unmodified lines
880930 return s_builtInLocalGroups;
881931 }
882932
883− public static IPrincipal AuthenticateUser(string domain, string username, string password, out string errorMessage)
933+ public static IPrincipal AuthenticateUser(string domain, string userName, string password, out string errorMessage)
884934 {
885935 WindowsPrincipal principal = null;
886936 int responseCode;
@@ -888,28 +938,33 @@ namespace GSF.Identity · 1 unmodified line
888938
889939 if (UserInfo.IsLocalDomain(domain))
890940 {
891− responseCode = AuthenticateUser(username, password);
941+ // Set the domain as the local machine if one is not specified
942+ if (string.IsNullOrEmpty(domain))
943+ domain = Environment.MachineName;
944+
945+ responseCode = AuthenticateUser(userName, password);
892946
893947 if (responseCode == 0)
894− principal = new WindowsPrincipal(new UnixIdentity(username));
948+ principal = new WindowsPrincipal(new UnixIdentity(domain, userName));
895949 else
896− errorMessage = string.Format("Failed to authenticate \"{0}\": {1}", username, GetPAMErrorMessage(responseCode));
950+ errorMessage = string.Format("Failed to authenticate \"{0}\": {1}", userName, GetPAMErrorMessage(responseCode));
897951 }
898952 else
899953 {
900954 // Attempt PAM based authentication first - if configured, this will be the best option
901− string domainUserName = string.Format("{0}\\{1}", domain, username);
955+ string domainUserName = string.Format("{0}\\{1}", domain, userName);
902956
903957 responseCode = AuthenticateUser(domainUserName, password);
904958
905959 if (responseCode == 0)
906− principal = new WindowsPrincipal(new UnixIdentity(domainUserName));
960+ principal = new WindowsPrincipal(new UnixIdentity(domain, userName));
907961
908− string ldapPath = GetLdapPath();
962+ // Try really hard to find a configured LDAP host
963+ string ldapHost = GetLdapHost();
909964
910− // If LDAP path cannot be determined, no LdapConnection can be established - if authentication
965+ // If LDAP host cannot be determined, no LdapConnection can be established - if authentication
911966 // succeeded, user will be treated as a local user
912− if ((object)ldapPath == null)
967+ if ((object)ldapHost == null)
913968 {
914969 if ((object)principal == null)
915970 errorMessage = string.Format("Failed to authenticate \"{0}\": {1}", domainUserName, GetPAMErrorMessage(responseCode));
@@ -924,18 +979,18 @@ namespace GSF.Identity · 8 unmodified lines
924979 // Attempt LDAP account authentication
925980 LdapConnection connection = new LdapConnection();
926981
927− if (ldapPath.StartsWith("LDAP", StringComparison.OrdinalIgnoreCase))
982+ if (ldapHost.StartsWith("LDAP", StringComparison.OrdinalIgnoreCase))
928983 {
929− Uri ldapURI = new Uri(ldapPath);
930− ldapPath = ldapURI.Host + (ldapURI.Port == 0 ? "" : ":" + ldapURI.Port);
984+ Uri ldapURI = new Uri(ldapHost);
985+ ldapHost = ldapURI.Host + (ldapURI.Port == 0 ? "" : ":" + ldapURI.Port);
931986 }
932987
933988 // If host LDAP path contains suffixed port number, this will be preferred over specified 389 default
934− connection.Connect(ldapPath, 389);
935− connection.Bind(string.Format("{0}@{1}", username, domain), password);
989+ connection.Connect(ldapHost, 389);
990+ connection.Bind(string.Format("{0}@{1}", userName, domain), password);
936991
937992 if ((object)principal == null)
938− principal = new WindowsPrincipal(new UnixIdentity(domainUserName, connection));
993+ principal = new WindowsPrincipal(new UnixIdentity(domain, userName, connection));
939994 else
940995 ((UnixIdentity)principal.Identity).Connection = connection;
941996 }
@@ -951,11 +1006,11 @@ namespace GSF.Identity · 9 unmodified lines
9511006 return principal;
9521007 }
9531008
954− private static string GetLdapPath()
1009+ private static string GetLdapHost()
9551010 {
956− string ldapPath = null;
1011+ string ldapHost = null;
9571012
958− // Attempt to derive an LDAP path from a Samba configuration @ /etc/samba/smb.conf looking for key "realm"
1013+ // Attempt to derive an LDAP host from a Samba configuration @ /etc/samba/smb.conf looking for key "realm"
9591014 try
9601015 {
9611016 const string smbConfFileName = "/etc/samba/smb.conf";
@@ -979,7 +1034,7 @@ namespace GSF.Identity · 17 unmodified lines
9791034 string[] parts = line.Split('=');
9801035
9811036 if (parts.Length > 1)
982− ldapPath = parts[1].Trim();
1037+ ldapHost = parts[1].Trim();
9831038
9841039 break;
9851040 }
@@ -991,11 +1046,11 @@ namespace GSF.Identity · 5 unmodified lines
9911046 }
9921047 catch
9931048 {
994− ldapPath = null;
1049+ ldapHost = null;
9951050 }
9961051
997− // If LDAP path has not been derived yet, attempt to derive an LDAP path from an OpenLDAP configuration @ /etc/openldap/ldap.conf looking for key "URI"
998− if (string.IsNullOrEmpty(ldapPath))
1052+ // If LDAP host has not been derived yet, attempt to derive an LDAP host from an OpenLDAP configuration @ /etc/openldap/ldap.conf looking for key "URI"
1053+ if (string.IsNullOrEmpty(ldapHost))
9991054 {
10001055 try
10011056 {
@@ -1017,7 +1072,7 @@ namespace GSF.Identity · 15 unmodified lines
10171072
10181073 if (line.StartsWith("URI", StringComparison.OrdinalIgnoreCase) && line.Length > 3)
10191074 {
1020− ldapPath = line.Substring(3).Trim();
1075+ ldapHost = line.Substring(3).Trim();
10211076 break;
10221077 }
10231078 }
@@ -1028,12 +1083,12 @@ namespace GSF.Identity · 4 unmodified lines
10281083 }
10291084 catch
10301085 {
1031− ldapPath = null;
1086+ ldapHost = null;
10321087 }
10331088 }
10341089
1035− // If LDAP path has not been derived yet, attempt to derive an LDAP path from a Centrify DirectControl Express adinfo call looking for key "Joined to domain:"
1036− if (string.IsNullOrEmpty(ldapPath))
1090+ // If LDAP host has not been derived yet, attempt to derive an LDAP host from a Centrify DirectControl Express adinfo call looking for key "Joined to domain:"
1091+ if (string.IsNullOrEmpty(ldapHost))
10371092 {
10381093 try
10391094 {
@@ -1054,7 +1109,7 @@ namespace GSF.Identity · 14 unmodified lines
10541109 string[] parts = line.Split(':');
10551110
10561111 if (parts.Length > 1)
1057− ldapPath = parts[1].Trim();
1112+ ldapHost = parts[1].Trim();
10581113
10591114 break;
10601115 }
@@ -1065,12 +1120,12 @@ namespace GSF.Identity · 4 unmodified lines
10651120 }
10661121 catch
10671122 {
1068− ldapPath = null;
1123+ ldapHost = null;
10691124 }
10701125 }
10711126
1072− // If LDAP path has not been derived yet, attempt to derive an LDAP path from a LikewiseOpen / Beyond Trust lw-get-status call looking for key "Domain:"
1073− if (string.IsNullOrEmpty(ldapPath))
1127+ // If LDAP host has not been derived yet, attempt to derive an LDAP host from a LikewiseOpen / Beyond Trust lw-get-status call looking for key "Domain:"
1128+ if (string.IsNullOrEmpty(ldapHost))
10741129 {
10751130 try
10761131 {
@@ -1091,7 +1146,7 @@ namespace GSF.Identity · 14 unmodified lines
10911146 string[] parts = line.Split(':');
10921147
10931148 if (parts.Length > 1)
1094− ldapPath = parts[1].Trim();
1149+ ldapHost = parts[1].Trim();
10951150
10961151 break;
10971152 }
@@ -1102,12 +1157,12 @@ namespace GSF.Identity · 4 unmodified lines
11021157 }
11031158 catch
11041159 {
1105− ldapPath = null;
1160+ ldapHost = null;
11061161 }
11071162 }
11081163
1109− // If LDAP path has not been derived yet, attempt to derive an LDAP path from the configuration for known GSF security providers
1110− if (string.IsNullOrEmpty(ldapPath))
1164+ // If LDAP host has not been derived yet, attempt to derive an LDAP host from the configuration for known GSF security providers
1165+ if (string.IsNullOrEmpty(ldapHost))
11111166 {
11121167 try
11131168 {
@@ -1119,37 +1174,37 @@ namespace GSF.Identity · 5 unmodified lines
11191174 {
11201175 // In AdoSecurityProvider the ConnectionString setting is used for database connection
11211176 // to load role based security -- so it adds a new setting for actual LdapPath
1122− ldapPath = settings["LdapPath"].Value;
1177+ ldapHost = settings["LdapPath"].Value;
11231178 }
11241179 catch
11251180 {
1126− ldapPath = null;
1181+ ldapHost = null;
11271182 }
11281183
11291184 // Otherwise, attempt to get LDAP path defined by LdapSecurityProvider
1130− if ((object)ldapPath == null)
1185+ if ((object)ldapHost == null)
11311186 {
11321187 string ldapConnectionString = settings["ConnectionString"].Value;
11331188
11341189 if (ldapConnectionString.StartsWith("LDAP://", StringComparison.OrdinalIgnoreCase) ||
11351190 ldapConnectionString.StartsWith("LDAPS://", StringComparison.OrdinalIgnoreCase))
1136− ldapPath = ldapConnectionString;
1191+ ldapHost = ldapConnectionString;
11371192
11381193 foreach (KeyValuePair<string, string> pair in ldapConnectionString.ParseKeyValuePairs())
11391194 {
11401195 if (pair.Value.StartsWith("LDAP://", StringComparison.OrdinalIgnoreCase) ||
11411196 pair.Value.StartsWith("LDAPS://", StringComparison.OrdinalIgnoreCase))
1142− ldapPath = pair.Value;
1197+ ldapHost = pair.Value;
11431198 }
11441199 }
11451200 }
11461201 catch
11471202 {
1148− ldapPath = null;
1203+ ldapHost = null;
11491204 }
11501205 }
11511206
1152− return ldapPath;
1207+ return ldapHost;
11531208 }
11541209
11551210 public static WindowsImpersonationContext ImpersonateUser(string domain, string userName, string password)
@@ -1535,7 +1590,8 @@ namespace GSF.Identity · 379 unmodified lines
15351590
15361591 private static int GetCachedLocalUserPasswordInformation(string userName, out UserPasswordInformation userPasswordInformation, out AccountStatus accountStatus)
15371592 {
1538− // Attempt to pick up Unix user principal identity in case shadow information has already been parsed
1593+ // Attempt to retrieve Unix user principal identity in case shadow information has already been parsed, in most
1594+ // cases we will have already reduced rights needed to read this information so we pick up pre-parsed info
15391595 WindowsPrincipal principal = Thread.CurrentPrincipal as WindowsPrincipal;
15401596
15411597 if ((object)principal != null)
@@ -1543,7 +1599,7 @@ namespace GSF.Identity · 1 unmodified line
15431599 UnixIdentity identity = principal.Identity as UnixIdentity;
15441600
15451601 // If user has already been authenticated, we can load pre-parsed shadow information
1546− if ((object)identity != null && identity.LoadedUserPasswordInformation)
1602+ if ((object)identity != null && identity.LoadedUserPasswordInformation && identity.UserName.Equals(userName, StringComparison.InvariantCultureIgnoreCase))
15471603 {
15481604 userPasswordInformation = identity.UserPasswordInformation;
15491605 accountStatus = identity.AccountStatus;
@@ -1614,7 +1670,7 @@ namespace GSF.Identity · 64 unmodified lines
16141670 case PAMResponseCode.PAM_AUTHTOK_DISABLE_AGING:
16151671 return "Authentication token aging has been disabled for at least one of the modules.";
16161672 case PAMResponseCode.PAM_PERM_DENIED:
1617− return "Permission denied.";
1673+ return "Permission denied. Validate user credentials.";
16181674 case PAMResponseCode.PAM_TRY_AGAIN:
16191675 return "Not all of the modules were in a position to update the authentication token(s). None of the user's authentication tokens were updated.";
16201676 default:
Source/Libraries/GSF.Core/Xml/XmlExtensions.cs+35−25
@@ -30,8 +30,6 @@ · 29 unmodified lines
3030 // Edited Comments.
3131 // 09/14/2009 - Stephen C. Wills
3232 // Added new header and license agreement.
33−// 12/14/2012 - Starlynn Danyelle Gilliam
34−// Modifed Header.
3533 //
3634 //******************************************************************************************************
3735
@@ -105,15 +103,21 @@ namespace GSF.Xml · 67 unmodified lines
105103 {
106104 XmlNodeList nodes = parentNode.SelectNodes(xpath);
107105
108− if (nodes.Count == 0)
106+ if ((object)nodes != null)
109107 {
110− node = parentNode.OwnerDocument.CreateElement(xpath);
111− parentNode.AppendChild(node);
112− isDirty = true;
113− }
114− else
115− {
116− node = nodes[0];
108+ if (nodes.Count == 0)
109+ {
110+ if ((object)parentNode.OwnerDocument != null)
111+ {
112+ node = parentNode.OwnerDocument.CreateElement(xpath);
113+ parentNode.AppendChild(node);
114+ isDirty = true;
115+ }
116+ }
117+ else
118+ {
119+ node = nodes[0];
120+ }
117121 }
118122 }
119123 else
@@ -135,16 +139,15 @@ namespace GSF.Xml · 15 unmodified lines
135139 /// <returns>A <see cref="System.String"/> value returned for the attribute's value.</returns>
136140 public static string GetAttributeValue(this XmlNode node, string name)
137141 {
138− XmlAttribute attr = node.Attributes[name];
142+ if ((object)node == null || (object)node.Attributes == null)
143+ return null;
139144
140− if ((object)attr == null)
141− {
145+ XmlAttribute attribute = node.Attributes[name];
146+
147+ if ((object)attribute == null)
142148 return null;
143− }
144− else
145− {
146− return attr.Value;
147− }
149+
150+ return attribute.Value;
148151 }
149152
150153 /// <summary>Safely sets an XML node's attribute.</summary>
@@ -154,19 +157,26 @@ namespace GSF.Xml · 3 unmodified lines
154157 /// <param name="value">A <see cref="System.String"/> value to set the node attribute's value to.</param>
155158 public static void SetAttributeValue(this XmlNode node, string name, string value)
156159 {
157− XmlAttribute attr = node.Attributes[name];
160+ if ((object)node == null || (object)node.Attributes == null)
161+ return;
162+
163+ XmlAttribute attribute = node.Attributes[name];
158164
159− if ((object)attr == null)
165+ if ((object)attribute == null)
160166 {
161167 // Add the attribute.
162− attr = node.OwnerDocument.CreateAttribute(name);
163− node.Attributes.Append(attr);
168+ if ((object)node.OwnerDocument != null)
169+ {
170+ attribute = node.OwnerDocument.CreateAttribute(name);
171+ attribute.Value = value;
172+ node.Attributes.Append(attribute);
173+ }
164174 }
165− if ((object)attr != null)
175+ else
166176 {
167177 // Set the attribute value.
168− attr.Value = value;
169− node.Attributes.SetNamedItem(attr);
178+ attribute.Value = value;
179+ node.Attributes.SetNamedItem(attribute);
170180 }
171181 }
172182
Source/Tools/ConfigTest/App.config+14−0
@@ -0,0 +1,14 @@
1+<?xml version="1.0" encoding="utf-8" ?>
2+<configuration>
3+ <configSections>
4+ <section name="categorizedSettings" type="GSF.Configuration.CategorizedSettingsSection, GSF.Core" />
5+ </configSections>
6+ <categorizedSettings>
7+ <systemSettings>
8+ <add name="connectionString" value="nope" description="Test" encrypted="false" />
9+ </systemSettings>
10+ </categorizedSettings>
11+ <startup>
12+ <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
13+ </startup>
14+</configuration>
Source/Tools/ConfigTest/ConfigTest.csproj+71−0
@@ -0,0 +1,71 @@
1+<?xml version="1.0" encoding="utf-8"?>
2+<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+ <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
4+ <PropertyGroup>
5+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+ <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+ <ProjectGuid>{B38AC5E5-B880-43E9-8F88-107294F3E69F}</ProjectGuid>
8+ <OutputType>Exe</OutputType>
9+ <AppDesignerFolder>Properties</AppDesignerFolder>
10+ <RootNamespace>ConfigTest</RootNamespace>
11+ <AssemblyName>ConfigTest</AssemblyName>
12+ <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
13+ <FileAlignment>512</FileAlignment>
14+ <SccProjectName>SAK</SccProjectName>
15+ <SccLocalPath>SAK</SccLocalPath>
16+ <SccAuxPath>SAK</SccAuxPath>
17+ <SccProvider>SAK</SccProvider>
18+ </PropertyGroup>
19+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
20+ <PlatformTarget>AnyCPU</PlatformTarget>
21+ <DebugSymbols>true</DebugSymbols>
22+ <DebugType>full</DebugType>
23+ <Optimize>false</Optimize>
24+ <OutputPath>bin\Debug\</OutputPath>
25+ <DefineConstants>DEBUG;TRACE</DefineConstants>
26+ <ErrorReport>prompt</ErrorReport>
27+ <WarningLevel>4</WarningLevel>
28+ </PropertyGroup>
29+ <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
30+ <PlatformTarget>AnyCPU</PlatformTarget>
31+ <DebugType>pdbonly</DebugType>
32+ <Optimize>true</Optimize>
33+ <OutputPath>bin\Release\</OutputPath>
34+ <DefineConstants>TRACE</DefineConstants>
35+ <ErrorReport>prompt</ErrorReport>
36+ <WarningLevel>4</WarningLevel>
37+ </PropertyGroup>
38+ <ItemGroup>
39+ <Reference Include="System" />
40+ <Reference Include="System.Configuration" />
41+ <Reference Include="System.Core" />
42+ <Reference Include="System.Xml.Linq" />
43+ <Reference Include="System.Data.DataSetExtensions" />
44+ <Reference Include="Microsoft.CSharp" />
45+ <Reference Include="System.Data" />
46+ <Reference Include="System.Xml" />
47+ </ItemGroup>
48+ <ItemGroup>
49+ <Compile Include="Program.cs" />
50+ <Compile Include="Properties\AssemblyInfo.cs" />
51+ </ItemGroup>
52+ <ItemGroup>
53+ <None Include="App.config">
54+ <SubType>Designer</SubType>
55+ </None>
56+ </ItemGroup>
57+ <ItemGroup>
58+ <ProjectReference Include="..\..\Libraries\GSF.Core\GSF.Core.csproj">
59+ <Project>{ff3fcba6-f01a-4ec2-bc3f-6ba832afcf88}</Project>
60+ <Name>GSF.Core</Name>
61+ </ProjectReference>
62+ </ItemGroup>
63+ <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
64+ <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
65+ Other similar extension points exist, see Microsoft.Common.targets.
66+ <Target Name="BeforeBuild">
67+ </Target>
68+ <Target Name="AfterBuild">
69+ </Target>
70+ -->
71+</Project>
Source/Tools/ConfigTest/ConfigTest.csproj.vspscc+10−0
@@ -0,0 +1,10 @@
1+""
2+{
3+"FILE_VERSION" = "9237"
4+"ENLISTMENT_CHOICE" = "NEVER"
5+"PROJECT_FILE_RELATIVE_PATH" = ""
6+"NUMBER_OF_EXCLUDED_FILES" = "0"
7+"ORIGINAL_PROJECT_FILE_PATH" = ""
8+"NUMBER_OF_NESTED_PROJECTS" = "0"
9+"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
10+}
Source/Tools/ConfigTest/Program.cs+47−0
@@ -0,0 +1,47 @@
1+using System;
2+using GSF.Configuration;
3+
4+namespace ConfigTest
5+{
6+ class Program
7+ {
8+ static void Main(string[] args)
9+ {
10+ ConfigurationFile config = ConfigurationFile.Current;
11+ CategorizedSettingsElementCollection settings = config.Settings["systemSettings"];
12+ CategorizedSettingsElementCollection examples = config.Settings["examples"];
13+
14+ settings.Add("connectionString", "abc=123", "My device connection string");
15+ settings.Add("property2", "clever", "Smack, smack!");
16+ examples.Add("newProperty", "test", "New property description");
17+ examples.Add("areWeThereYet", "never", "Why?");
18+
19+ string connectionString = settings["connectionString"].ValueAs("default");
20+ string property2 = settings["property2"].ValueAs("default2");
21+ string newProperty = examples["newProperty"].ValueAs("default3");
22+ string areWeThereYet = examples["areWeThereYet"].ValueAs("default4");
23+
24+ Console.WriteLine();
25+ Console.WriteLine("systemSettings.connectionString setting = " + connectionString);
26+ Console.WriteLine("systemSettings.property2 setting = " + property2);
27+ Console.WriteLine("examples.newProperty = " + newProperty);
28+ Console.WriteLine("examples.areWeThereYet = " + areWeThereYet);
29+
30+ Console.WriteLine();
31+ Console.WriteLine("Updating setting to new random value...");
32+ settings["connectionString", true].Value = Guid.NewGuid().ToString();
33+
34+ Console.WriteLine();
35+ Console.WriteLine("New setting: " + settings["connectionString"].Value);
36+
37+ Console.WriteLine();
38+ Console.WriteLine("Saving updated configuration settings...");
39+
40+ config.Save();
41+
42+ Console.WriteLine();
43+ Console.WriteLine("Press any key to continue...");
44+ Console.ReadKey();
45+ }
46+ }
47+}
Source/Tools/ConfigTest/Properties/AssemblyInfo.cs+36−0
@@ -0,0 +1,36 @@
1+using System.Reflection;
2+using System.Runtime.CompilerServices;
3+using System.Runtime.InteropServices;
4+
5+// General Information about an assembly is controlled through the following
6+// set of attributes. Change these attribute values to modify the information
7+// associated with an assembly.
8+[assembly: AssemblyTitle("ConfigTest")]
9+[assembly: AssemblyDescription("")]
10+[assembly: AssemblyConfiguration("")]
11+[assembly: AssemblyCompany("")]
12+[assembly: AssemblyProduct("ConfigTest")]
13+[assembly: AssemblyCopyright("Copyright © 2014")]
14+[assembly: AssemblyTrademark("")]
15+[assembly: AssemblyCulture("")]
16+
17+// Setting ComVisible to false makes the types in this assembly not visible
18+// to COM components. If you need to access a type in this assembly from
19+// COM, set the ComVisible attribute to true on that type.
20+[assembly: ComVisible(false)]
21+
22+// The following GUID is for the ID of the typelib if this project is exposed to COM
23+[assembly: Guid("0324eeb3-dfef-49df-adce-f55c3deaa6a1")]
24+
25+// Version information for an assembly consists of the following four values:
26+//
27+// Major Version
28+// Minor Version
29+// Build Number
30+// Revision
31+//
32+// You can specify all the values or you can default the Build and Revision Numbers
33+// by using the '*' as shown below:
34+// [assembly: AssemblyVersion("1.0.*")]
35+[assembly: AssemblyVersion("1.0.0.0")]
36+[assembly: AssemblyFileVersion("1.0.0.0")]

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t