Configuration

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:

  • The XML files that control the configuration can be edited with any standard text editor or XML parser. It is not necessary to use special configuration tools, although the .NET Framework does include a graphical configuration tool.

  • Since the configuration is accomplished with text files, it is easy to administer remotely. Files can be created or edited on a development machine, and then copied into place via FTP or remote network access by anyone with security clearance.

  • The system is hierarchical. Each application inherits a baseline configuration from machine.config. The application configuration file then applies successive configuration attributes and parameters.

  • A corollary of the hierarchical nature of the system is that each application can have its own independent configuration. Applications do not need to share a machine-wide configuration.

  • The system is extensible. The baseline system provides configurability to a large number of standard program areas. In addition, you can add custom parameters, attributes, and section handlers, as required by your application.

  • The configuration settings for each application are computed at load time, using all the relevant hierarchical configuration files, if they exist. Changes made to any configuration files after the program begins execution will not take effect until the program is terminated and restarted.

    Tip

    This last point is distinctly different from the way that ASP.NET web applications are configured. ASP.NET automatically detects whether any configuration files anywhere in the hierarchy are modified, recomputes and re-caches the configuration settings accordingly, and transparently starts a new application domain. For a complete discussion of configuration of ASP.NET applications, see our book Programming ASP.NET (O'Reilly).

Hierarchical Configuration

The configuration system is hierarchical—configuration files are applied in successive order. Unlike many hierarchies, and unlike ASP.NET configuration, this hierarchy does not have any parallel branches—only 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.

Note

As discussed in Section 22.5, later in this chapter, Visual Studio .NET can build either a Debug or a Release version of an application (or a custom version). If you build a debug or release version in C#, VS.NET will output to a subdirectory of the project called binDebug or binRelease, respectively. (When using VB.NET, both Debug and Release versions of the output go to the bin directory.) Since the application configuration file must be in the same directory as the executable, and with the correct name corresponding to the name of the executable, you must manually create copies of the application configuration file and place one in each output directory.

To avoid this tedious requirement, create a single application configuration file named app.config and place it in the project's root directory. In Solution Explorer, add the file app.config to the project by right-clicking on the project, selecting Add Add New Item, and then selecting Application Configuration File. When the project is built, the configuration file will be correctly renamed automatically and copied to the output directory (such as Debug or Release).

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.

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

Camel casing means that you combine multiple words, with each word except the first capitalized. For example, you might have a tag named myNewTag.

Pascal casing is exactly like camel casing, except the first letter is uppercase as well: MyNewAttribute.

Note that not all attributes use Pascal casing:

  • true and false are always lowercase.

  • Literal strings do not adhere to either camel or Pascal casing — for example, a database connection string, may be specified as "SERVER=Zeus;DATABASE=Pubs;UID=sa;PWD=secret;".

  • If the value is the name of another tag in a configuration file, then it will be camel-cased.

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.

<?xml version="1.0" encoding="UTF-8" ?>

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.

Tip

This first line is an exception to the rule and does not have a corresponding closing tag.

The next line in the configuration files is the opening <configuration> tag:

<configuration>

The entire contents of the configuration file, except the initial XML declaration, is contained between the opening <configuration> tag and the closing </configuration> tag.

Comments can bec contained within the file by using the standard HTML comment:

<!-- Your comments here -->

Comments may be located anywhere within the file except prior to the first line that declares the XML file.

Within the <configuration> tags are two broad categories of entries. They are, in the order in which they appear in the configuration files:

  • Configuration Section Handler Declarations

  • Configuration Sections

Configuration Section Handler Declarations

The first part of the machine.config file consists of handler declarations that are contained between an opening <configSections> tag and a closing </configSections> 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.

Tip

This terminology can be very confusing. The first part of the file is enclosed in <configSections> tags, but contains only a list of the configuration sections and their handlers, not the configuration sections themselves. And as you will see shortly, the configuration sections are each contained within tags, but no grouping tag contains all the separate configuration sections, analogous to <configSections>.

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.

Tip

In the original machine.config file, the contents of Example 22-1 were all contained in a single line. That line is broken here only for readability.

Example 22-1. A typical configuration section handler declaration

<section name="system.diagnostics"
         type="System.Diagnostics.DiagnosticsConfigurationHandler, 
               System, 
               Version=1.0.5000.0, 
               Culture=neutral, 
               PublicKeyToken=b77a5c561934e089"/>

Despite appearances to the contrary, this <section> 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:

  • The class that handles the named configuration section

  • The assembly file (DLL) that contains that class

  • Version and culture information to coordinate with the assembly file

  • A public key token used to verify that the DLL being called is secure

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.

Tip

Only a small subset of the actual entries in machine.config are included in Example 22-2. Also, the type attribute of each entry was edited to remove all but the class, and lines have been broken to enhance the readability.

Example 22-2. Truncated machine.config file

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   
   <configSections>
      <section name="runtime" 
               type="System.Configuration.IgnoreSectionHandler"/> 
      <section name="mscorlib" 
               type="System.Configuration.IgnoreSectionHandler"/> 
      <section name="startup" 
               type="System.Configuration.IgnoreSectionHandler"/> 
      <section name="system.runtime.remoting" 
               type="System.Configuration.IgnoreSectionHandler"/> 
   
      <section name="system.diagnostics" 
               type="System.Diagnostics.DiagnosticsConfigurationHandler"/>
      <section name="appSettings" 
               type="System.Configuration.NameValueFileSectionHandler"/>
   
      <sectionGroup name="system.net">
         <section name="authenticationModules" 
          type="System.Net.Configuration.NetAuthenticationModuleHandler"/>
         <section name="defaultProxy" 
                  type="System.Net.Configuration.DefaultProxyHandler"/>
         <section name="webRequestModules" 
                 type="System.Net.Configuration.WebRequestModuleHandler"/>
      </sectionGroup>
   
      <section name="system.windows.forms" 
               type="System.Windows.Forms.WindowsFormsSectionHandler"/>
   </configSections>
   
   <!-- use this section to add application specific configuration 
        example: 
   <appSettings>
      <add key="XML File Name" value="myXmlFileName.xml" /> 
   </appSettings>
   -->
   
   <system.diagnostics>
      <switches>
          <!-- <add name="SwitchName" value="4"/>  -->
      </switches>
      <trace autoflush="false" indentsize="4"/>
   </system.diagnostics>
   
   <system.net>
      <defaultProxy>
         <proxy usesystemdefault="true"/>
      </defaultProxy>
      <webRequestModules>
         <add prefix="http" type="System.Net.HttpRequestCreator"/>
         <add prefix="https" type="System.Net.HttpRequestCreator"/>
         <add prefix="file" type="System.Net.FileWebRequestCreator"/>
      </webRequestModules>
   </system.net>
   
   <!-- <system.windows.forms jitDebugging="false" /> -->
   
   <system.runtime.remoting>
      <application>
         <channels>
            <channel ref="http client" 
                     displayName="http client (delay loaded)" 
                     delayLoadAsClientChannel="true"/>
            <channel ref="tcp client" 
                     displayName="tcp client (delay loaded)" 
                     delayLoadAsClientChannel="true"/> 
         </channels>
      </application>
   </system.runtime.remoting>
</configuration>

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 <sectionGroup> 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.

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:

<globalization requestEncoding="utf-8" responseEncoding="utf-8" />

and:

<globalization>
    requestEncoding="utf-8"
    responseEncoding="utf-8"
</globalization>

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 <sectionGroup> tag, then its corresponding configuration section will be contained within a tag containing the name of the <sectionGroup>. 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.

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):

