Home > C#, WCF > Dynamically updating WCF endpoint addresses in WSDL meta data

Dynamically updating WCF endpoint addresses in WSDL meta data

Introduction

I have already written some blog articles about WCF and how flexible and extensible it is. A lot of WCF its internal components can be extended or replaced by custom, user developed implementations. Not surprisingly, we can also hook into the WSDL meta data generation.

Recently I was in a situation were I had to develop a self hosted WCF service, but I did not know the target hosting server(s) address(es) upfront and it was impossible for me to change the address in the app.config before deployment. Because WCF requires an absolute address for self hosted services, I had to use localhost. This works fine and gives no issues when calling the service from another computer, but it has one important drawback: WCF will use the localhost address in the generated WSDL meta data. In order to fix this, I created a custom WCF behavior.

WSDL before

IWsdlExportExtension

We can hook into the WSDL meta data generation by implementing the interface IWsdlExportExtension. This interface contains 2 methods: ExportContract and ExportEndpoint. If the class also implements IContractBehavior, the method ExportContract will be invoked by the WCF pipeline and vice versa for IEndpointBehavior and ExportEndpoint.

Because I only wanted to modify the endpoint addresses, I only had to implement the interface IEndpointBehavior and the method ExportContract. I wanted this also as a service behavior, so I also implemented the interface IServiceBehavior and I also extended the behavior from Attribute. This way, the behavior can be easily injected into the WCF pipeline by annotating the service implementation class with the behavior.

The modification itself is very simple; every occurrence of localhost in the endpoint addresses is replaced by the DNS name of the host machine.

public class HostNameAddressBehavior : Attribute, IWsdlExportExtension, IEndpointBehavior, IServiceBehavior
{
    private readonly Regex addressRegex;
    private readonly string hostName;

    public HostNameAddressBehavior()
    {
        hostName = Dns.GetHostName();
        addressRegex = new Regex(@"^(?<scheme>[a-z\.]+)://localhost(?<path>.*)",
            RegexOptions.IgnoreCase);
    }

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

    public void ApplyClientBehavior(ServiceEndpoint endpoint,
        ClientRuntime clientRuntime)
    {
    }

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

    public void Validate(ServiceEndpoint endpoint)
    {
    }

    public void AddBindingParameters(ServiceDescription serviceDescription,
        ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints,
        BindingParameterCollection bindingParameters)
    {
        // If we are added as service behavior, add us to all the endpoints
        foreach (ServiceEndpoint endpoint in endpoints)
        {
            endpoint.Behaviors.Add(this);
        }
    }

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

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

    public void ExportContract(WsdlExporter exporter,
        WsdlContractConversionContext context)
    {
    }

    public void ExportEndpoint(WsdlExporter exporter, WsdlEndpointConversionContext context)
    {
        // Overwrite the address in the service meta data
        EndpointAddress address = context.Endpoint.Address;

        string absoluteUri = address.Uri.AbsoluteUri;
        Match m = addressRegex.Match(absoluteUri);
        if (m.Success)
        {
            string scheme = m.Groups["scheme"].Value;
            string path = m.Groups["path"].Value;

            // Update base address
            Uri newAbsoluteUri = new Uri(string.Format("{0}://{1}{2}",
                scheme, hostName, path));
            context.Endpoint.Address = new EndpointAddress(newAbsoluteUri,
                address.Identity, address.Headers, address.GetReaderAtMetadata(),
                address.GetReaderAtExtensions());
        }
    }
}

If we also want to add this behavior using the config file, we have to create a BehaviorExtensionElement.

public class HostNameAddressBehaviorExtension : BehaviorExtensionElement
{
    public override Type BehaviorType
    {
        get
        {
            return typeof(HostNameAddressBehavior);
        }
    }

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

Now the behavior can be added declarative as endpoint behavior or service behavior using the config file:

</system.serviceModel>

  ...

  <behaviors>
    <serviceBehaviors>
      <behavior name="HelloBehavior">
        <serviceMetadata httpGetEnabled="true" />
        <hostNameAddress />
      </behavior>
    </serviceBehaviors>
  </behaviors>

  <extensions>
    <behaviorExtensions>
      <add name="hostNameAddress" type="HelloConsoleService.HostNameAddressBehaviorExtension, HelloConsoleService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
    </behaviorExtensions>
  </extensions>
</system.serviceModel>

With the behavior in place, the meta data now contains the DNS name instead of “localhost” in the endpoint addresses.

WSDL after

Conclusion

Of course creating the endpoints dynamically in code at runtime could also have solved this problem, but that would result in code that is not reusable and the WCF service programmer would have been responsible for the implementation. By developing this as a behavior, it is reusable across services.

About these ads
Categories: C#, WCF
  1. No comments yet.
  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: