Chapter 36. Configuration Files

WHAT'S IN THIS CHAPTER?

  • Understanding the .NET configuration system

  • Using configuration files within your application

  • Storing custom types in configuration files

One of the challenges of building applications is adjusting the way the application functions on the fly without having to rebuild it. There's a long history of applications using configuration files to control the way an application runs. .NET applications use a series of XML configuration files that can be adjusted to determine application behavior. This chapter explores the structure of these configuration files and demonstrates how you can store custom information using a configuration section handler.

.CONFIG FILES

The .NET Framework configuration system consists of several configuration files (discussed in the following sections) that can be used to adjust one or more applications on a computer system. Part of this system is an inheritance model that ensures that configurations can be applied at the appropriate level. This model is such that sections defined in a configuration file at a lower level will override the same sections specified in a file higher up the chain. If no configuration file defines a value or section, the default values are taken from the schema files to which the configuration files must adhere.

Machine.Config

At the root of the inheritance model is the machine.config file (located in the systemrootMicrosoft .NETFrameworkversionNumberCONFIG folder, or systemrootMicrosoft .NET Framework64versionNumberCONFIG for 64-bit machines), which defines configuration settings for the entire system. All configuration files inherit from this file and can override these settings.

Web.Config

Web applications are configured via the web.config file. This file can be located in a number of locations, depending on the scope to which the settings need to be applied. To apply a configuration to all web applications on a machine, place the web.config file in the same directory as the machine.config file. In most cases the settings need to be applied at a much finer granularity. As such, the web.config file can also be placed in any virtual directory or subdirectory to control web applications at that level. If it is placed in the root folder for a web site, the configuration will be applied to all ASP.NET applications in that web site.

A word of caution: When you are working with virtual directories that do not align with the directory structure on the computer, it's possible to have an application that has different configurations depending on how it is referenced. For example, consider C:inetpubwwwrootMainApplicationContactsContact.aspx, which has been set up with both MainApplication and Contacts as virtual directories. You can reference the contact page as either:

http://localhost/MainApplication/Contacts/Contact.aspx

or:

http://localhost/Contacts/Contact.aspx

In the first case, the configuration settings that are applied are inherited from the MainApplication folder and may be overridden by a configuration file in the Contacts folder. However, in the second case, settings are applied only from the configuration file within the Contacts folder.

Note

Making changes to a web.config file causes the ASP.NET application to be restarted. This is quite an effective way to force a web application to flush its cache and behave as if it were being accessed for the first time, without having to restart the entire server.

App.Config

Windows applications can be configured via an application configuration file, which also inherits from machine.config. Because the output assembly name is known only when an application is compiled, this file starts off as app.config and is renamed to application.exe.config as part of the build process. For example, an application with AccountingApplication.exe as the main executable would have a configuration file entitled AccountingApplication.exe.config. This configuration file is automatically loaded based on its name when the application is loaded. If an app.config files is added to a dll, it will be renamed to assembly.dll.config during the build process.

Security.Config

In conjunction with the application configuration files are a number of security configuration files. These also follow an inheritance path but across a different dimension. Instead of being application-focused, the security configuration files are broken down into enterprise (Enterprisesec.config), machine (Security.config), and user (Security.config). The enterprise- and machine-level files are both stored in the same location as the machine.config file, whereas the user-level file is stored under the user-specific application data folder.

ApplicationHost.Config

IIS7 changes the way configuration information is stored to use a set of configuration files that work in parallel with those for ASP.NET and the .NET Framework. Because IIS and the .NET Framework are versioned independently, configuration information specific to the individual technologies are held in the machine.config/web.config and the applicationHost.config files, respectively. However, because there is an interrelationship between IIS and ASP.NET, the applicationHost.config file does fit into the configuration file inheritance hierarchy. Because the applicationHost.config file is specific to an instance of IIS, it fits into the inheritance hierarchy after both the machine.config and web.config files located at the machine level (that is, located in the systemrootMicrosoft .NETFrameworkversionNumberCONFIG folder).

The applicationHost.config file can be found in the systemrootSystem32InetSrvConfig folder, and the corresponding schema files can be found in the Schema subdirectory. There are also administration.config and redirection.config files in this folder that are responsible for IIS feature delegation and configuration file redirection, respectively.