<section name="appSettings"  
        type="System.Configuration.NameValueFileSectionHandler, System" />

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 <configuration>.

Example 22-3. Application configuration file containing appSettings section

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
   <appSettings>
                     <add key="PubsConnection" 
         value="server=YourSrvr; uid=YourID; pwd=YourPW; database=pubs" />
                     <add key="Title" value="Programming .NET Windows Applications" />
                     </appSettings>
</configuration>

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:

  • If either the server or the password is changed, you must locate and update all the code lines in all the applications that use that connection string, and then you must recompile.

  • Hardcoding the connection string makes it very unwieldy to do development against a test server and then to deploy the live application against a production server.

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:

image with no caption

string connectionString =
      "server=YourSrvr; uid=YourID; pwd=YourPW; database=pubs";

with this line:

image with no caption

string connectionString = ConfigurationSettings.AppSettings["PubsConnection"];

In VB.NET, you would replace this line of code:

image with no caption

dim connectionString as String = _
      "server=YourSrvr; uid=YourID; pwd=YourPW; database=pubs"

with this line:

image with no caption

dim connectionString as String = _
      ConfigurationSettings.AppSettings("PubsConnection")

Note

Any data included in a configuration file is in plain text, clearly visible to anyone who looks at the config file. Therefore, depending on the projected distribution of your application, it may be a bad idea to use appSettings to hold passwords, unless you encrypt the string in the config file and decrypt it in your application. (Of course, any encryption scheme is potentially breakable to someone with enough time and motivation.)

