Intro

As nearly all of our configuration files are Xml, and we want to be able to handle reloads dynamically, the functionality required to do so was long ago encapsulated in the XmlSerializerSectionHandler. Because the needs of remotely hosted configuration sections are very similar, and to ease the transition, we created XmlSerializerReplacementHandler.

XmlSerializerReplacementHandler can handle both configuration sections hosted on the configuration system and local files, and will detect changes from either to notify any EventHandlers you create.

For more information about the configuration system as a whole, please see Documentation.

Example Code

There is an example of an console app that utilizes the functionality of XmlSerializerReplacementHandler in the XmlSerializerExample project. All of the code below is taken from that project.

Creating A New Configuration Section

If your configuration is an XML file that can be deserialized into a domain object, then using the XmlSerializerReplacementHandler is an excellent choice for handling it. Here's what it gives you:
  • Your configuration will be returned as your domain object from ConfigurationManager
  • You can specify a function to be called when the configuration is updated
  • You can override the remote configuration with a local file.
    • Using this functionality you can test reloading easily, or just have everything in place for the section being remotely hosted when you're ready while keeping everything in the file system for now

Below are the steps for creating a new configuration section that is handled by XmlSerializerReplacementHandler.

All of the code below is also in the XmlSerializerExample project.

Create your Configuration Object

Once you know the values you need to have configurable, the first thing to do is to create your configuration section.

Define the Domain Object

You need a domain object to represent your configuration object, and it needs a little bit of information to control XML serialization. If you needed a string, an integer, and an array of strings, your class would look like:

using System.Xml.Serialization;

namespace MySpace.XmlSerializerExample
{
	[XmlRoot("MySection", Namespace = "http://myspace.com/MySection.xsd")]
	public class MySection
	{
		[XmlElement("MyString")]
		public string MyString;

		[XmlElement("MyInt")] public int MyInt;

		[XmlArray("MyListOfStuffs")]
		[XmlArrayItem("Stuff")]
		public string[] MyListOfStuffs { get; set; }
	}
}

Create a Schema (Optional)

Because this is representing XML, it's also a good idea to create a matching schema. This is not required for the system to work, but it will give you the ability to have intellisense when editing your config files, and the ability to have a deployment system validate any files being pushed to production.

The schema for the above would look like

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="MySection"
    targetNamespace="http://MySpace.com/MySection.xsd"
    elementFormDefault="qualified"
    xmlns="http://MySpace.com/MySection.xsd"
    xmlns:mstns="http://MySpace.com/MySection.xsd"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
  <xs:complexType name="MySection">
    <xs:sequence>
      <xs:element name="MyString" type="xs:string" minOccurs="1" maxOccurs="1"/>
      <xs:element name="MyInt" type="xs:int" minOccurs="1" maxOccurs="1"/>
      <xs:element name="MyListOfStuffs" minOccurs="1" maxOccurs="1">
        <xs:complexType>
          <xs:sequence>
            <xs:element name="Stuff" type="xs:string" minOccurs="1" maxOccurs="unbounded" />
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:sequence> 
    <xs:attribute name="type" type="xs:string" />     
  </xs:complexType>
</xs:schema>

Since you very astute, you surely noticed the "type" attribute that has nothing to do with the domain object we defined. That is there because the config file needs some way to tell the handler how to instantiate your domain object, and that's it. More on that later (but not much more, it's pretty simple).

Link the .cs and .xsd Files in Visual Studio (even MORE optional)

Visual studio can visually display the .xsd and .cs files that make up your schema and object definition, but there's no obvious way to do so in the UI. It's a fairly quick edit in your .csproj, and it's kind of nice to have the files linked.

Just find the entry that looks like

<Compile Include="MySection.cs" />

And edit it to look like
<Compile Include="MySection.cs">
  <DependentUpon>MySection.xsd</DependentUpon>
</Compile>

And the two files will be linked in the UI. Doing it this way will make "MySection.xsd" be an item group like a folder. You can also invert it and do

<ItemGroup>
  <None Include="MySection.xsd">
    <DependentUpon>MySection.cs</DependentUpon>
  </None>
</ItemGroup>

to make MySection.cs be the master file. Again, all of this is strictly optional, but you may see other projects set up this way and wonder how it's done!

Create a Configuration File

Now that you've defined your configuration, you need to actually create one. An example of the above schema is

<?xml version="1.0" encoding="utf-8" ?>
<MySection type="MySpace.XmlSerializerExample.MySection, MySpace.XmlSerializerExample" xmlns="http://myspace.com/MySection.xsd">
  <MyString>I am a string</MyString>
  <MyInt>42</MyInt>
  <MyListOfStuffs>
    <Stuff>Thing 1</Stuff>
    <Stuff>Blue Thing</Stuff>
  </MyListOfStuffs>
</MySection>

The only thing worth mentioning here is that "type" attribute. This contains a standard .net type string - [Class Name], [Assembly Name] - and is the name of the domain object for your class. This is just used to deserialize the XML into your domain object.

