Dynamically modifying client endpoints in WCF

I was doing some contract work for a client building out a SOA backend for them. It consisted of some WCF services that all talked to one another. Due to how the client had set up their deployment process, we had to dynamically load these client endpoint address from an external XML file at runtime, instead of setting them inside the web.config.
In other words, we needed a way to dynamically change the address attribute:

<system.serviceModel>
    <client>
        <endpoint address="http://someaddress.com/webservice"
          binding="ws2007HttpBinding" bindingConfiguration="WebService_ws2007HttpBinding"
          contract="WebService.ServiceContracts.IWebService" name="ws2007HttpBinding_IWebService" /> 
    </client> 
</system.serviceModel> 

WCF allows you to programmatically configure any System.ServiceModel setting in the web.config, so the challenge was to inject the endpoint before the call was actually made. Normally, you can pass in the endpoint address to the channel factory constructor and be done with it. However, the service in question we needed to modify was a simple passthrough WCF router. There was no code to speak of, so in order to modify the client endpoints we decided to do so in a service behavior.

The first task was to figure out how to access the endpoints themselves. At first, I tried:

    var serviceModelSection = ConfigurationManager.GetSection("system.serviceModel"); 
    ClientSection clientSection = serviceModelSection.GetSection("client" ) as ClientSection;
    ChannelEndpointElementCollection endpointCollection = clientSection.Endpoints;

However, when I actually tried to edit the endpoints inside ChannelEndpointElementCollection, I got an error: “The configuration is read only”. After searching online, I tried using WebConfigurationManager.OpenWebConfiguration instead:

       Configuration webConfig = WebConfigurationManager.OpenWebConfiguration("~" );
       ClientSection clientSection = webConfig.GetSection("system.serviceModel/client" ) as ClientSection;
       ChannelEndpointElementCollection endpointCollection = clientSection.Endpoints;

       //dynamically load the URI here
       Uri serviceAddress = “http://sometempuri.org”;

       endpointCollection[0].Address = new EndpointAddress(serviceAddress);         
       webConfig.Save();

This option was a non starter because webConfig.Save() literally saves the actual web.config file itself. This causes the application pool to recycle, and since it edits the physical file, the changes made won’t apply to the current request.

Ultimately, we ended up implementing IEndpointBehavior interface. The IEndpointBehavior interface has an ApplyClientBehavior method, that takes as its parameter a client service endpoint. This method fires only once for the lifetime of the application for each client endpoint defined, which is exactly what we wanted. The following sample code demonstrates how this service behavior can be used to dynamically set the EndpointAddress for the client endpoint.

public class WebServiceEndpointBehavior : IEndpointBehavior
{
    public void Validate(ServiceEndpoint endpoint)
    {
           
    }

    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
           
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {           
        Uri serviceAddress = new Uri("http://sometempuri.org ");   //dynamically load URL here
            endpoint.Address = new EndpointAddress(serviceAddress);       
    }                                                
}        

From here, it was just a matter of coding the behavior extension element that loads the behavior, as well wiring in this extension element into the web.config. Here are the relevant snippets:

namespace WebService
{
    public class WebServiceEndpointBehaviorExtensionElement: BehaviorExtensionElement
    {
        protected override object CreateBehavior()
        {
            return new WebServiceEndpointBehavior ();
        }

        public override Type BehaviorType
        {
            get { return typeof (WebServiceEndpointBehavior ); }
        }
    }
}
  <endpointBehaviors>
        <behavior name="updateEndpointAddress">
          <webserviceEndpointBehavior/>
        </behavior>
<extensions>
    <behaviorExtensions>
        <add name="webserviceEndpointBehavior" type="WebService.WebServiceEndpointBehaviorExtensionElement, WebService" />
    </behaviorExtensions>
</extensions>

Leave a Reply

Your email address will not be published. Required fields are marked *