Loading the WCF configuration from different files on the client side

A common problem in WCF that many people face is the impossibility of loading the client configuration from different configuration files. This is a common scenario  when the developer wants to deploy some binaries with along with an independent configuration file (Which may be in a resource file also) to avoid modifying the main configuration file. A weeks ago, I described a easy workaround to use the external configuration files through the use of the configSource attribute (A mechanism provided by .NET). However, that approach requires several configuration files to configure an entire WCF channel, at least three files (client section, bindings and behaviors).

Another approach, which is less common and I will describe in this post, requires some custom code to extend the ChannelFactory<T> class.

The ChannelFactory<T> provides a virtual method "CreateDescription" that can be overridden to create a ServiceEndpoint.

 

//

// Summary:

//    Creates a description of the service endpoint.

//

// Returns:

//    The System.ServiceModel.Description.ServiceEndpoint of the service.

//

// Exceptions:

//   System.InvalidOperatorException:

//    The callback contract is null but the service endpoint requires one that

//    is non-null.

protected override ServiceEndpoint CreateDescription();

The default implementation of this method basically tries to load the endpoint configuration from the default configuration file (The file configured for the default AppDomain). So, the workaround here is to override that method and load the configuration from another file. It seems to be pretty easy to do at first glance, but as you will see next, it requires a lot of plumbing code to make it work.

The first step is to derive our custom channel class from the ChannelFactory<T> class and add an argument in the constructor to specify a different configuration file.

/// <summary>

/// Custom client channel. Allows to specify a different configuration file

/// </summary>

/// <typeparam name="T"></typeparam>

public class CustomClientChannel<T> : ChannelFactory<T>

{

  string configurationPath;

 

  /// <summary>

  /// Constructor

  /// </summary>

  /// <param name="configurationPath"></param>

  public CustomClientChannel(string configurationPath) : base(typeof(T))

  {

    this.configurationPath = configurationPath;

    base.InitializeEndpoint((string)null, null);

  }

As you can see, a call to the method InitialiazeEndpoint of the base class is required. That method will automatically call to our CreateDescription method to configure the service endpoint.

/// <summary>

/// Loads the serviceEndpoint description from the specified configuration file

/// </summary>

/// <returns></returns>

protected override ServiceEndpoint CreateDescription()

{

   ServiceEndpoint serviceEndpoint = base.CreateDescription();

 

   ExeConfigurationFileMap map = new ExeConfigurationFileMap();

   map.ExeConfigFilename = this.configurationPath;

 

   Configuration config = ConfigurationManager.OpenMappedExeConfiguration(map, ConfigurationUserLevel.None);

   ServiceModelSectionGroup group = ServiceModelSectionGroup.GetSectionGroup(config);

 

   ChannelEndpointElement selectedEndpoint = null;

 

   foreach (ChannelEndpointElement endpoint in group.Client.Endpoints)

   {

      if (endpoint.Contract == serviceEndpoint.Contract.ConfigurationName)

      {

        selectedEndpoint = endpoint;

        break;

      }

    }

 

    if (selectedEndpoint != null)

    {

      if (serviceEndpoint.Binding == null)

      {

        serviceEndpoint.Binding = CreateBinding(selectedEndpoint.Binding, group);

      }

 

      if (serviceEndpoint.Address == null)

      {

        serviceEndpoint.Address = new EndpointAddress(selectedEndpoint.Address, GetIdentity(selectedEndpoint.Identity), selectedEndpoint.Headers.Headers);

      }

 

      if (serviceEndpoint.Behaviors.Count == 0 && selectedEndpoint.BehaviorConfiguration != null)

      {

        AddBehaviors(selectedEndpoint.BehaviorConfiguration, serviceEndpoint, group);

      }

 

      serviceEndpoint.Name = selectedEndpoint.Contract;

    }

 

    return serviceEndpoint;

 

}

 

CreateDescription is the core method where we create the service endpoint using the configuration file specified in the constructor method.

And the last step, is to use our custom channel in the client application instead of the base channel provided by WCF.

CustomClientChannel<ICalculator> channel = new CustomClientChannel<ICalculator>("OtherConfig.config");

ICalculator client = channel.CreateChannel();

The code below uses the ICalculator contract  that comes with the WCF sdk samples.

You can download the complete sample here

 

 

 

21 Comments

  • Nice approach to create the client channel by loading a non-default application configuration file!
    However, I just found a tiny problem from your sample that if the endpointConfigurationName is explicitly specified, an InvalidOperationException will be thrown to tell me that the specified endpoint element cannot be found. Any ideas?

  • Hi Eddie, I am not sure about that problem, it must be bug in code since I did not test it very well for all scenarios. I will take a look more in detail, thanks for the feedback!!.

  • I just uploaded a new version of this code that fixes the non-default configurationName bug.

    Thanks
    Pablo.

  • One more small bug.

    In AddBehaviours() function. If number of EndpointBehaviors is 0, the function will fail.

    I had to add

    if (group.Behaviors.EndpointBehaviors.Count == 0)
    return;

