Archive for September, 2010

Metadata Exchange options for WCF

September 5, 2010

There are two options for publishing metadata from a WCF service.
By default, the services metadata is not published.
In order to make the services information about itself public, you must do either of the following.

  1. Provide the metadata over HTTP-GET automatically.
  2. Use a dedicated endpoint.

Thankfully, the ServiceHost already knows all about the services metadata.

Metadata exchange (whether using HTTP-GET or a dedicated endpoint) can be enabled Programmatically or administratively.
I find the second option by far the most popular, due to being able to modify, turn on/off after compilation, deployment.
The examples I’m going to show are how to enable the metadata exchange administratively (via the config files).

Bear in mind, that HTTP-GET is a WCF feature (may not be supported by all platforms).
Where as using a dedicated metadata exchange endpoint is an industry standard (WS-MetadataExchange).

Often the best way of explaining something is by example, so that’s what I’ll do here.
Most of the examples are shown using the HTTP transport.
Although, I also show the TCP and IPC transports using the mexTcpBinding and mexNamedPipeBinding respectively.

The solution layout looks like this

We’re pretty much just going to focus on the config files in the services project, that’s ServiceConsoleHost.

HTTP-GET examples

AppUsingHTTP-GET1.config

<?xml version="1.0" encoding="utf-8" ?>
  <configuration>
    <system.serviceModel>
      <services>
        <!--service must reference the custom behavior-->
        <service name="Tasks.Services.TaskManagerService"
            behaviorConfiguration="Tasks.Services.TaskServiceBehavior">
          <host>
            <baseAddresses>
              <!--now we browse to the baseAddress to get out metadata-->
              <!--By default, the address the clients need to use for HTTP-GET is the registered HTTP base address of the service.
              If the host is not configured with an HTTP base address, loading the service will throw an exception.
              You can also specify a different address (or just a URI appended to the HTTP base address)
              at which to publish the metadata by setting the httpGetUrl property of the serviceMetadata tag-->
              <add baseAddress = "http://localhost:8080/Tasks" />
            </baseAddresses>
          </host>
          <endpoint address ="TaskManager"
              binding="basicHttpBinding"
              contract="Tasks.Services.ITaskManagerService" />
        </service>
      </services>
      <behaviors>
        <serviceBehaviors>
          <behavior name="Tasks.Services.TaskServiceBehavior">
            <!--publishing metadata over HTTP-GET.
            explicit service behavior must be added.
            no metadata endpoint needed-->
            <serviceMetadata httpGetEnabled="true"/>
            <serviceDebug includeExceptionDetailInFaults="True" />
          </behavior>
        </serviceBehaviors>
      </behaviors>
    </system.serviceModel>
</configuration>

AppUsingHTTP-GET2.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <!--service must reference the custom behavior-->
      <service name="Tasks.Services.TaskManagerService"
          behaviorConfiguration="Tasks.Services.TaskServiceBehavior">
        <host>
          <baseAddresses>
            <!--now we browse to the baseAddress to get out metadata-->
            <add baseAddress = "http://localhost:8080/Tasks" />
          </baseAddresses>
        </host>
        <endpoint address ="TaskManager"
            binding="basicHttpBinding"
            contract="Tasks.Services.ITaskManagerService" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="Tasks.Services.TaskServiceBehavior">
          <!--publishing metadata over HTTP-GET.
          explicit service behavior must be added.
          no metadata endpoint needed-->
          <!--for the httpGetUrl, an absolute or relative address can be used
          MSDN states:
          If the value of HttpGetUrl is absolute,
            the address at which the metadata is published is the value of HttpGetUrl value plus a ?wsdl querystring.
          If the value of HttpGetUrl is relative,
            the address at which the metadata is published is the base address and the service address plus a ?wsdl querystring.-->
          <serviceMetadata httpGetEnabled="true" httpGetUrl="http://localhost:8081"/> <!--generate proxy like this C:\>SvcUtil http://localhost:8081 /out:proxy.cs-->
          <!--<serviceMetadata httpGetEnabled="true" httpGetUrl="my"/>--> <!--generate proxy like this C:\>SvcUtil http://localhost:8080/Tasks/my /out:proxy.cs-->
          <serviceDebug includeExceptionDetailInFaults="True" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

Dedicated Endpoint examples

