Introduction
In the .NET environment there are few things left that are not strongly typed. The application settings, based on the .config files are one of them. When you access the settings through ConfigurationSettings.AppSettings
, the .NET framework requires a string identifier to get the item you need from the .config
file. Moreover, what you get is a generic object that you need to cast
into the right type before you can use it. The problem is that errors
in the identifier or the type, only show at runtime. I thought it would
be nice to be able to detect these errors at compile time.
During the short analysis part of this project, I determined a few other requirements /nice features:
- Full integration to Visual Studio .NET
- Ability to write values
- Intellisense, complete with comments when accessing settings values
- Detect changes made to the XML file
Background
I had this idea while using strongly typed DataSet
s. I got curious about how VS.NET generated a .cs
file from the XML schema, so I did a little research. The answer is
that it uses a custom tool. If you have not heard of custom tools, I
suggest you read Chris Sells' article on MSDN about Visual Studio .NET's top 10 cool features.
Apparently, Microsoft changed its mind about letting VS.NET users
write their own custom tools. The interfaces and base classes that were
public in VS.NET 2002 disappeared in VS.NET 2003. Since some people
already had code that used these interfaces, they released the source
code on GotDotNet. If you ever want to write a custom tool, get this file.
Another interesting tidbit about this project is the integration of
new file types in VS.NET's "Add new item..." dialog. I wanted to be
able to add a special file to my project, define the values and types I
wanted to access, and have the custom tool do the rest. Fortunately
Chris Sells has another article on how to add your own file types to VS.NET.
After a bit of prototyping, it became clear that I would need to
generate quite a bit of code. CodeDom could have been a good solution,
but having used it a bit, I knew it would be a real pain to code that
part. So I decided to use CodeSmith
instead. It is a code generator that relies on a ASP.NET-like syntax
and you can use its engine in your code. You will need to download and
install it to get this tool to work.
Usage
I decided to forget about .config files and use a simple XML file so as not to interfere with the existing System.Configuration
classes and give me more flexibility. The schema for the XML file is extremely simple: each settings value is described in an <Entry>
tag that has a name, a type, a value and an optional comment. Arrays are supported via the <Set>
and <Item>
tags. Look at the example file to get a better idea of how it works.
The tool works as follows:
- Add a special item to your project (Config file in the Utility section). In fact it is an XML file (name .xfg)
based on the simple schema described above. VS.NET automatically
assigns the custom tool to it so that each time you save it, it will
generate a hidden .cs file with the class to access the values.
You can see this file if you click on "Show all files" in the solution
explorer. If the XML file is malformed, errors will show in the tasks
pane.
- Edit this file, add entries, sets etc. required by your application.
- The generated class has the same name as the .xfg file and is in the project's default namespace. Use
<defaultNamespace>.<fileName>.Settings
to access the settings. Note how Intellisense displays the properties along with comments.
- Compile. Any type mismatch and syntax errors will cause the
compilation to fail and you'll be able to correct them before running
the application.
- Make sure the .xfg file is located at the same place as the assembly and run.
The code
There is not much code involved in making such a custom tool. You simply write a class that inherits from the BaseCodeGeneratorWithSite
class you got from GotDotNet and override the GenerateCode
method. Since I use CodeSmith, I only need to set it up and run it with
a template that will generate the class to access the settings. Most of
the interesting code is in the template and the resulting generated
code.
Template code
The trick here is to clearly separate in your mind the generator
code from the generated code. Since they are mixed in a single
ASP.NET-like file it is easy to lose track of it. As an example, here
is the part of the template that generates the strongly typed
properties for each entry in the XML file. The parts in <% %>
contains generator code.
The rest is generated code, just like ASP.NET. The doc
variable is an XmlDocument
that contains the settings file.
<% foreach(XmlNode n in doc.SelectNodes("/Configuration/Entry"))
{%>
private <%=n.Attributes["Type"].Value%> m_<%=n.Attributes["Name"].Value%>;
<% if(n.Attributes["Description"] != null)
{
%>
[Description("<%=n.Attributes["Description"].Value%>")]
<%
}%>
[Category("Values")]
public <%=n.Attributes["Type"].Value%> <%=n.Attributes["Name"].Value%>
{
get { return m_<%=n.Attributes["Name"].Value%>; }
set { m_<%=n.Attributes["Name"].Value%> = value; }
}
<%
}
%>
You can analyze the rest of the template and particularly the Refresh
and Save
methods in the TypeConfig.cst
file. If you want to, you can also modify the template to alter the
generated code to your liking. Changes to the template file are taken
into account immediately by the tool, so you can modify it and test
without having to recompile.
Generated code
Like System.Configuration.ConfigurationSettings.AppSettings
,
I wanted a single access point, so I used a singleton pattern. This
way, it would require less code to access the settings and possibly
reduce threading problems. The constructor is private and you can only
get one instance through the static Settings
property.
private static Config1 m_settings;
public static Config1 Settings
{
get
{
if(m_settings == null)
m_settings = new Config1();
return m_settings;
}
}
private Config1()
{
}
In order to monitor changes made to the configuration file externally, a FileSystemWatcher
object is created and made to look for any modification made to the file. When it detects a change, it fires the FileChanged
event.
The Refresh
method reads the file and uses XPath to read the values and store them in private fields. The Save
method writes the values in the file.
The class also inherits from Component
. It
enables the VS.NET designer and property grid to access settings
visually, if an instance of the generated class is dropped on a form
for example. The singleton pattern disrupted that idea but it could be
helpful.
Points of interest
The most difficult aspect of this project was certainly the
installation procedure required to integrate it to Visual Studio
correctly. There are files to put at the right place, registry keys to
read to determine where to put the files, components to register etc. I
used NSIS
(Winamp's installer) for that, because it is powerful but it is also
fairly difficult to use. The install script and files required to
generate the installer is included in the source package, in case you
want to have a look at it.
Updates
Latest versions will be posted on my weblog and on Code Project. Feel free to post comments or suggestions.
Downloads
NOTE : The source code package contains only the source code which
in itself is not functional. You need to run the installer to get the
tool to work.
History
- July 17, 2003 : initial release, build 1271.
- June 02, 2004 : added xml schema to get Intellisense when editing the config file. Also added a demo application.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.