Introduction
So when we last left off, we had been discussing the use of XAML as a means of storing configuration information for applications. During the conversation, we illustrated how an XAML file which contained strongly typed configuration data could be loaded into the process via the XamlReader
class and utilized at runtime, as an object of a predefined type, to maintain offline configuration data. We further illustrated this by creating a general class and giving it some public
properties (to represent the configurable items), then created an XAML file that defined an instance of that general class. This XAML file was then read in and used to configure an application.
This is a great tool for a certain type of application, and works very well. We had no problem with this approach and used it as the base configuration mechanism for LaunchPoint, the application that we illustrated in the previous post. As we mentioned before however, .NET already comes with a set of configuration files which are generally always there. These are the app.config and the web.config files (referred to as *.config for the duration of this article). These configuration files are kind of baked into the implementation of various technologies that build on top of the .NET Framework like ASP.NET and WPF, hence they cannot be easily replaced. For these types of scenarios, an approach that utilizes both of these patterns would be ideal. That way, we could use our next generation configuration approach but not lose the legacy *.config stuff that already exists. We can, of course, do this by adding a new configuration file into the mix which works outside of the space of the *.config system and there would be little wrong with that. In fact when the configuration requires runtime updates that must be persisted back to the file system, the split approach actually outshines the static *.config style. However, in the interest of unification, we felt it necessary to introduce the use of custom configuration sections as a means of merging these two approaches in a much more seamless manner.
Doing It
To use the sample or follow along in the discussion, you may feel the need to get a hold of the MindFactorial.Library
open source project (it can be found here) to build and acquire the mindfactorial.library.dll file. The project includes a copy of this file if you do not care to see the source code. We also included the code for the implementation of XamlConfigSectionHandler
if you feel the need to create your own implementation.
So to recap, we can take a definition such as the one that follows for a Person
configuration:
public class Person {
public string FirstName { get; set; }
public string LastName { get; set; }
public ContactInfo Contact { get; set; }
}
public class ContactInfo
{
public Address StreetAddress { get; set; }
public string EmailAddress { get; set; }
public string HomePhoneNumber { get; set; }
public string CellPhoneNumber { get; set; }
public string FaxNumberNumber { get; set; }
}
public class Address
{
public string Street { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
And represent it, for whatever reason in XAML as:
<Person xmlns="clr-namespace:XamlConfig;assembly=XamlConfig"
FirstName="John"
LastName="Smith"
>
<Person.Contact>
<ContactInfo CellPhoneNumber="123456789"
FaxNumberNumber="123456789"
HomePhoneNumber="123456789"
EmailAddress="test@test.com">
<ContactInfo.StreetAddress>
<Address>
<Address.Street>1234 fifth street</Address.Street>
<Address.City>sammamish</Address.City>
<Address.State>wa</Address.State>
<Address.Zip>98074</Address.Zip>
</Address>
</ContactInfo.StreetAddress>
</ContactInfo>
</Person.Contact>
</Person>
This, we all should know at this point. If not, please read the previous article.
It just so happens that this exact XAML can be added to your pre-existing *.config file by utilizing a custom configuration handler / config section pattern. The sample provided utilizes the (mind)! Config handler. The implementation of XamlConfigSectionHandler
in the example is quite simple should you choose to roll your own with more functionality:
public class XamlConfigSectionHandler :
System.Configuration.IConfigurationSectionHandler
{
object _XAML_instance = null;
public T GetInstance<T>()
{
return (T)_XAML_instance;
}
public object Create(object parent, object configContext, System.Xml.XmlNode section)
{
StringReader XAML = new StringReader(section.OuterXml);
XmlReader XAML_reader = XmlReader.Create(XAML);
_XAML_instance = System.Windows.Markup.XamlReader.Load(XAML_reader);
return this;
}
}
So as you can see from the example, all this handler does is take the XAML from the custom section that had been created and load it as XAML. Additionally, a generic method GetInstance
exists which simply casts the instance created from XAML to a type argument specified by the caller. It is presumed that the caller will know the type of the XAML object since the type must be accessible to the XamlReader
class at runtime. The final configuration file will look like this:
="1.0"="utf-8"
<configuration>
<configSections>
<section name="Person" type="MindFactorial.Library.XamlConfigSectionHandler,
MindFactorial.Library" />
</configSections>
<Person xmlns="clr-namespace:XamlConfig;assembly=XamlConfig"
FirstName="John"
LastName="Smith"
>
<Person.Contact>
<ContactInfo CellPhoneNumber="123456789"
FaxNumberNumber="123456789"
HomePhoneNumber="123456789"
EmailAddress="test@test.com">
<ContactInfo.StreetAddress>
<Address>
<Address.Street>1234 fifth street</Address.Street>
<Address.City>sammamish</Address.City>
<Address.State>wa</Address.State>
<Address.Zip>98074</Address.Zip>
</Address>
</ContactInfo.StreetAddress>
</ContactInfo>
</Person.Contact>
</Person>
</configuration>
Again, here we use the MindFactorial version of the config section handler but the choice is really yours as to whether to utilize it or create your own XAML section handler with more or less features. Our approach was to keep it as general as possible so as to support multiple forms of XAML with the same section handler. Hence the app.config file below is also valid:
="1.0"="utf-8"
<configuration>
<configSections>
<section name="Person" type="MindFactorial.Library.XamlConfigSectionHandler,
MindFactorial.Library" />
<section name="String" type="MindFactorial.Library.XamlConfigSectionHandler,
MindFactorial.Library" />
</configSections>
<Person xmlns="clr-namespace:XamlConfig;assembly=XamlConfig"
FirstName="John"
LastName="Smith"
>
<Person.Contact>
<ContactInfo CellPhoneNumber="123456789"
FaxNumberNumber="123456789"
HomePhoneNumber="123456789"
EmailAddress="test@test.com">
<ContactInfo.StreetAddress>
<Address>
<Address.Street>1234 fifth street</Address.Street>
<Address.City>sammamish</Address.City>
<Address.State>wa</Address.State>
<Address.Zip>98074</Address.Zip>
</Address>
</ContactInfo.StreetAddress>
</ContactInfo>
</Person.Contact>
</Person>
<String xmlns="clr-namespace:System;assembly=mscorlib" >
another custom config section using XAML
</String>
</configuration>
In this second iteration of the app.config file, we illustrate the use of multiple XAML instances within the same *.config file without needing to change section handlers. The code below is a full listing of the source for reading the XAML sections out of a *.config file. The previous implementation which used a standalone XAML configuration file is included to illustrate the similarity between the two.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.IO;
using MindFactorial.Library;
using System.Configuration;
namespace XamlConfig
{
class Program
{
static void Main(string[] args)
{
Person XAML_person1 = null, XAML_person2 = null;
using (FileStream XAML_stream = File.OpenRead("../../person.XAML"))
{
XAML_person1 = (Person)System.Windows.Markup.XamlReader.Load(XAML_stream);
Console.WriteLine(XAML_person1);
}
XamlConfigSectionHandler XAML_person_section =
(XamlConfigSectionHandler)ConfigurationManager.GetSection("Person");
XAML_person2 = XAML_person_section.GetInstance<Person>();
XamlConfigSectionHandler XAML_string_section =
(XamlConfigSectionHandler)ConfigurationManager.GetSection("String");
string data = XAML_string_section.GetInstance<string>();
}
}
}
Hopefully, at this point you should have gained the appreciation for XAML that we have and begin to understand why it's a no-brainer as a choice for configuring applications. Whether integrated into pre-existing configuration files or left as standalone, XAML is a powerful, flexible, and extremely extensible alternative to simple XML for expression of configuration information.
History
- 16th April, 2008: Initial post