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
786
.
chaPter 36 conFigurATion FileS
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
application settings .
787
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
Code snippet RegisteredEntities.vb
c#
class RegisteredEntities : ConfigurationSection{
[ConfigurationProperty("Companies")]
public Companies Companies{
get{
return base["Companies"] as Companies;
}
}
}
[ConfigurationCollection(typeof(Company))]
class Companies : ConfigurationElementCollection{
788 .
chaPter 36 conFigurATion FileS
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;
}
}
}
Code snippet RegisteredEntities.cs
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:
application settings .
789
Vb
Dim registered as RegisteredEntities= _
ctype(configurationmanager.GetSection("RegisteredEntities"),RegisteredEntities)
c#
var registered =
ConfigurationManager.GetSection("RegisteredEntities") as RegisteredEntities;
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= “ Registered
Entities ” 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 Files\Microsoft Visual Studio 10.0\Xml\Schemas\. Unfortunately,
790
.
chaPter 36 conFigurATion FileS
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
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.
User Settings . 791
792 .
chaPter 36 conFigurATion FileS
referenced ProJects with settinGs