Udostępnij za pośrednictwem


Setting up WCF with a load balancer using SSL in the middle

I was onsite today with a customer who was having problems getting WCF to work, and after much wailing and gnashing we finally arrived at a solution so I thought I would blog it. We were getting an EndpointNotFoundException with the error message “There was no channel that could accept the message with action …”. Then we’d get a warning saying “The incoming message is not part of an existing security session”. We went down a load of blind alleys on this before we finally got a solution (thanks go to Zulfiqar for helping a load).

The customer had a couple of servers hosted behind a load balancer. The load balancer was doing the heavy lifting of dealing with the incoming SSL request, and passing this on to the WCF service over HTTP. It took a while for us to get to the right configuration so here it is.

On the server side I defined the service etc. as follows…

 <wsHttpBinding>
  <binding name="myBinding">
    <security mode="None"/>
  </binding>
</wsHttpBinding>

 <behavior name="myBehavior">
  <serviceMetadata httpGetEnabled="True"/>
  <serviceDebug includeExceptionDetailInFaults="False" />
  <applyAddressFilterModeBehavior/> <!-- Discussed later on -->
</behavior>

 <service behaviorConfiguration="myBehavior" 
         name="TestService.MyService">
  <endpoint binding="wsHttpBinding" 
            bindingConfiguration="myBinding" 
            contract="TestService.IMyService"/>
</service>

And the client was defined as follows…

 <wsHttpBinding>
  <binding name="myBinding" >
    <security mode="Transport">
      <transport clientCredentialType="None" />
      <message establishSecurityContext="false" />
    </security>
  </binding>
</wsHttpBinding>

 <endpoint address=https://MyServer/MyService.svc 
          binding="wsHttpBinding" 
          bindingConfiguration="myBinding" 
          contract="TestService.IMyService" 
          name="WSHttpBinding_IMyService" />

The one other thing we needed to do was to add the [ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)] attribute to the service, as this effectively allows us to call to https://myserver/service.svc and have this processed by https://myserver/service.svc. The one hitch in our case was that there were a number of services that would need this same code change, and my customer didn’t want to have to change all of them.

So, I cranked out some code to setup the address filter mode using a custom behavior. The code for that one is below…

 public class ApplyAddressFilterModeBehavior : IServiceBehavior
{
    public void AddBindingParameters(ServiceDescription serviceDescription, 
                                     ServiceHostBase serviceHostBase, 
                                     Collection<ServiceEndpoint> endpoints, 
                                     BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, 
                                      ServiceHostBase serviceHostBase)
    {
        for (int i = 0; i < serviceHostBase.ChannelDispatchers.Count; i++)
        {
            ChannelDispatcher dispatcher = serviceHostBase.ChannelDispatchers[i] as ChannelDispatcher;

            if (null != dispatcher)
            {
                foreach (EndpointDispatcher endpoint in dispatcher.Endpoints)
                {
                    endpoint.AddressFilter = new MatchAllMessageFilter();
                }
            }
        }
    }

    public void Validate(ServiceDescription serviceDescription, 
                         ServiceHostBase serviceHostBase)
    {
    }
}

The behavior is applied to the service by using another class which I defined within the same assembly…

 public class ApplyAddressFilterModeBehaviorElement : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get { return typeof(ApplyAddressFilterModeBehavior); }
    }

    protected override object CreateBehavior()
    {
        return new ApplyAddressFilterModeBehavior();
    }
}

The behavior is added to the service in the config file by adding in a couple of elements…

 <system.serviceModel>
  <extensions>
    <behaviorExtensions>
      <add name="applyAddressFilterModeBehavior" 
           type="Test.Behaviors.ApplyAddressFilterModeBehaviorElement, Test.Behaviors, 
                 Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
    </behaviorExtensions>
  </extensions>

With that defined all I then need to do is update the service behavior to add a reference to the above extension…

 <behaviors>
  <serviceBehaviors>
    <behavior name="myBehavior">
      <serviceMetadata httpGetEnabled="True"/>
      <serviceDebug includeExceptionDetailInFaults="False" />
      <applyAddressFilterModeBehavior/>
    </behavior>
  </serviceBehaviors>
</behaviors>

With all that little lot in place I can now call my service and I don’t get the errors that I had received before. Phew!

Comments

  • Anonymous
    May 16, 2013
    Thanks a lot you gave complete and simple solution. Thank you once again -Viswanath ***