Create Your Section Definition

Now that you have your configuration definition and file, you just need to tell your application how to get to it, and you do that by defining a <section> element in <configSections> at the top of your app config. This just tells the configuration manager which class to use when you ask it for your section.

The section definition is:

<section name="MySection" type="MySpace.ConfigurationSystem.XmlSerializerReplacementHandler, MySpace.ConfigurationSystem.Client" restartOnExternalChanges="false"/>    

The only thing you need to change for each section the "name" attribute.

Ensure Configuration Client is Configured

Please see the main Documentation to ensure the configuration client is set up for your application.

Add a Blank Configuration Entry

Because of the way the .net configuration manager works, there must be some configuration info specified. Since the information will actually be coming from the remote server, we won't put any of the info in the application config, but we still need a placeholder. All that's needed is:

<MySection/>

Tell the Configuration Servers How to Read Your Section

The configuration server needs to know where your config file is before they can serve it, and course needs a copy of it.

This configuration is called the section mapping, and by default is a file on the server root called SectionMapping.xml. You just need to add an entry that looks like

    <section
      name="MySection"
      provider="FileProvider"
      source="MySection.config"
      />

in the <sectionmapping> node and copy the file there. (This entry and the file are included in the source of the example windows service as well). Within a minute or so, MySection will be available at http://configserver/get/MySection. You can also check http://config/mapping to see if your update has made it in.

Registering for Change Notification

It's extremely desirable to be reload configuration information without requiring and application restart, so of course we support that! The handler keeps a list of any Event Handlers that are registered for each section, and calls them when changes happen. This section explains how to do this.

Create Your Reload Method

First you need a method that will be called when there is updated information. Because it is an EventHandler, it must have the signature:

public void MyReload(object sender, EventArgs args)

Of course what exactly this does is going to be very application specific. In the example code, the reload just displays the new section. When this method is called "sender" will be an instance of the configuration with new values. In addition, args will actually be an instance of ConfigurationChangedEventArgs, which contains the name of the section, the string representing the section, and the bytes of the string. In most cases you don't need any of this, but it's there if you do!

Here's an example of what a reload handler can do in a simple case:

public static void MyReload(object sender, EventArgs args)
{
  Console.WriteLine("Got an updated section!");
  MySection section = sender as MySection;
  DisplaySection(section);		
}

In order for your method to be called, you must register it. You can do at any point (before or after you request the section) but it's best to do so only once!

The call to register looks like this:

XmlSerializerReplacementHandler.RegisterReloadNotification("MySection", MyReload);

With these in place, once the config file has been updated on the server, within two minutes your reload method should be called. (There is a one minute granularity to both the servers checking for updates and the clients checking the servers for updates, so there is up to a 2 minute delay).

Overriding With Local Files

There are a couple of circumstances where you may wish to override what is stored remotely with something on the local file system. You may have a config section that has many different versions, some of which change often and some of which don't, so that you want to keep some consumers using local files and others using centralized storage. You may simple want to test something on a simple machine easily. Whichever the case, with XmlSerializerReplacementHandler it is easy to use a local file instead of the remotely hosted version.

There are two different ways you can do this, both using attributes on the {code:c#}<MySection/>{code:c#} block in your app config.

The first, and recommended, way, uses two attributes. One specifies the file name to use, and the other whether or not to use it. This allows you to keep the file name in the config, and toggle between local and remote easily without removing any information. The attributes are "useLocal" and "localFileName", like this:

<MySection useLocal="true" localFileName="MySection.config"/>

You have probably guess that changing the "true" to "false" will disable the local file. Because this information is in the app configuration directly, there is sadly no way to change this without restarting the app pool.

The other way is to simple use "configSource", as you would to use an external file for any other section. This is supported because it is the Standard Dot Net Way To Do Things, but we believe the former method will be easier throughout the development deployment and debugging lifecycle. Nevertheless, if you wish to use this method it would look like:

<MySection configSource="MySection.config"/>

= FAQS =

== What happens if the remote system is down or slow? ==

This is of course the first concern that people have when we introduce a centralized repository for... anything. And it's a good thing to be concerned about! Luckily we've spent a lot of time thinking about it. There are basically two points of failure possible with the system:
  • Clients talking to servers
  • Servers talking to providers

Both of these are logically the same, and we deal with them the same way.

Clients looking for config sections talk to the servers as little as possible, and do so asynchronously whenever possible. Because configuration servers are redundant and cache information on their local drives, the only situation where a client will completely fail to return data for a configuration section is when there are NO config servers available and that client has NEVER read that section before. If no servers cannot be reached, the local copy will be used until a server is reachable.

That takes care of the "down" issue, but what about "slow"? That's where the asynchronous polling comes into play. The only time a clients talk to servers synchronously is on start-up. The rest of the time, sections are checked and updated in the background.

Last edited May 23, 2011 at 10:26 PM by eriknelson, version 5

Comments

No comments yet.