38.3. Application Settings

Applications frequently have settings that do not fit into the default configuration schema. There are four mechanisms for storing this information.

38.3.1. 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="PrimaryServer" value="http://www.softteq.com"/>
    </appSettings>
</configuration>

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

Dim server As String = ConfigurationManager.AppSettings("PrimaryServer")

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.

38.3.2. Project Settings

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

Figure 38.1. Figure 38-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.Settings>
           <setting name="PrimaryServer" serializeAs="String">
            <value>www.softteq.com</value>
        </setting>
    </ConfigurationApplication.My.Settings>
</configuration>

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

C#

string primaryServer = Properties.Settings.Default.PrimaryServer;

VB.NET

Dim primaryServer as String = My.Settings.PrimaryServer

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

Me.Button1.Text= Global.ConfigurationApplication.My.MySettings.Default.Button1_Text

When this application is deployed, the text displayed on Button1 is dynamically loaded from the configuration file. In the following steps, for example, we 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 Data category or the ApplicationSettings item in the alphabetic list, as shown in Figure 38-2.

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

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

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

    Figure 38.4. Figure 38-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.

38.3.4. 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, as 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 2008 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, we 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.

Of course, the best way to illustrate this potential unsuitability is with an example. Our application requires a list of registered entities with which to work. One type of entity is a company, and we need to be provided with both the company name and the date on which it was registered. The XML snippet that we 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):

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

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 we 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 we had a web application for which we 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, we can simply call the appropriate section using the configurationManager framework class:

Dim registered as RegisteredEntities= _
    ctype(configurationmanager.GetSection("RegisteredEntities"),RegisteredEntities)

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

38.3.4.2. IntelliSense

Even after you get the custom configuration sections correctly mapped, there is still no support provided by Visual Studio 2008 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.

In order to get IntelliSense and validation for your custom configuration section, you need to indicate the structure of the configuration section to Visual Studio 2008. 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 9.0XmlSchemas. Unfortunately, this is where it gets a little bit more complex, as 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.

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

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