appSettings work well for secure storage of passwords in ASP.NET applications because the application is hosted on a remote server, and browsers — and hence, users — are blocked from retrieving the config files.

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:

image with no caption

using System.Configuration;

image with no caption

imports System.Configuration

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 <switches> 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 <trace> tag in the default machine.config file has attributes to set the values of the AutoFlush and IndentSize properties of the Trace class:

<trace autoflush="false" indentsize="4"/>

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.

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).

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.

<!-- <system.windows.forms jitDebugging="false" /> -->

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.

Tip

For a more complete discussion of JIT debugging, see Mastering Visual Studio .NET by Chris Sells, Jon Flanders, and Ian Griffiths (O'Reilly).

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.

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:

  • Sections that provide access to a collection of name/value pairs, similar to appSettings.

  • Sections that return any type of object.

Name/Value pairs

In Example 22-3, you added an <appSettings> 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:

  1. Determine which configuration file you will use to hold the custom section. This file determines the scope, or visibility, of the custom section.

    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.

  2. Declare the section handler by adding a line to the <configSections> section of the designated configuration file. This tells the CLR to expect a configuration section with the specified name and which class and assembly file to use to process the section.

    Add the highlighted lines between the <configSections> 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 <configuration> tags, or the <configSections> 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.

  3. Add the custom section itself to the configuration file. This section consists of the highlighted lines in Example 22-4 containing the <altDB> tags. This custom configuration section contains two entries, one named Test and the other named Content, each with its own value attribute.

Example 22-4. Custom sections in configuration files

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   <configSections>
      <section name="altDB"
               type="System.Configuration.NameValueSectionHandler, 
                     System, Version=1.0.5000.0, Culture=neutral, 
                     PublicKeyToken=b77a5c561934e089"/>
   </configSections>
   
   <altDB>
                     <add key="Test" 
           value="SERVER=Server1;DATABASE=Test;UID=YourID;PWD=secret;" />
                     <add key="Content" 
           value="SERVER=Server2;DATABASE=Content;UID=YourID;PWD=secret;" />
                     </altDB>
</configuration>

Tip

The lines of code in Example 22-4 are wrapped for readability. The configuration files are sensitive to new line characters, so it is safest to make each line contiguous.

Note that the type in the <section> 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

Tip

The appSettings section handler specified in the machine.config file refers to the NameValueFileSectionHandler class, while the altDB section handler created in Example 22-4 refers to the NameValueSectionHandler class, both of which are contained in System.dll. The classes seem interchangeable. Only the latter is documented in the SDK as being a member of the System.Configuration namespace hierarchy.

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.

Tip

Remember that if the code from Example 22-4 is contained in a stand-alone application configuration file rather than machine.config, then that configuration file must have the same name as the program executable with the added extension of .config. For example, if the C# version of the program is compiled to CustomConfigCS.exe, then the config file should be called CustomConfigCS.exe.config.

Example 22-5. Using custom configuration in C# (CustomConfig.cs)

image with no caption

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)

