Configuration Strategies

Applies to: Windows Communication Foundation

Published: June 2011

Author: Alex Culp

Referenced Image

This topic contains the following sections.

  • Reduce Unnecessary Configuration
  • Separate Configuration Sources into Files

One of the many challenging aspects of developing enterprise-scale WCF services is how to develop an overall strategy for configuring those services, where each has its own requirements. Another complication is that these services are typically deployed in multiple environments, such as development, Quality Assurance (QA), and production. All of this configuration information can quickly become unmanageable. In many WCF services, the ServiceModel configuration accounts for a good portion of all configuration complexities. In the .NET Framework 4.0, WCF allows you to set up services with a default configuration. However if you use a binding type other than basicHttpBinding, or have any configurations changes that differ from the default, you can end up with the same level of complexity that you experienced using WC in the 3.5 version of the .NET Framework.

This article provides some practical options for reducing configuration complexity.

When configuration files are spread across many services and environments, any sort of change is difficult. A single changed shared dependency or new security requirements might require that you update many configuration files. As the number of configuration files increases, it becomes more difficult to keep them all current. Obsolete files often cause software defects, a slower time to market, and a lack of confidence in the services. For example, if you move a database to a new server, you must be careful to update all configuration files that reference this database. The following figure illustrates three environments, each with its own services. Every service has its own configuration file, which is denoted by the small colored square.

Each service in each environment has its own configuration file

Referenced Image

While you will always need to manage configuration, there are strategies that will make this task easier.

Reduce Unnecessary Configuration

The first strategy is to eliminate any unnecessary configuration. One of the great myths about configuration files is that they add flexibility. People believe this because a configuration file is an easy way to make a quick change that affects the behavior of a service. However, in the enterprise, configuration should be treated as if it were code. A business would not dream of putting untested code into production. Because configuration affects how code operates, making configuration changes in the production environment is effectively the same as putting untested code in the production environment. For this reason, it is a good idea to limit the amount of configuration that you do. Instead, you can take advantage of the simplified configuration that is available with WCF 4.0, which supports an approach known as Convention over Configuration. For example, you no longer need to explicitly specify endpoints. Instead, WCF 4.0 can automatically apply default endpoints. For more information see "Simplified Configuration for WCF Services" at https://msdn.microsoft.com/en-us/library/ee530014.aspx.

Separate Configuration Sources into Files

Another strategy is to separate configuration sections into their own files. This allows different applications to use the same configuration source. For instance, multiple applications might need the same set of connection strings. Storing this information in a separate file means that, if a connection string changes, you only need to edit one file rather than the configuration sections of each application. The following XML code is an example of how to isolate a configuration section.

<system.serviceModel>
   <bindings configSource="Config\Bindings.config"/>
   <client configSource="Config\Client.config"/>
   <services configSource="Config\Services.config"/>
   <behaviors configSource="Config\Behaviors.config"/>
   <diagnostics configSource="Config\ServiceModelDiagnostics.config" />
</system.serviceModel>
<appSettings configSource="Config\AppSettings.config"/>
<connectionStrings configSource="Config\ConnectionStrings.config"/>

For more information about configuration sources, see "SectionInformation.ConfigSource Property" at https://msdn.microsoft.com/en-us/library/system.configuration.sectioninformation.configsource.aspx.

Isolate the Service Configuration

Another strategy is to isolate each service's configuration. This strategy addresses the situation where an incorrect configuration of one service prevents an entire application from starting. However, this strategy is not as straightforward as separating the configuration sections into files, and it requires a custom ServiceHost class and a custom ServiceHostFactory class.

Create a Custom Service Host

The ServiceHost class is not sealed, which means that you can derive a class from it. In particular, you can override the virtual method ApplyConfiguration to read configuration information from another source. The following code is an example of how to do this.

Visual C# Custom ServiceHost to Load Separate Configuration

public class SeparateConfigServiceHost : System.ServiceModel.ServiceHost
{
    public SeparateConfigServiceHost()
    {
    }
 
    public SeparateConfigServiceHost(Type serviceType, params Uri[] baseAddresses)
        :
        base(serviceType, baseAddresses)
    {
    }
 
    public SeparateConfigServiceHost(object singeltonInstance, params Uri[] baseAddresses)
        :
        base(singeltonInstance, baseAddresses)
    {
    }
 
 
    protected override void ApplyConfiguration()
    {
 
        // get the path to the configuration file, you can use this or any other path
        // you want.
        string configFilePath = Path.Combine(System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath,
                                    String.Format("{0}.config", this.Description.Name));
 
        //If the file does not exist, then just use the base configuration which will read
        //from the web.config
        if (!System.IO.File.Exists(configFilePath))
        {
            base.ApplyConfiguration();
        }
        else
        {
            LoadConfigFromFile(configFilePath);
        }
    }
 