CONFIGURATION SCHEMA

A configuration file, whether it is a machine.config, a web.config, or an application configuration file, needs to adhere to the same configuration schema that determines which elements should be included. The schema is located at C:Program FilesMicrosoft Visual Studio 10.0XmlSchemasDotNetConfig.xsd (C:Program Files (x86)Microsoft Visual Studio 10.0XmlSchemasDotNetConfig.xsd on 64-bit machines) and is broken down into a number of sections.

Section: configurationSections

Configuration files can be customized to contain any structured XML data. In order to do this, you must define a custom section in the configurationSections block within the configuration file. This defines both the name of the configuration section and the class that is to be called in order to process the section.

The configurationSections section in the machine.config file defines the handlers for each of the standard configuration sections discussed here. You can define your own configuration sections in your application configuration file so long as you specify which class will be used to validate and process that section. For example, the following code snippet defines the section handler for the ConfigurationApplication.My.MySettings configuration section, along with the corresponding section. The schema of this section must correspond to what the System.Configuration.ClientSettingsSection class expects, rather than the normal configuration file schema.

<configuration>
<configSections>
<section name="ConfigurationApplication.My.MySettings"
                 type="System.Configuration.ClientSettingsSection,
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
                 requirePermission="false" />
</configSections>
.
<ConfigurationApplication.My.MySettings>
<setting name="PrimaryServer" serializeAs="String">
<value>www.builttoroam.com</value>
</setting>
</ConfigurationApplication.My.MySettings>
</configuration>

It is also possible to include configSections in a sectionGroup element that can be used to help lay out configuration information. The preceding example can be extended as follows:

<configuration>
<configSections>
<sectionGroup name="applicationSettings"
                      type="System.Configuration.ApplicationSettingsGroup,
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken= b77a5c561934e089" >
<section name="ConfigurationApplication.My.MySettings"
                     type="System.Configuration.ClientSettingsSection,
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
                     requirePermission="false" />
<section name="ReferencedAssembly.My.MySettings"
                     type="System.Configuration.ClientSettingsSection,
System, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
                     requirePermission="false" />
</sectionGroup>
</configSections>
.
<applicationSettings>
<ConfigurationApplication.My.MySettings>
<setting name="PrimaryServer" serializeAs="String">
<value>www.builttoroam.com</value>
</setting>
</ConfigurationApplication.My.MySettings>
<ReferencedAssembly.My.MySettings>
<setting name="SecondaryServer" serializeAs="String">
<value>www.peaksite.com</value>
</setting>
</ReferencedAssembly.My.MySettings>
</applicationSettings>
</configuration>

Where used, the configSections element must appear as the first child of the configuration element.

Section: startup

The startup configuration section determines the version of the framework that is either required (requiredRuntime) or supported (supportedRuntime) by the framework. By default, a .NET application will attempt to execute using the same version of the framework on which it was built. Any application being built with support for multiple versions of the framework should indicate this with the supportedRuntime element, defining the most preferred framework version first:

<configuration>
<startup>
<supportedRuntime version="v4.0.20409"/>
<supportedRuntime version="v2.0.50727"/>
<supportedRuntime version="v1.1.4322"/>
</startup>
</configuration>

This configuration section would be used by an application that has been tested for versions 4.0, 2.0, and 1.1 of the .NET Framework. Anomalies were detected in the testing for version 1.0 of the .NET Framework, so it has been omitted from the supportedRuntime list. The version number must correspond exactly to the installation directory for that framework version (for example, version 4.0 of the .NET Framework typically installs to C:WINDOWSMicrosoft.NETFrameworkv4.0.20409).

Section: runtime

Garbage collection is a feature of the .NET Framework that distinguishes it from non-managed environments. The process of collecting and disposing of unreferenced objects is usually done in parallel with the main application on a separate thread. This means that the user should not see any performance issues as a result of this process being run. However, there may be circumstances when this process should be run inline with the main application. The runtime section of the configuration file can be used to provide limited control over how the .NET runtime engine operates. Among other things, you can specify whether the garbage collection should be done concurrently with the main application.

This section can also be used to specify a location in which to search for assemblies that may be required by an application. This attribute can be useful if an application references assemblies that are in a non-standard location. The following code illustrates the use of the codeBase attribute to locate the ImportantAssembly.dll, as well as to dictate that garbage collection be done inline with the main application thread:

<configuration>
<runtime>
<assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
<dependentAssembly>
<assemblyIdentity name="ImportantAssembly"
                              publicKeyToken="32ab4ba45e0a69a1"
                              culture="neutral" />
<codeBase version="2.0.0.0" href="./ImportantAssembly.dll"/>
</dependentAssembly>
</assemblyBinding>
<gcConcurrent enabled="false"/>
</runtime>
</configuration>

Section: system.runtime.remoting

The remoting section of the configuration file can be used to specify information about remote objects and channels required by the application. For example, the default HTTP channel can be directed to listen to port 8080 by means of the following configuration snippet:

<configuration>
<system.runtime.remoting>
<application>
<channels>
<channel port="8080" ref="http"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>

Section: system.net

Because of the current demand for more secure operating environments, organizations often use proxies to monitor and protect traffic on their networks. This can often result in applications not functioning correctly unless they have been configured to use the appropriate proxies. The networking section of the configuration files can be used to adjust the proxy that an application uses when making HTTP requests.

The .NET Framework ships with an SmtpClient class that can be used to send mail from within an application. Obviously, doing this requires information such as the server and the credentials to use when sending mail. Although such information can be hard-coded within an application, a more flexible approach would be to specify it in a configuration file that can be adjusted when the application is deployed. The following configuration snippet illustrates the use of the default proxy (although it bypasses the proxy for local addresses and the DeveloperNews web site) and specifies the default SMTP settings to be used by the SMTP client:

<configuration>
<system.net>
<defaultProxy>
<proxy usesystemdefaults="true"
                   proxyaddress="http://192.168.200.222:3030"
                   bypassonlocal="true" />
<bypasslist>
<add address="[a-z]+.developernews.com" />
</bypasslist>
</defaultProxy>
<mailSettings>
<smtp deliveryMethod="network">
<network host="smtp.developernews.com"
   port="25" defaultCredentials="true" />
</smtp>
</mailSettings>
</system.net>
</configuration>

Section: cryptographySettings

Although the .NET Framework contains base implementations for a number of cryptographic algorithms, such as the hashing function, sometimes it is necessary to override these algorithms. When this is required, the cryptographySettings section of the configuration file can be included to remap existing algorithm names, or map new names, to another implementation class.

Section: system.diagnostics

Debugging is always the hardest part of writing an application. It is made even more difficult when the application is in production and the error cannot be replicated in the debugging environment. One technique that is particularly important for debugging this type of error is to use trace statements:

Trace.WriteLine("The application made it this far before crashing.")

Both trace and debug statements work very similarly to events and event handlers. For the preceding WriteLine statement to have any effect, an object must be listening for this WriteLine. This is typically done by a TraceListener class. The framework supports a number of default trace listeners that can be wired up to the application via the diagnostics section of the configuration file, as shown in the following section in which an EventLog trace listener has been attached to the application:

<configuration>
<system.diagnostics>
<trace autoflush="true" indentsize="0">
<listeners>
<add name="MyEventListener"
type="System.Diagnostics.EventLogTraceListener, system,
version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
initializeData="DeveloperApplicationEventLog"/>
</listeners>
</trace>
</system.diagnostics>
</configuration>

The initializeData attribute specifies a text string to be passed into the constructor for the trace listener. In the case of the event-log listener, this text corresponds to the name of the event log into which trace statements will be inserted.

Other elements can also be added to the diagnostics section of the configuration file — for example, to determine the level of trace logging to perform, which will determine how verbose the trace messages are; or to control whether or not the debug assertion dialog is displayed for an application.

Section: system.web

The system.web section of the configuration file is used to control how web applications behave. This is the section that can have quite a deep hierarchy, because configuration settings can be specified on a machine, web server, web site, web application, or even subfolder basis. Because this section controls the security requirements for a web application, it is often used to restrict access to certain areas of the web application.

webServices

Although web service applications use several configuration settings, such as authentication and impersonation sections, the system.web section of the configuration file contains some settings that are particular to the way that web services operate. For example, the following code snippet enables the use of SOAP and Documentation protocols, but removes the POST and GET protocols for the application:

<configuration>
<system.web>
<webServices>
<protocols>
<add name="HttpSoap"/>
<remove name="HttpPost"/>
<remove name="HttpGet"/>
<add name="Documentation"/>
</protocols>
</webServices>
</system.web>
</configuration>

By default, only SOAP and Documentation are enabled for web services. Quite often, for debugging purposes, it is convenient to allow the POST protocol so that the web service can be tested via a web browser. You should do this on an application basis by including the appropriate section in the configuration file within the application folder.

Section: compiler

The compiler section of the configuration file is used to list the compilers installed on a computer. The following snippet shows how the VB.NET compiler is referenced in the machine.config file. Within an application, this information can be accessed via the CodeDomProvider framework class.

<configuration>
<system.codedom>
<compilers>
<compiler language="vb;vbs;visualbasic;vbscript" extension=".vb"
type="Microsoft.VisualBasic.VBCodeProvider, System, Version=2.0.0.0,
Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</compilers>
</system.codedom>
</configuration>

Configuration Attributes

All configuration elements can specify a configSource, which is simply a redirection to a separate file. This can be useful if a configuration file becomes unwieldy in length. The following code snippet illustrates how a section of a configuration file can be extracted and subsequently referenced by means of this attribute:

<!—Original Configuration File—>
<configuration>
.
<WindowsApplication1.My.MySettings>
<setting name="Button1_Text" serializeAs="String">
<value>Press Me!</value>
</setting>
</WindowsApplication1.My.MySettings>
</configuration>

<!—Reduced Configuration File using configSource—>
<configuration>
.
<WindowsApplication1.My.MySettings configSource="MySettings.Config" />
</configuration>

<!—Code from MySettings.Config—>
<WindowsApplication1.My.MySettings>
<setting name="Button1_Text" serializeAs="String">
<value>Press Me!</value>
</setting>
</WindowsApplication1.My.MySettings>

Note a couple of limitations to using a configSource:

  • There is no merging of configuration sections between the referenced file and the original configuration file. If you include the section in both files, a configuration error will be generated when you attempt to run the application.

  • This attribute cannot be applied to configuration section groups. This can be a significant limitation, because the purpose of a section group is to group items that relate similar configuration sections. A logical separation could see all items in a particular section group in a separate configuration file.

  • If the attribute is used within a web.config file, changing the referenced configuration file will not restart the ASP.NET application. In order for the configuration information to be reread, you need to either manually restart the ASP.NET application or modify the web.config file itself.

Each element within the configuration file inherits a number of attributes that can be set to control whether or not that element can be overridden. To prevent an element, or even an entire section, from being overridden, you can lock it. Five different locking attributes (outlined in Table 36-1) can be used to specify any number of configuration attributes and elements that are to be locked.

Being able to lock configuration items is particularly relevant when you're dealing with web applications, which might contain a deep hierarchy of configuration inheritance. Windows applications inherit only from the machine.config file, so it is unlikely that you will need to lock items.

Table 36.1. Locking Attributes

CONFIGURATION ELEMENT

DESCRIPTION

LockItem

Locks the element to which this attribute is applied, including all other attributes provided on that element and all child elements

LockAttributes

Locks the comma-delimited list of attributes provided

LockAllAttributesExcept

Locks all attributes except those provided in the comma-delimited list

LockElements

Locks the comma-delimited list of child elements provided

LockAllElementsExcept

Locks all child elements except those provided in the comma-delimited list

APPLICATION SETTINGS

Applications frequently have settings that do not fit into the default configuration schema. The four mechanisms for storing this information are discussed in the following sections.

Using appSettings

The first technique is to use the predefined appSettings section of the configuration file. This section can be used to store simple name-value pairs of application settings, which might be useful for storing the name of the server, as in the following example:

<configuration>
<appSettings>
<add key="Server" value="http://www.builttoroam.com"/>
</appSettings>
</configuration>

This value can easily be accessed within code by means of the AppSettings property of the ConfigurationManager class (which requires a reference to the System.Configuration assembly):

VB
Dim server As String = ConfigurationManager.AppSettings("Server")
C#
var server = ConfigurationManager.AppSettings["Server"];

One of the weaknesses of this approach is that the name of the setting is specified as a string, rather than as a strongly typed property. It also assumes that the value will be a string, which is often not the case.

Note