AppUsingDedicatedEndpoint1.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <!--If the service does not reference the behavior, the host will expect your service to implement IMetadataExchange.
          While this normally adds no value, it is the only way to provide for custom implementation of the IMetadataExchange.-->
      <service name="Tasks.Services.TaskManagerService"
          behaviorConfiguration="Tasks.Services.TaskServiceBehavior">
        <host>
          <baseAddresses>
            <add baseAddress = "http://localhost:8080/Tasks" />
          </baseAddresses>
        </host>
        <endpoint address ="TaskManager"
            binding="basicHttpBinding"
            contract="Tasks.Services.ITaskManagerService" />
        <!--Don't need an address in the mex endpoint as the service uses the baseAddress anyway-->
        <endpoint
            binding="mexHttpBinding"
            contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="Tasks.Services.TaskServiceBehavior">
          <!--It's not necessary to have the ServiceMetadataBehavior's HttpGetEnabled property set to true, as we now use the mex endpoint.-->
          <!--By simply adding the serviceMetadata behavior below and creating an endpoint with a mex binding and using the IMetadataExchange interface,
          WCF has the service host automatically provide the implementation of IMetadataExchange, providing the service references the behavior,
          in order to see serviceMetadata.-->
          <serviceMetadata/>
          <serviceDebug includeExceptionDetailInFaults="True" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

AppUsingDedicatedEndpoint2.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Tasks.Services.TaskManagerService"
          behaviorConfiguration="Tasks.Services.TaskServiceBehavior">
        <host>
          <baseAddresses>
            <!--Don't need a baseAddress in order to get metadata, but if you want to browse the service (not the service's end point),
            you need a baseAddress.-->
          </baseAddresses>
        </host>
        <endpoint address ="http://localhost:8081/TaskManager"
            binding="basicHttpBinding"
            contract="Tasks.Services.ITaskManagerService" />
        <!--To get metadata using SvcUtil.exe:
        svcutil http://localhost:8081/mex /out:myProxy.cs
        or
        svcutil http://localhost:8081/ /out:myProxy.cs-->
        <endpoint
            address="http://localhost:8081/mex"
            binding="mexHttpBinding"
            contract="IMetadataExchange" />
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="Tasks.Services.TaskServiceBehavior">
          <serviceMetadata/>
          <serviceDebug includeExceptionDetailInFaults="True" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>
</configuration>

AppUsingDedicatedEndpoint3.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <services>
      <service name="Tasks.Services.TaskManagerService"
          behaviorConfiguration="Tasks.Services.TaskServiceBehavior">
        <host>
          <baseAddresses>
            <add baseAddress="net.tcp://localhost:8080/"/>
            <add baseAddress="net.pipe://localhost/"/>
            <!--The http baseAddress can reside here or be removed and added to the mex endpoint as an absolute address-->
            <add baseAddress="http://localhost:8081/"/>
          </baseAddresses>
        </host>
        <endpoint address ="TaskManager"
            binding="basicHttpBinding"
            contract="Tasks.Services.ITaskManagerService" />
        <!--To get metadata using SvcUtil.exe:
        svcutil net.tcp://localhost:8080/mex /out:myProxy.cs
        or
        svcutil net.tcp://localhost:8080/ /out:myProxy.cs-->
        <endpoint
            address="mex"
            binding="mexTcpBinding"
            contract="IMetadataExchange" />
        <!--To get metadata using SvcUtil.exe:
        svcutil net.pipe://localhost/mex /out:myProxy.cs
        or
        svcutil net.pipe://localhost/ /out:myProxy.cs-->
       <endpoint
           address="mex"
           binding="mexNamedPipeBinding"
           contract="IMetadataExchange" />
       <!--Don't need an address in the mex endpoint as the service uses the baseAddress anyway-->
       <!--To get metadata using SvcUtil.exe:
       svcutil http://localhost:8081/ /out:myProxy.cs-->
       <endpoint
           binding="mexHttpBinding"
           contract="IMetadataExchange" />
     </service>
   </services>
   <behaviors>
     <serviceBehaviors>
       <behavior name="Tasks.Services.TaskServiceBehavior">
         <serviceMetadata/>
         <serviceDebug includeExceptionDetailInFaults="True" />
       </behavior>
     </serviceBehaviors>
   </behaviors>
 </system.serviceModel>
</configuration>

In order to use any of the above shown config files,
just rename and remove any text in the App.config file name between the App and the .config.
So for example AppUsingHTTP-GET1.config would become App.config

Click on link below to download full source code.

MetadataExchangeExamples.zip

This solution was created in Visual Studio 2008.