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>

CredentialCache.DefaultNetworkCredentials is empty and so is Thread.CurrentPrincipal.Identity.Name

I was working on a simple console application in C# that issued HTTP DELETE requests to WebDAV to delete expired documents from the file system. Once completed, this was to run periodically as a job. However, I kept getting back 401 Unauthorized on the DELETE requests. While troubleshooting the issue, I went down the rabbit hole and learned some interesting things. I was passing in CredentialCache.DefaultNetworkCredentials as my HttpRequest credentials. So as a sanity check, I tried viewing it in the debugger to make sure the program was passing in my credentials, only to find that CredentialCache.DefaultNetworkCredentials.UserName was blank.

Well, it turns out that you can’t actually view the credentials unless you set them manually yourself. According to the MSDN documentation:

“The ICredentials instance returned by DefaultCredentials cannot be used to view the user name, password, or domain of the current security context.”

So I tried checking the value of Thread.CurrentPrincipal.Identity.Name instead. This was blank as well. Upon further reading, I determined that this was due to the principal policy not being explicitly set to WindowsPrincipal. Once I did so, Thread.CurrentPrincipal.Identity.Name correctly displayed my windows login ID:

AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
Console.WriteLine(Thread.CurrentPrincipal.Identity.Name);

It is helpful to review what an application domain is before proceeding. In Windows, every application runs as its own process, complete with its own set of resources. By isolating applications via processes, this minimizes the risk that one badly coded application can negatively impact others. In the Common Language Runtime, application domains provide an even more granular level of isolation. A single process (the application host) can run multiple application domains, each with the same level of isolation that separate processes would have, minus any of the overhead.
Because every app domain is separate from one another, each has its own Principal object. This object represents the current security context that the code is running as. The PrincipalPolicy is an enum that indicates how this principal object is to be created for the given app domain. Setting it to WindowsPrincipal will map the principal object to the Windows user that the application host is executing as. By default, the PrincipalPolicy will be UnauthenticatedPrincipal, which will set Name to empty string.

After doing some more digging, I also found out that I could use WindowsIdentity.GetCurrent().Name to determine what user the program was executing as:

Console.WriteLine(WindowsIdentity.GetCurrent().Name); 

Having finally proven to myself that my program was running as the correct user, I eventually figured out the issue. It was completely unrelated to the code of course; I had simply forgotten to enable Windows Authentication in IIS. I didn’t mind the time sink, as it proved to be quite educational.