In the case of web applications, you should use the WebConfigurationManager class instead of the ConfigurationManager class because it provides access to additional configuration information specific to ASP.NET applications.

Project Settings

Using the Settings tab of the project properties designer, you can define application settings of a variety of types. Figure 36-1 illustrates how the PrimaryServer setting would appear in this designer.

Figure 36-1

Figure 36-1. Figure 36-1

Adding application settings via this designer does not use the appSettings section as you might expect. Instead, it defines a new section in the configuration, as discussed earlier in the section on the configSection element and shown in the following snippet:

<configuration>
...
<ConfigurationApplication.My.MySettings>
<setting name="PrimaryServer" serializeAs="String">
<value>www.builttoroam.com</value>
</setting>
</ConfigurationApplication.My.MySettings>
</configuration>

To access this setting in code, you can make use of the generated strongly typed access properties.

VB
Dim primaryServer as String = My.Settings.PrimaryServer
C#
string primaryServer = Properties.Settings.Default.PrimaryServer;

Dynamic Properties

The third mechanism for storing application-specific information is the use of dynamic properties. These are typically used to dynamically set designer properties. For example, you could set the text on a Button1 using the following configuration block:

<configuration>
...
<applicationSettings>
<ConfigurationApplication.My.MySettings>
<setting name="Button1_Text" serializeAs="String">
<value>Press Me Now!</value>
</setting>
</ConfigurationApplication.My.MySettings>
</applicationSettings>
</configuration>

You will note that the preceding code uses the same syntax as application settings defined using the project properties designer. In fact, they are one and the same, the only difference being that in the InitializeComponent method of the form there is a line of code that sets the button text:

VB
Me.Button1.Size =
             Global.ConfigurationApplication.My.MySettings.Default.Button1_Size
C#
this.button1.Size =
             global::ConfigurationApplication.Properties.Settings.
             Default.Button1_Size;

When this application is deployed, the text displayed on Button1 is dynamically loaded from the configuration file. In the following steps, for example, you set the size of a control, Button1, to be dynamically loaded from the configuration file:

  1. Select Button1 on the designer surface and press F4 to display the Properties window. Locate the ApplicationSettings item within the Data category or in the alphabetic list, as shown in Figure 36-2.

  2. Click the ellipsis button (. . .) next to the PropertyBinding row. This opens a dialog that lists the available properties for Button1, along with any application settings that have been assigned, as shown in Figure 36-3.

    Figure 36-2

    Figure 36-2. Figure 36-2

    Figure 36-3

    Figure 36-3. Figure 36-3

  3. Select the drop-down next to the Size property and select New. This opens a dialog in which you can specify a default value, a name for the application setting, and the scope of the setting.

    Figure 36-4

    Figure 36-4. Figure 36-4

  4. Specify a name for the application setting — for example, Button1_Size, and set the scope to Application. You can modify the default value or simply accept the value that has been extracted from the current properties of Button1, as shown in Figure 36-4.

  5. Click OK on both dialogs. If you open the app.config file that will be available from the Solution Explorer window, you will see a section that defines the Button1_Size setting.

Custom Configuration Sections

Developers often want to include more structured information in the configuration file than can be stored in the appSettings section. To solve this problem and eliminate any need for additional configuration files, you can create a custom configuration section. The new configuration section must be defined at the top of the configuration file via the configSection element, complete with a reference to a class that should be used to process that portion of the configuration file.

In the past this process was fairly complex, because the class needed to implement the IConfigurationSectionHandler interface. This exposed a simple method, Create, which was called the first time that section was referenced in code. There was little support from the framework to process the section, and a class implementing this interface often resorted to parsing the XML block to determine settings.

Visual Studio 2010 provides much better support for creating custom configuration sections via the ConfigurationSection and ConfigurationElement classes. These provide the bases for creating classes that map to the structure of the data being stored in the configuration files. Instead of mapping a class that processes the configuration section, you can now create a much simpler class that maps to the section. When the section is referenced in code, an instance of this class is returned with the appropriate data elements set. All the XML processing that would have been necessary in the past is now handled by the .NET Framework.