image with no caption

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.

Tip

In Visual Studio .NET, the default for Option Strict is Off. You can change this default by right-clicking on the project in the Solution Explorer and selecting Properties to view the Property Pages (not to be confused with the Properties window). Then go to Common Properties Build. There are drop-down menus for Option Explicit, Option Strict, and Option Compare. You can also change these for all projects by clicking on Tools Options Projects VB Defaults and setting the values in the drop-down menus.

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:

image with no caption

strMsg = ConfigurationSettings.GetConfig("altDB")("Test")

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 <section> tag and a configuration section to the designated configuration file, as with the custom section returning name/value pairs, described in the previous section.

Tip

This example is presented only in VB.NET. The C# implementation is essentially the same, with only language syntax differences.

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

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
   
   <configSections>
      <section name="altDB" 
               type="System.Configuration.NameValueSectionHandler, 
                     System, Version=1.0.3300.0, Culture=neutral,
                     PublicKeyToken=b77a5c561934e089"/>
   
      <section name="DataSetSectionHandler" 
               type="ProgWinApps.Handlers.DataSetSectionHandler,
                     vbSectionHandlers">
                     </section>
   </configSections>
   
   <altDB>
      <add key="Test" 
           value="SERVER=Server1;DATABASE=Test;UID=sa;PWD=secret;" />
      <add key="Content" 
           value="SERVER=Server2;DATABASE=Content;UID=sa;PWD=secret;" />
   </altDB>
   
   <!— Custom config section returning an object —>
                     <DataSetSectionHandler  
                     str="Select au_id,au_lname + ', ' + au_fname as name from authors" />
   
</configuration>

In a <section> section within the <configSections> 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.

Tip

The two <section> sections in Example 22-7 differ in that one is self-closing while the other has a closing tag. They are equivalent and you can choose the one you prefer. As a rule, the self-closing tag is self-documenting in that you intend the element to have no contents (nothing between the open and close tags) while the paired tags indicate the opposite. If the element has children, of course, you have no choice but to use a pair of tags.

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)

image with no caption

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

Tip

Be sure to set the connection string to match your specific database.

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.)

Tip

A full discussion of the object-oriented concepts such as inheritance, base classes, and interfaces is beyond the scope of this book. For now, you remember that an interface acts as a contract that the implementing class must fulfill. The interface may, for example, dictate the signature of methods the implementing class must implement, or it may dictate which properties the class must provide. For a complete discussion of object-oriented concepts, see Programming C# or Programming Visual Basic .NET, both by Jesse Liberty (O'Reilly).

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 <DataSetSectionHandler> tag (the str attribute) is assigned to a string variable in this line:

image with no caption

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.

Tip

You could have also obtained the connection string in Example 22-8 from the configuration file, as demonstrated in Example 22-3 and Example 22-4. Leaving the connection string in this class (which will be compiled) has the benefit of hiding it from prying eyes a bit more securely (although nothing is totally secure from dedicated snoops), but it sacrifices the advantages of configuration files mentioned in previous sections.

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.

Tip

Rather than creating this DataSetSectionHandler class as a separate source code file and DLL, you could embed the class directly in the application that uses it, obviating the need for the compilation step. Doing so, however, prevents you from reusing the code in multiple applications.

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)

image with no caption

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:

image with no caption

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:

image with no caption

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.

.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.

.NET Framework Configuration tool (mscorcfg.msc)

Figure 22-2. .NET Framework Configuration tool (mscorcfg.msc)

Note

Having the .NET Configuration Tool available from within Visual Studio .NET is often convenient. It is easy to add it to the Tools menu. In Visual Studio .NET, click on Tools External Tools. Click on the Add button of the External Tools dialog box. Change the default tool name from [New Tool 1] to .NET Config Tool. In the Command field, enter mmc.exe. In the Arguments field, enter:

c:windowsmicrosoft.netframeworkversion numbermscorcfg.msc.

where version number is the version directory on your machine.

Most configuration handled by this tool involves assemblies.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.172.160