Usually, WCF services are configured either programmatically or within the applications configuration file. Sometimes there are scenarios where the WCF service must be configured dynamically, i.e. at runtime. Of course, this can be done in code. The wider the range of possible configurations gets, the harder it gets to configure the service in code because every single possible configuration item must be coded. I guess that the reader (you) landed on this blog page because of this issue so I won’t spend much time explaining the pain with that.
So, it would be great to have the option for XML configuration snippets to be loaded into a service. By doing so, these snippets can be saved in a database or created at runtime. Also, these snippets are more compact than code configuration, which makes them easier to read and to write. But how can we do that?
A solution widely spread is to subclass a ServiceHost and make it load a ServiceElement we loaded ourselves. Subclassing a service host and making it load a service element is easy because the ServiceHost provides overloadable methods to do so. The only difficult part is loading the service element, so let’s start with that:
public static T DeserializeSection<T>(string xml) where T : ConfigurationSection { var configurationSection = Activator.CreateInstance<T>(); var xmlReaderSettings = new XmlReaderSettings {ConformanceLevel = ConformanceLevel.Fragment}; using (var stringReader = new StringReader(xml)) using (var reader = XmlReader.Create(stringReader, xmlReaderSettings)) { var method = typeof(ConfigurationSection).GetMethod("DeserializeSection", BindingFlags.Instance | BindingFlags.NonPublic); method.Invoke(configurationSection, new object[] { reader }); } return configurationSection; }
Here we have a static method of a helper class (called ConfigurationSectionHelper and used below). What we essentially do is to invoke a private method called DeserializeSection. This is the whole trick, actually. If this method was public, most developers would have figured this out by themselves, but here we are now.
The method above takes any generic argument that subclasses a ConfigurationSection, creates an instance of it and invokes DeserializeSection with our XML snippet as a parameter. The result is a loaded configuration section.
And here’s how we use the method to configure our service:
public class JobServerHost<T> : ServiceHost { private readonly string _config; public JobServerHost(string config) { _config = config; InitializeDescription(typeof(T), new UriSchemeKeyedCollection()); } protected override void ApplyConfiguration() { var servicesSection = ConfigurationSectionHelper.DeserializeSection<ServicesSection>("<services>" + _config + "</services>"); var serviceElement = servicesSection.Services.Cast<ServiceElement>().FirstOrDefault(); if (serviceElement == null) { throw new InvalidOperationException("Service configuration contains no services."); } LoadConfigurationSection(serviceElement); } }
What happens here is that in the appropriate method overload (ApplyConfiguration) we use our helper class to create a ServiceElement and load it with our XML snippet. Then we extract the first ServiceElement from it and call the method LoadConfigurationSection to actually create the configured service.
Just for completeness, here’s a sample of the XML expected:
<service name="MyServer"> <host> <baseAddresses> <add baseAddress="http://localhost/myserver"/> </baseAddresses> </host> <endpoint address="" binding="basicHttpBinding" contract="MunirHusseini.IMyServer"/> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/> </service>
If you need to add binding configurations or such to the service configuration, these elements must be defined in the regular application configuration and can be referenced in the service configuration. For example, the above service element might look like this:
<service name="MyServer" behaviorConfiguration="MyServerServiceBehavior">
The referenced behavior configuration MyServerServiceBehavior must be located in the app.config or web.config and will be found by the service host during initialization.
That’s all, folks.