Although this mapping makes the process of writing a custom configuration section much easier, you may sometimes want more control over how the section is read. Two options can be used to give you this control:

  • The first option is to go back to using a configuration section handler and manually process the XML file. This can be useful if the original XML representation is required. However, it still requires that the XML file be processed.

  • The second strategy is to create an appropriate mapping class as an in-between measure. Instead of referencing this class directly, another class can be generated that exposes the configuration information in the right way.

If you need to use either of these options, it might be worth taking a step back and determining whether the configuration section structure is actually in a format suited to the data being stored.

In the following example your application requires a list of registered entities with which to work. One type of entity is a company, and you need to be provided with both the company name and the date on which it was registered. The XML snippet that you would like to have in the configuration file might look like the following:

<RegisteredEntities>
<Companies>
<add CompanyName="Random Inc" RegisteredDate="31/1/2005" />
<add CompanyName="Developer Experience Inc" RegisteredDate="1/8/2004" />
</Companies>
</RegisteredEntities>

Once generated, the corresponding classes that would map to the preceding snippet might look like the following (again, this requires a reference to the System.Configuration assembly):

VB
Public Class RegisteredEntities
    Inherits ConfigurationSection

<ConfigurationProperty("Companies")>  _
    Public ReadOnly Property Companies() As Companies
        Get
            Return CType(MyBase.Item("Companies"),Companies)
        End Get
    End Property
End Class

<ConfigurationCollectionAttribute(GetType(Company))>  _
Public Class Companies
    Inherits ConfigurationElementCollection

    Protected Overrides Function CreateNewElement() As ConfigurationElement
        Return New Company
End Function

    Protected Overrides Function GetElementKey _
                           (ByVal element As ConfigurationElement) As Object
        Return CType(element, Company).CompanyName
    End Function

    Public Sub Add(ByVal element As Company)
        Me.BaseAdd(element)
    End Sub

End Class

Public Class Company
    Inherits ConfigurationElement

<ConfigurationProperty("CompanyName",DefaultValue:="Random Inc",
IsKey:=true, IsRequired:=true)>  _
    Public Property CompanyName() As String
        Get
            Return CType(MyBase.Item("CompanyName"),String)
        End Get
        Set
            MyBase.Item("CompanyName") = value
        End Set
    End Property

<ConfigurationProperty("RegisteredDate",DefaultValue:="31/1/2005",
IsKey:=false, IsRequired:=false)>  _
    Public Property RegisteredDate() As String
        Get
            Return CType(MyBase.Item("RegisteredDate"),String)
        End Get
        Set
            MyBase.Item("RegisteredDate") = value
        End Set
    End Property
End Class

Custom Configuration Sections
C#
class RegisteredEntities : ConfigurationSection{
    [ConfigurationProperty("Companies")]
    public Companies Companies{
        get{
            return base["Companies"] as Companies;
        }
    }
}

[ConfigurationCollection(typeof(Company))]
class Companies : ConfigurationElementCollection{
protected override ConfigurationElement CreateNewElement(){
        return new Company();
    }

    protected override object GetElementKey
           (ConfigurationElement element){
        return (element as Company).CompanyName;
    }

    public void Add(Company element){
        BaseAdd(element);
    }
}

class Company : ConfigurationElement{
    [ConfigurationProperty("CompanyName", DefaultValue = "Random Inc",
                           IsKey = true, IsRequired = true)]
    public string CompanyName{
        get{
            return base["CompanyName"] as string;
        }
        set{
            base["CompanyName"] = value;
        }
    }

    [ConfigurationProperty("RegisteredDate", DefaultValue = "31/1/2005",
                           IsKey = false, IsRequired = true)]
    public string RegisteredDate{
        get{
            return base["RegisteredDate"] as string;
        }
        set{
            base["RegisteredDate"] = value;
        }
    }
}

Custom Configuration Sections

The code contains three classes that are required in order to correctly map the functionality of this section. The registered entities section corresponds to the RegisteredEntities class, which contains a single property that returns a company collection. A collection is required here because you want to be able to support the addition of multiple companies. This functionality could be extended to clear and/or remove companies, which might be useful if you had a web application for which you needed to control which companies were available to different portions of the application. Lastly, there is the Company class that maps to the individual company information being added.

To access this section from within the code, you can simply call the appropriate section using the configurationManager framework class:

VB
Dim registered as RegisteredEntities= _
    ctype(configurationmanager.GetSection("RegisteredEntities"),RegisteredEntities)
