The .NET Framework provides a powerful and flexible means of configuring applications. This configuration is accomplished by using text-based XML configuration files. The machine-wide configuration file is called machine.config (described later). This file is supplemented by application-specific configuration files, also described shortly.
This configuration scheme offers the following advantages:
|
22.2.1 Hierarchical Configuration
The configuration system is hierarchicalconfiguration files are applied in successive order. Unlike many hierarchies, and unlike ASP.NET configuration, this hierarchy does not have any parallel branchesonly levels.
A file called machine.config is at the top of the hierarchy. This file is contained in the subdirectory:
c:windowsMicrosoft.NETFrameworkversion numberCONFIG
where version number will be replaced with the version of the .NET runtime installed on your machine, such as v1.1.4322.
The next level in the configuration hierarchy has publisher policy files. These files are provided by the publisher of an assembly and are associated with a specific version of a specific assembly. They apply to all managed applications that call that assembly's version. Their purpose is to redirect requests for older versions of an assembly to the new version. The creation and use of publisher configuration policy files is obscure and beyond the scope of this book (see the integrated documentation from Microsoft for details).
The bottom level of the configuration hierarchy is the application-specific configuration file, named after the application executable with an additional extension of .config, and located in the same directory as the executable file. For example, if an application executable file is called SomeApp.exe, the configuration file associated with that application will be SomeApp.exe.config.
|
The application configuration files are optional. If there are none for a given application, then the configuration settings contained in machine.config will apply to that application without any modification.
Each application executable can have at most a single configuration file. The configuration settings contained in an application configuration file apply only to that application. If an application configuration file contains a setting that is in conflict with a setting higher up in the configuration hierarchy (i.e., in machine.config), then the lower-level setting will override and apply to its application.
The application configuration files described here don't meet all the configuration requirements an application may have. For example, you cannot store user settings for multiuser applications here because the config files are per application and not per user. In addition, updating a config file as an application runs requires write access to Program Files, which is normally granted only to users with Administrative privileges. If you want to store per-user configuration settings, use some other technique, such as writing a custom XML file or making a Registry entry.
22.2.2 Configuration File Format
The configuration files are XML files. As such, they must be well formed. (For a description of well-formed XML files, see the sidebar Sidebar 22-2.)
Typically, tag and attribute names consist of one or more words using camel case. Attribute values are usually Pascal cased.
|
Note that not all attributes use Pascal casing:
The first line in the configuration files declares the file an XML file, with attributes specifying the version of the XML specification to which the file adheres and the character encoding used.
The character encoding specified here is UTF-8, which is a superset of ASCII. The character-encoding parameter may be omitted only if the XML document is written in either UTF-8 or UTF-32. Therefore, if the XML file is written in pure ASCII, the encoding parameter may be omitted, although including the attribute contributes to self-documentation.
|
The next line in the configuration files is the opening tag:
The entire contents of the configuration file, except the initial XML declaration, is contained between the opening tag and the closing tag.
Comments can bec contained within the file by using the standard HTML comment:
Comments may be located anywhere within the file except prior to the first line that declares the XML file.
Within the tags are two broad categories of entries. They are, in the order in which they appear in the configuration files:
22.2.3 Configuration Section Handler Declarations
The first part of the machine.config file consists of handler declarations that are contained between an opening tag and a closing tag. Each handler declaration specifies the name of a configuration section, contained elsewhere in the file, that provides specific configuration data. Each declaration also contains the name of the .NET class that will process the configuration data in that section.
|
The machine.config file contains, in the default installation, many configuration section handler declarations that cover the areas subject to configuration by default. (Since this system is extensible, you can also create your own, as described next.)
A typical entry containing a handler declaration is shown in Example 22-1.
|
Example 22-1. A typical configuration section handler declaration
Despite appearances to the contrary, this
tag has only two attributes: name and type. The name is system.diagnostics. This name implies that somewhere else in the configuration file lies a configuration section called system.diagnostics. That configuration section contains the actual configuration settings, which typically are name/value pairs contained within XML elements, to be used by the application(s). It will be described in detail shortly.
The type attribute has a lengthy value enclosed in quotation marks. It contains:
Each handler need only be declared once, either in the base-level machine.config file or in an application configuration file further down the configuration hierarchy. The configuration section it refers to can then be specified as often as desired in other configuration files.
Example 22-2 shows a truncated version of the default machine.config.
|
Example 22-2. Truncated machine.config file
The first three declarations in machine.config are runtime, mscorlib, and startup. They are special because they are the only declarations that do not have corresponding configuration sections in the file.
In Example 22-2, many handler declarations are contained within tags, which allows nesting of elements. By loose convention, the name attribute of these tags corresponds to the namespace that contains the handlers. This groups all configuration sections that are handled out of the same namespace.
22.2.4 Configuration Sections
The configuration sections contain the actual configuration data. They each are contained within an element corresponding to the name of the section specified in the configuration section handler declaration. The following two configuration sections are equivalent:
and:
requestEncoding="utf-8" responseEncoding="utf-8"
Configuration sections typically contain name/value pairs that hold the configuration data. They may also contain subsections.
machine.config contains, at most, one configuration section for each handler declaration. (Not all handler declarations have a configuration section actually associated with it.) If the handler declaration is contained within a tag, then its corresponding configuration section will be contained within a tag containing the name of the . This can be seen in Example 22-2 for system.net.
The sections that follow provide a description of each configuration section relevant to Windows Forms contained in the default machine.config. Other configuration sections are outside the scope of this book, including system.net, system.web, and system.runtime.remoting.
22.2.4.1 appSettings
appSettings allow you to store application-wide name/value pairs for read-only access.
The handler declaration for appSettings, shown in Example 22-2 and reproduced here (minus the Culture and PublicKeyToken portions of the type attribute):
indicates that the NameValueFileSectionHandler class handles appSettings. This class provides name/value pair configuration handling for a specific configuration section.
As seen in Example 22-2, the appSettings section in the default machine.config file is commented out. If it were uncommented, any setting placed here would apply to every application on this machine. More typically, you would add an appSettings section to an application configuration file that would be in the directory of the application you wish to affect.
Example 22-3 shows an application configuration file for a specific application with an appSettings section added to provide two application-wide values. The appSettings section is not contained within any higher-level tag other than .
Example 22-3. Application configuration file containing appSettings section
value="server=YourSrvr; uid=YourID; pwd=YourPW; database=pubs" />
This appSettings section would be saved in a configuration file in the same directory as the executable for a given application, and these values could then be accessed anywhere within that application by referring to the static (shared, in VB.NET) AppSettings property of the System.Configuration.ConfigurationSettings class. This AppSettings property is of type NameValueCollection: give it the name of the key and it returns the value associated with that key.
For example, all the sample programs listed in Chapter 15 query the pubs database included with the default installations of SQL Server and Microsoft Access. Each program has the connection string hardcoded into the program, including the SQL Server user ID and password. This is a bad idea for two reasons:
Rather than hardcoding these connection strings, the application configuration file shown in Example 22-3 can be included in the application directory, or the highlighted code from Example 22-3 can be included in machine.config. In either case, your code could then refer to the appSettings in lieu of the actual connection string. In C#, you would replace this line of code:
string connectionString = "server=YourSrvr; uid=YourID; pwd=YourPW; database=pubs";
with this line:
string connectionString = ConfigurationSettings.AppSettings["PubsConnection"];
In VB.NET, you would replace this line of code:
dim connectionString as String = _ "server=YourSrvr; uid=YourID; pwd=YourPW; database=pubs"
with this line:
dim connectionString as String = _ ConfigurationSettings.AppSettings("PubsConnection")
|
The System.Configuration.ConfigurationSettings class is part of the System.Configuration namespace. Therefore, to use appSettings, you must reference that namespace in your code. Include the appropriate using or imports statement, respectively:
using System.Configuration;
imports System.Configuration
22.2.4.2 system.diagnostics
The system.diagnostics configuration section contains settings relating to the use of the System.Diagnostics Trace and Debug classes in your applications. These classes let you instrument your application for tracing and debugging without having to recompile the code.
To use tracing or debugging, compile the application with the appropriate flag. This process is discussed in Section 22.4.
The default system.diagnostics configuration section from machine.config is shown in Example 22-2; you can also add additional system.diagnostics sections to application configuration files.
The tags allow you to add switches and set their values, remove a specific switch, and clear all switches. The value of a switch can be tested using the properties of the TraceSwitch and BooleanSwitch classes, and depending on the result, appropriate action will be taken.
The tag in the default machine.config file has attributes to set the values of the AutoFlush and IndentSize properties of the Trace class:
22.2.4.3 system.net
The system.net configuration section contains subsections that deal with the .NET runtime. These subsections include authenticationModules, defaultProxy, connectionManagement, and webRequestModules. These subsections are outside the scope of this book.
22.2.4.4 system.web
The system.web configuration section contains subsections that configure ASP.NET. For a complete description of these sections, please refer to our book Programming ASP.NET (O'Reilly).
22.2.4.5 system.windows.forms
The default machine.config file contains only a single attribute in the system.windows.forms configuration section, and even then the entire single-line section is commented out. It is reproduced here from Example 22-2.
The jitDebugging attribute controls just-in-time debugging, which is disabled by default. Uncommenting this line and setting this value to true enables just-in-time debugging. When just-in-time debugging is enabled, a special dialog box appears when the program crashes, offering the user the opportunity to start one of the installed debuggers.
Visual Studio .NET controls just-in-time debugging independently of the configuration files. Click on Tools Options Debugging Just-In-Time. Two checkboxes enable just-in-time debugging for CLR programs and script programs. By default, these checkboxes are checked.
|
22.2.4.6 system.runtime.remoting
The system.runtime.remoting configuration section contains subsections that control remoting, which is the process of using an object residing in a different process or on a different machine. The details of this configuration section are beyond the scope of this book.
22.2.5 Custom Configuration
In addition to all the predefined configuration sections, you can add your own custom configuration sections. You might wish to add two different types of custom configuration sections:
22.2.5.1 Name/Value pairs
In Example 22-3, you added an tag to store the database connection string. Suppose you wanted to store connection strings for multiple databases, say one called Test (for testing purposes) and one called Content (to hold the production content). A custom configuration section returning a name/value pair would be one way to handle this situation.
The lines of code inserted into a configuration file to accomplish this task are shown in Example 22-4. Adding a custom configuration section that returns a name/value pair requires the following steps:
Adding the section to machine.config will make it available to every application on that machine. Adding it to an application configuration file will make the section visible only to that application.
Add the highlighted lines between the tags in Example 22-4 to the designated configuration file. If the file you are editing does not already have the opening XML declaration line, the tags, or the tags, then you will need to add them as well.
The PublicKeyToken is part of the strong name of the file that contains the NameValueFileSectionHandler class, System.dll, supplied as part of the .NET Framework. The value of the PublicKeyToken is copied from a reference to that file in machine.config. For a complete discussion of strong names, please refer to Section 22.3.7 later in this chapter.
Example 22-4. Custom sections in configuration files
type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/> value="SERVER=Server1;DATABASE=Test;UID=YourID;PWD=secret;" /> value="SERVER=Server2;DATABASE=Content;UID=YourID;PWD=secret;" />
|
Note that the type in the
tag is nearly the same as that provided for appSettings in the machine.config file. It specifies the NameValueSectionHandler class in the System.dll assembly file
|
To read the contents of this custom configuration section, use the GetConfig method from the ConfigurationSettings class, as demonstrated in Example 22-5 in C# and in Example 22-6 in VB.NET. These examples create a very simple form with two buttons: one labeled Test and one labeled Content. Clicking on either button displays the appropriate connection string from the configuration file listed in Example 22-4.
|
Example 22-5. Using custom configuration in C# (CustomConfig.cs)
using System; using System.Drawing; using System.Windows.Forms; using System.Configuration; //necessary for appSettings using System.Collections.Specialized; //necessary for NameValueCollection namespace ProgrammingWinApps { public class CustomConfig : Form { Button btnTest; Button btnContent; public CustomConfig( ) { Text = "Custom Configuration Demo"; Size = new Size(300,200); btnTest = new Button( ); btnTest.Parent = this; btnTest.Text = "Test DB"; btnTest.Location = new Point(100, 50); btnTest.Click += new System.EventHandler(btnTest_Click); btnContent = new Button( ); btnContent.Parent = this; btnContent.Text = "Content DB"; btnContent.Location = new Point(btnTest.Left, btnTest.Bottom + 25); btnContent.Click += new System.EventHandler(btnContent_Click); } // close for constructor static void Main( ) { Application.Run(new CustomConfig( )); } private void btnTest_Click(object sender, EventArgs e) { string strMSg = ((NameValueCollection) ConfigurationSettings.GetConfig("altDB"))["Test"]; MessageBox.Show(strMSg, "Test Connection String"); } private void btnContent_Click(object sender, EventArgs e) { string strMSg = ((NameValueCollection) ConfigurationSettings.GetConfig("altDB"))["Content"]; MessageBox.Show(strMSg, "Content Connection String"); } } // close for form class } // close form namespace
Example 22-6. Using custom configuration in VB.NET (CustomConfig.vb)
Option Strict On imports System imports System.Drawing imports System.Windows.Forms imports System.Configuration 'necessary for appSettings imports System.Collections.Specialized 'necessary for NameValueCollection namespace ProgrammingWinApps public class CustomConfig : inherits Form dim btnTest as Button dim btnContent as Button public sub New( ) Text = "Custom Configuration Demo" Size = new Size(300,200) btnTest = new Button( ) btnTest.Parent = me btnTest.Text = "Test DB" btnTest.Location = new Point(100, 50) AddHandler btnTest.Click, AddressOf btnTest_Click btnContent = new Button( ) btnContent.Parent = me btnContent.Text = "Content DB" btnContent.Location = new Point(btnTest.Left, _ btnTest.Bottom + 25) AddHandler btnContent.Click, AddressOf btnContent_Click end sub ' close for constructor public shared sub Main( ) Application.Run(new CustomConfig( )) end sub private sub btnTest_Click(ByVal sender as object, _ ByVal e as EventArgs) dim strMsg as string strMsg = CType(ConfigurationSettings.GetConfig("altDB"), _ NameValueCollection)("Test") MessageBox.Show(strMSg, "Test Connection String") end sub private sub btnContent_Click(ByVal sender as object, _ ByVal e as EventArgs) dim strMsg as string strMsg = CType(ConfigurationSettings.GetConfig("altDB"), _ NameValueCollection)("Content") MessageBox.Show(strMSg, "Content Connection String") end sub end class end namespace
The highlighted lines of code in Example 22-5 and Example 22-6 are the calls to the GetConfig method. They are different for VB.NET and C#, and a bit confusing in both.
The GetConfig method takes a configuration section name as a parameter and returns an object of type NameValueCollection. The desired value in the collection is retrieved by using the key as an offset into the collection, using the get property syntax. In VB.NET, a property is retrieved by enclosing the property name in parenthesis, and in C#, the property is retrieved by using square brackets.
In both languages, the code first casts the value returned by GetConfig to type NamedValueCollection. In C#, this is required since that language does not support late binding. In VB.NET, late binding is supported by default, but since Option Strict is turned On in the first line of code in the program (almost always a good idea), this VB.NET program also disallows late binding.
|
If Option Strict were not turned on in VB.NET, thereby allowing late binding, you could forego the cast by substituting the following line of code for the equivalent highlighted line in Example 22-6:
strMsg = ConfigurationSettings.GetConfig("altDB")("Test")
22.2.5.2 Objects
appSettings and custom configuration sections are very useful. However, they both suffer from the same limitation: they can return only a name/value pair. Sometimes returning an object would be very useful.
For example, suppose you have a standard query into a database. You could store the query string in an appSettings tag, then open a database connection after retrieving the string. However, it might be more convenient to store the query string in a configuration file and then have the configuration system return a DataSet directly.
To do this, add a
tag and a configuration section to the designated configuration file, as with the custom section returning name/value pairs, described in the previous section.
|
Edit the configuration file used in the previous example and shown in Example 22-4, adding the lines of code highlighted in Example 22-7.
Example 22-7. Returning objects from custom sections in a configuration file
type="ProgWinApps.Handlers.DataSetSectionHandler, vbSectionHandlers">
str="Select au_id,au_lname + ', ' + au_fname as name from authors" />
In a
section within the section, a handler declaration is created for the DataSetSectionHandler. This declaration specifies that elsewhere within the file, there will be a custom configuration section called DataSetSectionHandler. Furthermore, it specifies that the class handling that configuration section is called ProgWinApps.Handlers.DataSetSectionHandler, and that the class will be found in an assembly file called vbSectionHandlers.dll.
|
Further down in the configuration file, you will find a section called DataSetSectionHandler. It has a single attribute, str. This string contains the SQL statement you wish to pass to the database.
Next you must create the ProgWinApps.Handlers.DataSetSectionHandler class and place it in a source file called DataSetSectionHandler.vb, which will subsequently be compiled to vbSectionHandlers.dll. To do this, create a VB.NET source code file as shown in Example 22-8.
Example 22-8. Source code for section handler in VB.NET (DataSetSectionHandler.vb)
Imports System Imports System.Data Imports System.Data.SqlClient Imports System.XML Imports System.Configuration Namespace ProgWinApps.Handlers public class DataSetSectionHandler : _ Implements IConfigurationSectionHandler public Function Create(parent as Object, _ configContext as Object, _ section as XmlNode) as Object _ Implements IConfigurationSectionHandler.Create dim strSql as string strSql = section.Attributes.Item(0).Value dim connectionString as string = "server=YourServer; " & _ "uid=YourID; pwd=YourPassword; database=pubs" ' create the data set command object and the DataSet dim da as SqlDataAdapter = new SqlDataAdapter(strSql, _ connectionString) dim dsData as DataSet = new DataSet( ) ' fill the data set object da.Fill(dsData,"Authors") return dsData end Function end class end NameSpace
|
The database aspects of the code in this example are covered thoroughly in Chapter 19 and won't be discussed here in detail.
At the beginning of the Example 22-8 are several Imports statements (if written in C#, they would be using statements). Next, a namespace is declared to contain the class (to prevent ambiguity when calling the class).
For a class to be used as a configuration section handler, it must be derived from the IConfigurationSectionHandler interface. In VB.NET, this is implemented by using the Implements keyword. (In C#, this would be indicated with a colon between the class name and the inherited interface.)
|
The IConfigurationSectionHandler interface has only a single method, Create. Therefore, your implementing class must implement the Create method with the specified signature. The three parameters are dictated by the interface. The first two parameters are rarely used and will not be discussed further. The third parameter is the XML data from the configuration file.
The XML node is parsed and the value of the first item in the Attributes collection of the tag (the str attribute) is assigned to a string variable in this line:
strSql = section.Attributes.Item(0).Value
Once the SQL string is in hand, the connection string is coded, a SqlDataAdapter object is instantiated and executed, and the DataSet is filled. Then the DataSet is returned.
|
Before this class can be used, it must be compiled. Open a command prompt by clicking on the Start button, and then Programs Microsoft Visual Studio .NET 2003 Visual Studio .NET Tools Visual Studio .NET 2003 Command Prompt. Use the cd command to make current the directory that will contain the application. Then enter the following command line:
vbc /t:library /out:vbSectionHandlers.dll /r:system.dll,System.data.dll, System.xml.dll DataSetSectionHandler.vb
Chapter 2 explains how to use command-line compilers. Here the target type of the output is set to be library (a DLL). The name of the output file will be vbSectionHandlers.dll. Notice that three DLL files are referenced. The input source file is DataSetSectionHandler.vb. When the source file is compiled, you will have the output DLL in the current directory, where the classes it contains will be available to the application automatically.
|
The application shown in Example 22-9 (in VB.NET) shows how to use this configuration section. This is similar to the application seen in Example 15-17 in Chapter 15, modified to get the dataset from the configuration file.
Example 22-9. Section handler demonstration in VB.NET (ListBoxItems-CustomConfig.vb)
Option Strict On imports System imports System.Drawing imports System.Windows.Forms imports System.Data imports System.Data.SqlClient Imports System.Xml imports System.Collections ' necessary for ArrayList imports System.Configuration ' necessary for ConfigurationSettings namespace ProgrammingWinApps public class ListBoxItems : inherits Form dim lb as ListBox public sub New( ) Text = "ListBox Items Collection" Size = new Size(300,400) lb = new ListBox( ) lb.Parent = me lb.Location = new Point(10,10) lb.Size = new Size(ClientSize.Width - 20, Height - 200) lb.Anchor = AnchorStyles.Top or AnchorStyles.Left or _ AnchorStyles.Right or AnchorStyles.Bottom lb.BorderStyle = BorderStyle.Fixed3D ' get the data to populate the ListBox from pubs authors table dim ds as new DataSet( ) ds = CType(ConfigurationSettings.GetConfig( _ "DataSetSectionHandler"),DataSet) dim dt as new DataTable( ) dt = ds.Tables(0) dim arlstNames as new ArrayList( ) dim arNames( ) as object dim dr as DataRow lb.BeginUpdate( ) for each dr in dt.Rows lb.Items.Add( _ CType(dr("au_id"), string) + vbTab + _ CType(dr("name"), string)) next lb.Items.Add("12345" + vbTab + "Hurwitz, Dan") lb.Items.Add("67890" + vbTab + "Liberty, Jesse") lb.EndUpdate( ) end sub ' close for constructor public shared sub Main( ) Application.Run(new ListBoxItems( )) end sub end class end namespace
The lines of code in Example 22-9 that differ from the original version of the program in Example 15-17 in Chapter 15 are highlighted. An additional namespace, System.Configuration, is imported. Several lines of code from the original program, reproduced here:
dim connectionString as String = _ "server=YourServer; uid=sa; pwd=YourPassword; database=pubs" dim commandString as String = _ "Select au_id,au_lname + ', ' + au_fname as name from authors" dim dataAdapter as new SqlDataAdapter(commandString, _ connectionString) dim ds as new DataSet( ) dataAdapter.Fill(ds,"Authors")
are replaced with the highlighted lines from Example 22-9, reproduced here:
dim ds as new DataSet( ) ds = CType(ConfigurationSettings.GetConfig( _ "DataSetSectionHandler"),DataSet)
Rather than supply a connection string and SQL query string, a call is made to the static GetConfig method of the ConfigurationSettings class, which returns a DataSet object directly. Then the DataTable object is extracted from the DataSet. The parameter of the GetConfig method is a string containing the name of the section containing the configuration settings.
22.2.6 .NET Framework Configuration Tool (mscorcfg.msc)
This discussion about configuration has shown the actual configuration files that you can edit with any text editor or from within Visual Studio .NET. You can avoid some of the manual editing of standard config sections within configuration files by using the .NET Framework Configuration tool, mscorcfg.msc.
The .NET Framework Configuration tool, shown in Figure 22-2, is a Microsoft Management Console snap-in. It can be accessed by going to Control Panel and selecting Administrative Tools, and then Microsoft .NET Framework Configuration.
Figure 22-2. .NET Framework Configuration tool (mscorcfg.msc)
|
Most configuration handled by this tool involves assemblies.
Windows Forms and the .NET Framework
Getting Started
Visual Studio .NET
Events
Windows Forms
Dialog Boxes
Controls: The Base Class
Mouse Interaction
Text and Fonts
Drawing and GDI+
Labels and Buttons
Text Controls
Other Basic Controls
TreeView and ListView
List Controls
Date and Time Controls
Custom Controls
Menus and Bars
ADO.NET
Updating ADO.NET
Exceptions and Debugging
Configuration and Deployment