Configuration Strategies
Applies to: Windows Communication Foundation
Published: June 2011
Author: Alex Culp
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
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