C#
var registered =
      ConfigurationManager.GetSection("RegisteredEntities") as RegisteredEntities;

Note

In order for the .NET configuration system to correctly load your configuration file with the RegisteredEntities section you will also need to register this section in the configSections. You can do this by adding <section name="RegisteredEntities" type="ConfigurationApplication.RegisteredEntities, ConfigurationApplication, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/> to the configSections immediately before the </configSections> tag.

Automation Using SCDL

You just saw how custom configuration sections can be written and mapped to classes. Although this is a huge improvement over writing section handlers, it is still a fairly laborious process that is prone to error. Furthermore, debugging the configuration sections is nearly impossible because it's difficult to track what's going wrong.

As part of another project to support ASP.NET developers, a development manager for the ASP.NET team at Microsoft recognized that the process of creating these mapping classes was mundane and could easily be automated. To this end, he created a small application entitled SCDL (http://blogs.msdn.com/dmitryr/archive/2005/12/07/501365.aspx) that could take a snippet of configuration data, such as the RegisteredEntities section discussed previously, and output both the mapping classes and a schema file that represented the section supplied. Once generated, this code can be included in the application. Furthermore, if the snippet of configuration data is to be included as a non-compiled file within the solution, it is possible to automate the generation of the mapping classes via a pre-build batch command. If changes need to be made to the structure of the section, they can be made in the snippet. That way, the next time the solution is built the mapping classes will be updated automatically.

IntelliSense

Even after you get the custom configuration sections correctly mapped, there is still no support provided by Visual Studio 2010 for adding the custom section to the configuration file. Unlike the rest of the configuration file, which has support for IntelliSense and will report validation issues, your custom section will not be able to be validated.

To get IntelliSense and validation for your custom configuration section, you need to indicate the structure of the configuration section to Visual Studio 2010. You can do this by placing an appropriate schema (as generated by the SCDL tool) in the XML Schemas folder, which is usually located at C:Program FilesMicrosoft Visual Studio 10.0XmlSchemas. Unfortunately, this is where it gets a little bit more complex, because it is not enough to place the file in that folder; you also need to tell it that the schema should be included in the catalog used for parsing configuration files. To register your schema, follow these steps:

  1. Generate your schema file from your configuration snippet:

    Scdl.exe snippet.scdl snippet.vb snippet.xsd
  2. Copy the schema file (in this case, snippet.xsd) to the schema folder.

  3. Create a new text file called Config.xsd and include the following lines. Note that if your schema is called something different, you should update these lines appropriately. You may also add additional lines to include more than one schema. Do not remove the DotNetConfig.xsd line because that will remove validation for the standard configuration sections.

    <?xml version="1.0" encoding="utf-8" ?>
    <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
    <xs:include schemaLocation="DotNetConfig.xsd"/>
    <xs:include schemaLocation="snippet.xsd"/>
    </xs:schema>
  4. Open Catalog.xml in a text editor and replace DotNetConfig.xsd with Config.xsd. This effectively remaps the validation, and IntelliSense, for configuration files to use Config.xsd instead of DotNetConfig.xsd. However, because this file sources both DotNetConfig.xsd and your schema information, you will get validation for both your configuration section and the standard configuration sections.

USER SETTINGS

Because configuration files are commonly used to store settings that control how an application runs, it is often necessary to be able to dynamically change these to suit the way an individual uses the application. Rather than having to build an entirely different framework for accessing and saving these settings, you can simply change the scope of your settings. Figure 36-5 illustrates that on the Settings tab of the Project Properties page you can indicate whether you want a setting to have Application or User scope.

Figure 36-5

Figure 36-5. Figure 36-5

In essence, by changing the scope of a setting you are making the choice as to whether you want the setting to be read-only — in other words, it applies to the application regardless of which user is using the application — or read-write. When you access a project setting from code you will notice that if you try to assign a value to an Application setting you will get a compile error, whereas with a User setting you can assign a new value. Assigning a new value to the User setting only changes the value for that setting for the duration of that application session. If you want to persist the new value between sessions you should call the Save method on the designer-generated Settings object, as shown in the following code snippet:

VB

Properties.Settings.Default.BackgroundColor = Color.Blue;
Properties.Settings.Default.Save();

Table 36-2 lists the other methods that are defined on the Settings object that may be useful when manipulating User settings.

Table 36.2. Settings Objects Methods

METHOD NAME

FUNCTIONALITY

Save

Persists the current value of the setting.

Reload

Restores the persisted value of the setting.

Reset

Returns the persisted, and in-memory, value of a setting to the default value (this is the value you define during development in the Settings tab of the Project Properties page). You do not need to call Save after calling Reset.

Upgrade

When versioning your application you can call Upgrade to upgrade user settings to new values associated with your application. Note that you may want to be discriminate on when you call this method because you may inadvertently clear user settings.

(event) SettingChanging

Event raised when a setting is about to change.

(event) PropertyChanged

Event raised when a setting has changed.

(event) SettingsLoaded

Event raised when settings are loaded from persisted values.

(event) SettingsSaving

Event raised prior to current values being persisted.

When building an application that makes use of User-scoped settings it is important to test the application as if you were using it for the first time. The first time you run your application there will be no user-specific settings, which means your application will either use the values in the application configuration file or the default values that are coded in the designer-generated file. If you have been testing your application, the Synchronize button on the Settings tab of the Project Properties page (shown in the top-left corner of Figure 36-5) will remove any user-specific settings that may have been persisted during earlier executions of your application.

REFERENCED PROJECTS WITH SETTINGS

As applications grow, it is necessary to break up the logic into assemblies that are referenced by the main application. In the past, if these referenced assemblies wanted to use an application setting, there were a number of gotchas that made it problematic. With Visual Studio 2010, it is now possible to share application settings among assemblies using the project properties designer. Figure 36-6 shows the Settings tab of the project properties designer for a reference assembly. In this case the Access Modifier drop-down has been set to Public to allow access to these settings from the main application.

Figure 36-6

Figure 36-6. Figure 36-6

To access this property from the main application, you can again use the generated strongly typed access properties:

VB
ReferencedAssembly.My.MySettings.Default.SecondaryServer
C#
ReferencedAssembly.Properties.Settings.Default.SecondaryServer

A word of caution about using the project properties designer and referenced application settings: If you examine the code-behind file for the settings designer, you will note that for each of the settings you have defined there is a strongly typed access property, as previously discussed. What is important is the DefaultSettingValueAttribute that is applied. This is significant because it determines the value that will be returned by this property if the configuration file does not have any value specified. In the following snippet, the default value of www.peaksite.com will be returned if there is no SecondaryServer element defined in the configuration file:

VB

Namespace My
    Partial Friend NotInheritable Class MySettings
        Inherits Global.System.Configuration.ApplicationSettingsBase
.
<Global.System.Configuration.ApplicationScopedSettingAttribute(),  _
Global.System.Diagnostics.DebuggerNonUserCodeAttribute(),  _
Global.System.Configuration.DefaultSettingValueAttribute("www.peaksite.com")>  _
        Public ReadOnly Property SecondaryServer() As String
            Get
                Return CType(Me("SecondaryServer "),String)
            End Get
        End Property
    End Class
End Namespace

Now, you might ask why this is important when you're dealing with referenced application settings. It is because although the project properties designer enables you to specify that you want to allow access to settings from another assembly, it doesn't enable you to indicate that an application does, in fact, reference settings from another assembly. The upshot is that when it compiles the application it takes only the app.config file in the application project folder, rather than combining the elements from the app.config files in the referenced assembly folder.

Unfortunately, because of the default value attribute you are unlikely to notice this until the application is deployed and you realize that some of the settings are missing from the app.config file. Because of this, you should make sure you manually combine these files. In this case the result would be this:

<configuration>
.
<applicationSettings>
<ConfigurationApplication.My.MySettings>
<setting name="PrimaryServer" serializeAs="String">
<value>www.softteq.com</value>
</setting>
</ConfigurationApplication.My.MySettings>
<ReferencedAssembly.My.MySettings>
<setting name="SecondaryServer" serializeAs="String">
<value>www.peaksite.com</value>
</setting>
</ReferencedAssembly.My.MySettings>
</applicationSettings>
</configuration>

SUMMARY

In this chapter you have learned how configuration files can be used not only to control how your application runs, but also to store settings that may need to be adjusted at runtime. You should now be able to store simple name-value information, as well as more structured information, within the configuration file.

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

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