|
Introduction
In the Windows development history, the problem of maintaining the configuration settings for an application has been faced in many ways. For example, in Windows 3.1, the classic approach to the problem was in the use of INI files; more recently a standard way to store config settings was in reading/writing Windows Registry keys. Now, in the .NET age, we can take advantage of the power and simplicity of .config files that I consider a sort of return to the INI approach (simply due to the need of storing config settings near the application's assemblies, to make the XCOPY deployment possible).
Of course, their text-based, simply editable XML structure make .NET configuration files very handy. But, when you use the ConfigurationSettings class (from the System.Configuration namespace) to access them, there are two main limitations:
- the name for the application configuration file is one and fixed (i.e. web.config or app.exe.config) and
- the
AppSettings property of the ConfigurationSettings class allows only a read-only access to the values stored inside the file.
The simple VB.NET class I propose in this article shows a way to manage configuration settings in a read/write (not read-only) fashion, and to allow multiple configuration files for the same application. The proposed approach is - of course - custom, and it is not related to the classic .NET .config files and their structure, but:
- the idea of text-based, XML configuration files has been kept;
- configuration settings continue to be stored as key/value pairs;
- taking advantage of the ADO.NET
DataSet serialization features, this class is implemented in a few lines of code.
The codeImports System.IO
Public Class ConfigOpt
Private Shared DSoptions As DataSet
Private Shared mConfigFileName As String
Public Shared ReadOnly Property ConfigFileName() As String
Get
Return mConfigFileName
End Get
End Property
Public Shared Sub Initialize(ByVal ConfigFile As String)
mConfigFileName = ConfigFile
DSoptions = New DataSet("ConfigOpt")
If File.Exists(ConfigFile) Then
DSoptions.ReadXml(ConfigFile)
Else
Dim dt As New DataTable("ConfigValues")
dt.Columns.Add("OptionName", System.Type.GetType("System.String"))
dt.Columns.Add("OptionValue", System.Type.GetType("System.String"))
DSoptions.Tables.Add(dt)
End If
End Sub
Public Shared Sub Store()
Store(mConfigFileName)
End Sub
Public Shared Sub Store(ByVal ConfigFile As String)
mConfigFileName = ConfigFile
DSoptions.WriteXml(ConfigFile)
End Sub
Public Shared Function GetOption(ByVal OptionName As String) As String
Dim dv As DataView = DSoptions.Tables("ConfigValues").DefaultView
dv.RowFilter = "OptionName='" & OptionName & "'"
If dv.Count > 0 Then
Return CStr(dv.Item(0).Item("OptionValue"))
Else
Return ""
End If
End Function
Public Shared Sub SetOption(ByVal OptionName _
As String, ByVal OptionValue As String)
Dim dv As DataView = DSoptions.Tables("ConfigValues").DefaultView
dv.RowFilter = "OptionName='" & OptionName & "'"
If dv.Count > 0 Then
dv.Item(0).Item("OptionValue") = OptionValue
Else
Dim dr As DataRow = DSoptions.Tables("ConfigValues").NewRow()
dr("OptionName") = OptionName
dr("OptionValue") = OptionValue
DSoptions.Tables("ConfigValues").Rows.Add(dr)
End If
End Sub
End Class
How to use the code
All the methods the ConfigOpt class exposes are static, so there is no need to create an instance of the class.
Call the Initialize method to read and put in a memory cache an existing config file. You need to call this method also in absence of an existing config file, to initialize the data structure used by ConfigOpt class.
Call the GetOption method to read a configuration value, given its key. Keep in mind that read values are always kept from the cached config key/value pairs (not from the config file directly).
Call the SetOption method to add or update a configuration setting, in the form of a key/value pair. Keys and values are always String types. Keep in mind that addition and updation of config settings are done only in the cached memory structure; to persist all the config key/value pairs on the file system, you need to call the Store method.
Typically, the Store method is called once (when the user closes the application, or he closes a configuration dialog box, or he explicitly wants to save his settings), but you may call this method more often if you need to immediately persist on file the ConfigOpt internal data changes.
An example
Suppose you have, in a Windows Forms application of yours, a configuration form containing some controls (textboxes, checkboxes, etc.); if you need to persist the state of these controls on a file so that their values are maintained between different user sessions, you can use the ConfigOpt class in this way: Private Sub Form_Load(...) Handles MyBase.Load
ConfigOpt.Initialize("MyConfig.cfg")
ReadConfigValues()
End Sub
Private Sub ReadConfigValues()
txtMyTextbox.Text = ConfigOpt.GetOption("txtMyTextbox")
chkMyCheckbox.Checked = Boolean.Parse(ConfigOpt.GetOption("chkMyCheckbox"))
...
End Sub
Private Sub Form_Closing(...) Handles MyBase.Closing
WriteConfigValues()
ConfigOpt.Store()
End Sub
Private Sub WriteconfigValues()
ConfigOpt.SetOption("txtMyTextbox", txtMyTextbox.Text)
ConfigOpt.SetOption("chkMyCheckbox", chkMyCheckbox.Checked.ToString())
....
End Sub
The generated configuration file will contain this text that you can edit with Notepad whenever you want: ="1.0" ="yes"
<ConfigOpt>
<ConfigValues>
<OptionName>txtMyTextbox</OptionName>
<OptionValue>Sample string typed in txtMyTextbox.</OptionValue>
</ConfigValues>
<ConfigValues>
<OptionName>chkMyCheckbox</OptionName>
<OptionValue>True</OptionValue>
</ConfigValues>
...
</ConfigOpt>
As suggested by some CodeProject readers (thank you!), you may prefer a more compact XML file with this structure: ="1.0" ="yes"
<ConfigOpt xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ConfigValues OptionName="txtMyTextbox">
Sample string typed in txtMyTextbox.</ConfigValues>
<ConfigValues OptionName="chkMyCheckbox">True</ConfigValues>
</ConfigOpt>
In this case, you need to modify the Else branch in the ConfigOpt.Initialize method (where the DataTable is defined), modifying the default XML column mapping as follows: Dim dt As New DataTable("ConfigValues")
dt.Columns.Add("OptionName", System.Type.GetType("System.String"))
dt.Columns.Add("OptionValue", System.Type.GetType("System.String"))
dt.Columns("OptionName").ColumnMapping = MappingType.Attribute
dt.Columns("OptionValue").ColumnMapping = MappingType.SimpleContent
DSoptions.Tables.Add(dt)
| You must Sign In to use this message board. |
|
| | Msgs 1 to 25 of 36 (Total in Forum: 36) (Refresh) | FirstPrevNext |
|
 |
|
|
 |
|
|
I had to adjust a few things in order to get it to function the way I wanted it to, but you provided an excellent basis!!
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
My problem is the following: I have many projects in a solution, each project has a file AppConfig . I need to load all configuration data of the all project's solution on memory cache. How can i do this? Can you help me please?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
My problem is the following: I have many projects in a solution, each project has a file AppConfig. I need to load all configuration data of the all project's solution on memory cache. How can i do this? Can you help me please?
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
I guess my question is similar to another thread around multiple sections.
I'm attempting to create multiple instances of the ConfigOpt object, to collect say general application configuration data, with a second instance for other data. When doing this, data seems to report to the instance initailised last, regardless of the object name.
For example,
Dim objConfig As New ConfigOpt 'for application settings Dim objProjectData As New ConfigOpt 'for other data
'instance 1 objConfig.Initialize(gstrConfigFile) objConfig.SetOption("Param1","mydata") objConfig.Store
'instance 2 objProjectData.Initialize(gstrProjectDataFile) objProjectData.SetOption("Param2","mydata2") objProjectData.Store
'now returning to instance 1 objConfig.SetOption("Param3","mydata3") objConfig.Store
The above code seems to store mydata3 in the .xml file initialised under objProjectData, rather than objConfig.
Am I attempting something that is simply not possible? - Please help.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Well... the problem simply is that you're instantiating multiple times a class designed to be used only once. Notice, in my examples of use, the absence of instantiation (no "New" at all) and the use of the class name instead of instance names. To achieve your goal, you need to: 1) convert all static (shared) members of the ConfigOpt class in non-static members, by removing the "Shared" keyword from these lines: Private Shared DSoptions As DataSet Private Shared mConfigFileName As String Public Shared ReadOnly Property ConfigFileName() As String Public Shared Sub Initialize(ByVal ConfigFile As String) Public Shared Sub Store() Public Shared Sub Store(ByVal ConfigFile As String) Public Shared Function GetOption(ByVal OptionName As String) As String Public Shared Sub SetOption(ByVal OptionName As String, ByVal OptionValue As String) In this way, all instances will use their own members, so their own config files and DataSets (instead of sharing them, with the side effects you noticed on a shared DSoptions DataSet and a shared mConfigFileName filename). 2) use your code exactly as you wrote it.
Please let me know if you have further problems. Cheers, AV
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
using System.IO; using System.Data; /// /// Class for managing configuration persistence adapted from source code at /// http://www.codeproject.com/vb/net/ConfigOpt.asp#xx1433090xx This class /// allows setting custom sections much as ini file calls and registry calls do. /// public class CConfigSettings { /// /// This DataSet is used as a memory data structure to hold config /// section/key/value pairs Inside this DataSet, a DataTable for each /// section is created /// private DataSet m_dsOptions; // This is the filename for the DataSet XML serialization private string m_szConfigFileName;
/// /// This property is read-only, because it is set through Initialize or /// Store methods /// public string szConfigFileName { get { return m_szConfigFileName; } }
/// /// This method has to be invoked before using any other method of /// ConfigOpt class ConfigFile parameter is the name of the config file /// to be read. If the config file does not exist, and the user calls /// "SetOptions", the config file will be created when "Store" is /// called. /// /// public void Initialize(string szConfigFile) { this.m_szConfigFileName = szConfigFile; this.m_dsOptions = new DataSet("ConfigOpt"); if(File.Exists(szConfigFile)) { // If the specified config file exists, it is read to populate // the DataSet this.m_dsOptions.ReadXml(szConfigFile); } }
/// /// This method serializes the memory data structure holding the config /// parameters The filename used is the one defined calling Initialize /// method /// public void Store() { this.Store(this.m_szConfigFileName); }
/// /// Same as Store() method, but with the ability to serialize on /// different filename /// /// public void Store(string szConfigFile) { this.m_szConfigFileName = szConfigFile; this.m_dsOptions.WriteXml(szConfigFile); }
/// /// Read a configuration Value, given its name and section name /// (szOptionName). If the Key is not defined, the default value is /// returned, and the entry is added to the data set. /// /// /// /// /// string public string GetOption(string szSectionName, string szOptionName, string szDefault) { bool bAddOption = true; string szReturn = szDefault; if (this.m_dsOptions.Tables[szSectionName] != null) { DataView dv = m_dsOptions.Tables[szSectionName].DefaultView; dv.RowFilter = "OptionName='" + szOptionName + "'"; if (dv.Count > 0) { szReturn = dv[0]["OptionValue"].ToString(); bAddOption = false; } }
if (bAddOption == true) { this.SetOption(szSectionName, szOptionName, szDefault); }
return szReturn; }
/// /// Overload for getting integer values /// /// /// /// /// int public int GetOption(string szSectionName, string szOptionName, int iDefault) { return int.Parse(this.GetOption(szSectionName, szOptionName, iDefault.ToString())); }
/// /// Overload for getting double values /// /// /// /// /// public double GetOption(string szSectionName, string szOptionName, double dDefault) { return double.Parse(this.GetOption(szSectionName, szOptionName, dDefault.ToString())); }
/// /// Write in the memory data structure a Key/Value pair for a /// configuration setting. If the Key already exists, the Value is /// simply updated, else the Key/Value pair is added. Warning: to update /// the written Key/Value pair on the config file, you need to call /// Store /// /// /// /// public void SetOption(string szSectionName, string szOptionName, string szOptionValue) { if( this.m_dsOptions.Tables[szSectionName] == null) { DataTable dt = new DataTable(szSectionName); dt.Columns.Add("OptionName", System.Type.GetType("System.String")); dt.Columns.Add("OptionValue", System.Type.GetType("System.String")); // dt.Columns.Add("OptionType", this.m_dsOptions.Tables.Add(dt); }
DataView dv = this.m_dsOptions.Tables[szSectionName].DefaultView; dv.RowFilter = "OptionName='" + szOptionName + "'"; if( dv.Count > 0) { dv[0]["OptionValue"] = szOptionValue; } else { DataRow dr = m_dsOptions.Tables[szSectionName].NewRow(); dr["OptionName"] = szOptionName; dr["OptionValue"] = szOptionValue; this.m_dsOptions.Tables[szSectionName].Rows.Add(dr); } }
/// /// Overload of SetOption that takes an integer option value /// /// /// /// public void SetOption(string szSectionName, string szOptionName, int iOptionValue) { this.SetOption(szSectionName, szOptionName, iOptionValue. ToString()); }
/// /// Overload of SetOption that takes a double option value /// /// /// /// public void SetOption(string szSectionName, string szOptionName, double dOptionValue) { this.SetOption(szSectionName, szOptionName, dOptionValue. ToString()); } }
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
hi,
i wrote this code for my use and practice. use it widely!
using System; using System.Xml; using System.Data;
namespace Settings { /// /// Summary description for Xml. /// public class Xml { private Xml(){} private static bool flagInitialized = false; private static DataSet dataSet = new DataSet(); public static bool Initialize(string xmlPath) { try { dataSet.ReadXml(xmlPath); flagInitialized = true; return flagInitialized; } catch(Exception e) { throw e; } } public static string GetElement(string elemetName) { if (!flagInitialized) { return null; } DataView dv = dataSet.Tables[elemetName].DefaultView; if (dv.Count > 0) { return dv[0].Row.ItemArray[0].ToString(); } else { return null; } } } }
Oz R.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
'Add this in you Initialize procedure
Dim aFileStream As New FileStream(ConfigFile, FileMode.Open) Dim aStreamReader As New StreamReader(aFileStream) Dim aUE As New UnicodeEncoding Dim key() As Byte = aUE.GetBytes("password") Dim RMCrypto As RijndaelManaged = New RijndaelManaged Dim aCryptoStream As New CryptoStream(aFileStream, _ RMCrypto.CreateDecryptor(key, key), CryptoStreamMode.Read)
DSoptions.ReadXml(aCryptoStream) aStreamReader.Close() aFileStream.Close()
'Add this in your "store" procedure
Dim aXmlTextWriter As Xml.XmlTextWriter
aXmlTextWriter = New XmlTextWriter(ConfigFile, System.Text.Encoding.UTF8)
Dim aUE As New UnicodeEncoding Dim key() As Byte = aUE.GetBytes("password") Dim RMCrypto As RijndaelManaged = New RijndaelManaged Dim aCryptoStream As New CryptoStream(aXmlTextWriter.BaseStream, _ RMCrypto.CreateEncryptor(key, key), CryptoStreamMode.Write)
DSoptions.WriteXml(aCryptoStream) aCryptoStream.Close()
DSoptions.Clear()
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Sure, you just have to generalize the ConfigValues DataTable concept, extending (for example) the configuration DataSet so that it holds more configuration DataTables. Another way could be: adding a "section" column to the key-value combination, implementing then a Select search of the desired value inside the only ConfigValues DataTable. AV
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
|
I didn't try that code, but - as far as I see by reading it - it simply seems to make my class compliant with the standard .NET structure for configuration files (that is: AppSettings mechanism, based on an XML with "add" nodes containing key/value pairs as attributes). I think it doesn't support multiple sections (as mine doesn't), just bacause the Function GetOption(ByVal OptionName As String) method doesn't accept any kind of "section" parameter. Did you think about "simulating" configuration sections just by adopting a particular naming convention for the stored keys? For example, you could simply call: txtMyTextbox.Text = ConfigOpt.GetOption("MySection.txtMyTextbox") instead of: txtMyTextbox.Text = ConfigOpt.GetOption("txtMyTextbox") Ok: this "workaround" doesn't guarantee that keys of the same section are stored as a "set" in the configuration file, but - from the application perspective - you actually can have "homologous" keys in different "sections". AV
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
What would be the format of a file with multiple configuration sections that ReadXml can read and put in the options DataSet?
AN
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Hi,
This article solves a common problem with the .NET applications but when adopting this, I found it created a lot more problems for me. I work in a team on a fairly large project. We've used the .NET configuration settings widely because of its integration with the IDE and dynamic properties in the designers. A major shortcoming of this implementation, I found is its use of a different XML schema. This breaks our existing code. I have modified the source to use the existing .NET configuration file and I think others may find the source useful. It preserves other contents of the configuration file such as the custom sections and section groups. Please find the code below:
Imports System.IO Imports System.Xml ' Class for managing configuration persistence Public Class ConfigOpt
' This DataSet is used as a memory data ' structure to hold config key/value pairs ' Inside this DataSet, a single DataTable named ConfigValues is created Private Shared DSoptions As DataSet ' This is the filename for the DataSet XML serialization Private Shared mConfigFileName As String
' This property is read-only, because it is set ' through Initialize or Store methods Public Shared ReadOnly Property ConfigFileName() As String Get Return mConfigFileName End Get End Property
' This method has to be invoked before using ' any other method of ConfigOpt class ' ConfigFile parameter is the name of the config file to be read ' (if that file doesn't exists, the method ' simply initialize the data structure ' and the ConfigFileName property) Public Shared Sub Initialize(ByVal ConfigFile As String) mConfigFileName = ConfigFile DSoptions = New DataSet("configuration") If File.Exists(ConfigFile) Then ' If the specified config file exists, ' it is read to populate the DataSet DSoptions.ReadXml(ConfigFile) 'Dim dt As DataTable 'For Each dt In DSoptions.Tables ' Console.WriteLine(dt.TableName) 'Next Else Dim dt As New DataTable("add") dt.Columns.Add("key", System.Type.GetType("System.String")) dt.Columns.Add("value", System.Type.GetType("System.String")) dt.Columns("key").ColumnMapping = MappingType.Attribute dt.Columns("value").ColumnMapping = MappingType.Attribute DSoptions.Tables.Add(dt) End If End Sub
' This method serializes the memory data ' structure holding the config parameters ' The filename used is the one defined calling Initialize method Public Shared Sub Store() Store(mConfigFileName) End Sub
' Same as Store() method, but with the ability ' to serialize on a different filename Public Shared Sub Store(ByVal ConfigFile As String) mConfigFileName = ConfigFile DSoptions.WriteXml(ConfigFile) 'post process to collect all add records in configuration/appSettings If postProcess Then Dim xdoc As New XmlDocument xdoc.Load(ConfigFile) Dim xn As XmlNode Dim xnAdd As XmlNodeList = xdoc.DocumentElement.SelectNodes("add") Dim xnAppSett As XmlNode = xdoc.DocumentElement.SelectSingleNode("appSettings") For Each xn In xnAdd xdoc.DocumentElement.RemoveChild(xn) xnAppSett.AppendChild(xn) Next xdoc.Save(ConfigFile) End If End Sub
' Read a configuration Value (aka OptionValue), ' given its Key (aka OptionName) ' If the Key is not defined, an empty string is returned Public Shared Function GetOption(ByVal OptionName As String) As String Dim dv As DataView = DSoptions.Tables("add").DefaultView dv.RowFilter = "key='" & OptionName & "'" If dv.Count > 0 Then Return CStr(dv.Item(0).Item("value")) Else Return "" End If End Function
' Write in the memory data structure a Key/Value ' pair for a configuration setting ' If the Key already exists, the Value is simply updated, ' else the Key/Value pair is added ' Warning: to update the written Key/Value pair ' on the config file, you need to call Store Private Shared postProcess As Boolean = False Public Shared Sub SetOption(ByVal OptionName _ As String, ByVal OptionValue As String, Optional ByVal noadd As Boolean = False) Dim dv As DataView = DSoptions.Tables("add").DefaultView dv.RowFilter = "key='" & OptionName & "'" If dv.Count > 0 Then dv.Item(0).Item("value") = OptionValue Else Dim dr As DataRow = DSoptions.Tables("add").NewRow() dr("key") = OptionName dr("value") = OptionValue DSoptions.Tables("add").Rows.Add(dr) postProcess = True End If End Sub
End Class
In order to determine the name and location of the config file, I've used:
Dim Asm As System.Reflection.Assembly = System.Reflection.Assembly.GetEntryAssembly ConfigOpt.Initialize(Asm.Location + ".config")
I hope this is helpful.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
The ConfigOpt class was created as an alternative to config files, not having in mind any goal of compatibility with standard .NET configuration files. So... I can only give you many thanks to this your intervention, that makes the article more complete. Nice work! AV
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
Your problem may be solve on: http://idmnetcontrols.com You try download Config Manager (GUI application) and Configuration Assemblies for using inside of your application.
|
| Sign In·View Thread·PermaLink | 2.00/5 (1 vote) |
|
|
|
 |
|
|
Hey,
I'm using the class in a program I've written. From the application I've written, users can open files for editing (the details of these files is irrelevant i think). What I have noticed is that when you open a file, the path of the xml config file changes from the bin directory were the app is to whatever directory the opened file is in.
So when you run the application and view the contents of the config file through the program (via a menu which displays a property grid) you see one set of values, then if you open a file and do the same thing, you see a different set of values.
Any thoughts?
dave
|
| Sign In·View Thread·PermaLink | 5.00/5 (1 vote) |
|
|
|
 |
|
|
 |
|
|
Of course, if you specify for the configuration file a filename like in my sample:
ConfigOpt.Initialize("MyConfig.cfg")
the file will be read/written in the "default" folder. In the beginning, this "default" folder is the application folder, but it may change if you use - for example - an OpenFile dialog box during the application execution. The solution is to specify a fixed configuration filename, using an absolute path. Of course, you could simply use a path like:
ConfigOpt.Initialize("C:\MyApp\MyConfig.cfg")
but you also can let .NET Framework determine your application folder at run time, as in:
ConfigOpt.Initialize(Application.StartupPath & "\MyConfig.cfg")
AV
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
[...]
but you also can let .NET Framework determine your application folder at run time, as in:
ConfigOpt.Initialize(Application.StartupPath & "\MyConfig.cfg")
[...]
OR EVEN:
ConfigOpt.Initialize(".\MyConfig.cfg")
Which is much easier: ".\" versus Application.StartupPath & "\"
|
| Sign In·View Thread·PermaLink | | | | | | |