    private void LoadConfigFromFile(string configFilePath)
    {
        var filemap = new System.Configuration.ExeConfigurationFileMap();
        filemap.ExeConfigFilename = configFilePath;
 
        var config = ConfigurationManager.OpenMappedExeConfiguration(filemap, ConfigurationUserLevel.None);
 
        var serviceModelSectionGroup = System.ServiceModel.Configuration.ServiceModelSectionGroup.GetSectionGroup(config);
 
        bool foundMatchingConfigElement = false;
        foreach (ServiceElement se in serviceModelSectionGroup.Services.Services)
        {
if (se.Name == this.Description.ConfigurationName)
            {
                base.LoadConfigurationSection(se);
                foundMatchingConfigElement = true;
    break;
            }
        }
        if (!foundMatchingConfigElement)
            throw new FileNotFoundException("Matching Service Element Configuration does not exist.");
    }
}

Visual Basic Custom ServiceHost to Load Separate Configuration

Public Class SeparateConfigServiceHost
     Inherits System.ServiceModel.ServiceHost
     Public Sub New()
     End Sub

     Public Sub New(serviceType As Type, ParamArray baseAddresses As Uri())
          MyBase.New(serviceType, baseAddresses)
     End Sub

     Public Sub New(singeltonInstance As Object, ParamArray baseAddresses As Uri())
          MyBase.New(singeltonInstance, baseAddresses)
     End Sub


     Protected Overrides Sub ApplyConfiguration()

          ' get the path to the configuration file, you can use this or any other path
          ' you want.
          Dim configFilePath As String = Path.Combine(System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath, [String].Format("{0}.config", Me.Description.Name))

          'If the file does not exist, then just use the base configuration which will read
          'from the web.config
          If Not System.IO.File.Exists(configFilePath) Then
               MyBase.ApplyConfiguration()
          Else
               LoadConfigFromFile(configFilePath)
          End If
     End Sub

     Private Sub LoadConfigFromFile(configFilePath As String)
          Dim filemap = New System.Configuration.ExeConfigurationFileMap()
          filemap.ExeConfigFilename = configFilePath

          Dim config = ConfigurationManager.OpenMappedExeConfiguration(filemap, ConfigurationUserLevel.None)

          Dim serviceModelSectionGroup = System.ServiceModel.Configuration.ServiceModelSectionGroup.GetSectionGroup(config)

          Dim foundMatchingConfigElement As Boolean = False
          For Each se As ServiceElement In serviceModelSectionGroup.Services.Services
               If se.Name = Me.Description.ConfigurationName Then
                    MyBase.LoadConfigurationSection(se)
                    foundMatchingConfigElement = True
                    Exit For
               End If
          Next
          If Not foundMatchingConfigElement Then
               Throw New FileNotFoundException ("Matching Service Element Configuration does not exist.")
          End If
     End Sub
End Class

Create a Custom Service Host Factory

If you create a custom service host, you must also create custom service host factory. The following code is an example of how to do this.

Visual C# Custom ServiceHostFactory

public sealed class CustomServiceHostFactory : ServiceHostFactory
{
public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
{
    return base.CreateServiceHost(constructorString, baseAddresses);
}
 
protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
    return new SeparateSeparateConfigServiceHost(serviceType, baseAddresses);
}
}

Visual Basic Custom ServiceHostFactory

Public NotInheritable Class CustomServiceHostFactory 
     Inherits ServiceHostFactory
     Public Overrides Function CreateServiceHost(constructorString As String, baseAddresses As Uri()) As ServiceHostBase
          Return MyBase.CreateServiceHost(constructorString, baseAddresses)
     End Function

     Protected Overrides Function CreateServiceHost(serviceType As Type, baseAddresses As Uri()) As ServiceHost
          Return New SeparateSeparateConfigServiceHost(serviceType, baseAddresses)
     End Function
End Class

Register the Factory in the SVC File

You must register the custom service host factory in the SVC file. Otherwise, the default service host factory is used.

<%@ ServiceHost Language="C#" Debug="true" Service="MyService" Factory="CustomServiceHostFactory"%>

Note

A custom ServiceHostFactory class cannot be used when the service's InstanceContextMode property is set to Single.

Previous article: Security Considerations and Conclusion

Continue on to the next article: Configuring a Single Service Across Multiple Environments