As I was writing a small TCP server for serving a Silverlight local TCP policy, I came across a certain need. Inspired by Dan Wahlin’s server implementation, I chose to write a simplified version for myself. I needed to keep some XML in the App.config without constraining it with a schema.
The normal solution in this case is a custom section, sibling to appSettings if you wish. So my App.Config looked at first like this:
<configuration> <appSettings> <add key="ipAddress" value="127.0.0.1"/> </appSettings> <access-policy> <cross-domain-access> <policy> <allow-from> <domain uri="*" /> </allow-from> <grant-to> <socket-resource port="4502" protocol="tcp" /> </grant-to> </policy> </cross-domain-access> </access-policy> </configuration>
Upon running the program, even addressing the “ipAddress” key in the appSettings section throws an exception like:
System.Configuration.ConfigurationErrorsException was unhandled
Message="Configuration system failed to initialize"
Source="System.Configuration"
BareMessage="Configuration system failed to initialize"
Line=0
StackTrace:
at System.Configuration.ConfigurationManager.PrepareConfigSystem()
at System.Configuration.ConfigurationManager.GetSection(String sectionName)
at System.Configuration.ConfigurationManager.get_AppSettings()
at ConsoleApplication1.Program.Main(String[] args) in C:\Users\Andrei\Documents\Visual Studio 2008\Projects\ConsoleApplication1\ConsoleApplication1\Program.cs:line 21
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException: System.Configuration.ConfigurationErrorsException
Message="Unrecognized configuration section access-policy.
(C:\\Users\\Andrei\\Documents\\Visual Studio 2008\\Projects\\ConsoleApplication1\\ConsoleApplication1\\bin\\Debug\\ConsoleApplication1.vshost.exe.config line 8)"
Source="System.Configuration"
BareMessage="Unrecognized configuration section access-policy."
Filename="C:\\Users\\Andrei\\Documents\\Visual Studio 2008\\Projects\\ConsoleApplication1\\ConsoleApplication1\\bin\\Debug\\ConsoleApplication1.vshost.exe.config"
Line=8
StackTrace:
at System.Configuration.ConfigurationSchemaErrors.ThrowIfErrors(Boolean ignoreLocal)
at System.Configuration.BaseConfigurationRecord.ThrowIfParseErrors(ConfigurationSchemaErrors schemaErrors)
at System.Configuration.BaseConfigurationRecord.ThrowIfInitErrors()
at System.Configuration.ClientConfigurationSystem.EnsureInit(String configKey)
InnerException:
So something is wrong. We need to tell the runtime that the “access-policy” section is allowed.
<configuration> <strong> <configSections> <section name="access-policy" type="CustomSections.InlineXmlSection, CustomSections"/> </configSections> </strong> ...
At first, I didn’t place the type attribute in the “section” element, but it turned out it had to be specified and not be empty. Moreover, it must contain the fully-qualified class name and the assembly which contains it. The class must inherit from System.Configuration.ConfigurationSection.
So, I created an assembly called CustomSections, and added references to the System.Configuration assembly and the System.Xml assembly.
All you need to do is override the DeserializeSection method and load the XML document in there:
using System.Configuration; using System.Xml; namespace CustomSections { public class InlineXmlSection : ConfigurationSection { public XmlDocument Content { get; private set; } protected override void DeserializeSection(XmlReader reader) { (this.Content = new XmlDocument()).Load(reader); } } }
The code is pretty self-explanatory: we instantiate a new XmlDocument and load it from the XmlReader provided to us by the configuration infrastructure. If anything goes bad, the exception handling will be the responsibility of the caller. In this case, the first call to ConfigurationManager.
Now, let’s put the code to use:
private static void Main(string[] args) { expectedRequestBytes = Encoding.UTF8.GetBytes("<policy-file-request/>"); listener = new TcpListener(IPAddress.Parse(ConfigurationManager.AppSettings["ipAddress"]), 943); var policySection = (InlineXmlSection)ConfigurationManager.GetSection("access-policy"); policyBytes = Encoding.UTF8.GetBytes(policySection.Content.OuterXml); ... }
The underlined code is the relevant portion (the rest is provided for context). We get the section via ConfigurationManager.GetSection, and we have to cast the result to the desired section type. Then we use the section as we see fit.
Very nice, but with a caveat: every time you change a web.config, not an app.config, you restart IIS for that site. That means you lose any meaningful debugging information in case of a situation that you need to examine in real time.
I’ve met this case when I was investigating a weird problem on a production site. I suspected it had to do with a certain value in appSettings, but I couldn’t see if that was the case since the problem would also go away for a while if I just restarted IIS.
So, just be careful where you store the configuration. Sometimes it is better to just serialize an object in your own file and forget the ConfigurationManager.