    at the beginning of the function to solve the issue.
    ----
    Thanks a lot for the functionality. Works great.

  • Nice article, a excelent way to solved this !!!
    I use this code for testing porpouse, witah a standard service, but I receive a Exception in the CreateDescription() method.
    Then I debug and retouch a line for the call to AddBehaviors , I adding this code at the end of the conodition:
    && selectedEndpoint.BehaviorConfiguration != "")
    Because when the behavior isn't set, the behavior it's empty, then the app try to add the configuration and trhow a exception.
    The article it's very good and usefull, thanks.
    Patricio.

  • Is the source code for the sample still available? The posted URL seems to be down...
    Thanks!
    -Eugene

  • Extremely useful information. With some minor changes I could use this solution to make a C++ client to talk with WCF Server

  • You rock man.... the WCF team needs to solve this limitation.. I'm writing addons, and can't modify the applications config file.. thanks a ton!

  • Excellent! Thanks for that, I'm implementing a plug-in component that uses WCF and the main exe does not need to know anything about the endpoint or even WCF for that matter.

    I find it hard to believe that this isn't standard functionality in WCF!

    I had to modify the CreateDescription() method to cope with no 'Behavior' entries as follows:

    if (serviceEndpoint.Behaviors.Count == 0 && !string.IsNullOrEmpty(selectedEndpoint.BehaviorConfiguration))
    {
    AddBehaviors(selectedEndpoint.BehaviorConfiguration, serviceEndpoint, group);
    }

  • Excellent!
    Thanks!

    I added:
    1) if (group.Behaviors.EndpointBehaviors.ContainsKey(behaviorConfiguration)){ ...
    2) private Binding GetBinding(string bindingName){switch(bindingName.ToLower())...case "wshttpbinding":return new WSHttpBinding);...}}
    and call it from
    private Binding CreateBinding(string bindingName, ServiceModelSectionGroup group)

  • Is it possible to extend the DuplexChannelFactory in a similar way? I tried it but this didn't work as I can't assign the callBackObject anywhere in a protected/public property/method.

  • This is fantastic article . This code i slightly changed to load from GAC. In my project i faced one issue.I am writing one feature in a class library which is consuming WCF.I could n't load the Config file since my dll is Gaced. With this code i achieved the solution thanks..

  • Thank you, this is awesome.

    I couldn't have my own config attached to the main application so this has saved my life (not literally..!)

  • Thanks so much! I have been searching for hours and came across this. As with one of the commenters above I had to add a check for when there are 0 behaviors, but after that it worked fine.

  • Hi,

    Thanks for the code!

    I have a problem with the length of arrays allowed from the client when using this code. Apparently the creation of the binding does not parse the all the attributes correctly.

    Regards.

  • Amazing code! many thanks!

  • Try this approach: http://vassiltonev.blogspot.com/2009/03/loading-custom-config-file-instead-of.html

  • There's a new API for this in the new .NET 4.0 preview. Class full name: System.ServiceModel.Configuration.ConfigurationChannelFactory.

  • Hi,

    Thanks for your post, helped me a lot. I found another bug tho, besides the one with Behaviour. If in the config file are multiple endpoints each one with its binding, it takes always the first binding no matter of the endpoint.

    Fixed it like this:

    //in CreateDescription() modify

    if (serviceEndpoint.Binding == null)
    {
    serviceEndpoint.Binding = CreateBinding(selectedEndpoint.Binding, selectedEndpoint.BindingConfiguration, serviceModeGroup);
    }
    ...
    if (serviceEndpoint.Behaviors.Count == 0 && !String.IsNullOrEmpty(selectedEndpoint.BehaviorConfiguration))
    {
    AddBehaviors(selectedEndpoint.BehaviorConfiguration, serviceEndpoint, serviceModeGroup);
    }

    ///
    /// Configures the binding for the selected endpoint
    ///
    ///

    ///

    ///
    private Binding CreateBinding(string bindingName, string bindingConfiguration, ServiceModelSectionGroup group)
    {
    IBindingConfigurationElement be = null;
    BindingCollectionElement bindingElementCollection = group.Bindings[bindingName];
    if (bindingElementCollection.ConfiguredBindings.Count > 0)
    {
    foreach (IBindingConfigurationElement bindingElem in bindingElementCollection.ConfiguredBindings)
    {
    if (string.Compare(bindingElem.Name, bindingConfiguration) == 0)
    {
    be = bindingElem;
    break;
    }
    }
    Binding binding = null;
    if (be != null)

    {
    binding = GetBinding(be);
    be.ApplyConfiguration(binding);
    }


    return binding;
    }

    return null;
    }

  • This awesome.. saved time and got functionality done in sharepoint.. great.

  • It Works!!!

    One minor tiny change:
    if (serviceEndpoint.Behaviors.Count == 0 && selectedEndpoint.BehaviorConfiguration != null && selectedEndpoint.BehaviorConfiguration.Length > 0)

    --The best way to handle WCF class library config entries separate from client config file

Comments have been disabled for this content.