Metadata Exchange

A service has two options for publishing its metadata. You can provide the metadata over the HTTP-GET protocol, or you can use a dedicated endpoint, discussed later. WCF can provide the metadata over HTTP-GET automatically for your service; all you need is to enable it by adding an explicit service behavior. Behaviors are described in subsequent chapters. For now, all you need to know is that a behavior is a local aspect of the service, such as whether or not it wants to exchange its metadata over HTTP-GET. You can add this behavior administratively or programmatically. Example 1-10 shows a host application config file, where both hosted services reference a custom behavior section that enables the metadata exchange over HTTP-GET. The address the clients need to use for the HTTP-GET is the registered HTTP base address of the service. You can also specify in the behavior an external URL for this purpose.

Example 1-10. Enabling metadata exchange behavior using a config file

<system.serviceModel>
   <services>
      <service name = "MyService"behaviorConfiguration = "MEXGET">
         <host>
            <baseAddresses>
               <add baseAddress = "http://localhost:8000/"/>
            </baseAddresses>
         </host>
         ...
      </service>
      <service name = "MyOtherService" behaviorConfiguration = "MEXGET">
         <host>
            <baseAddresses>
               <add baseAddress = "http://localhost:8001/"/>
            </baseAddresses>
         </host>
         ...
      </service>
   </services>
   <behaviors>
      <serviceBehaviors>
         <behavior name = "MEXGET">
            <serviceMetadata httpGetEnabled = "true"/>
         </behavior>
      </serviceBehaviors>
   </behaviors>
</system.serviceModel>

Once you have enabled the metadata exchange over HTTP-GET, you can navigate to the HTTP base address (if present) using a browser. If all is well, you will get a confirmation page, such as the one shown in Figure 1-6, letting you know that you have successfully hosted a service. The confirmation page is unrelated to IIS hosting, and you can use a browser to navigate to the service address even when self-hosting.

A service confirmation page

Figure 1-6. A service confirmation page

Enabling Metadata Exchange Programmatically

To programmatically enable the metadata exchange over HTTP-GET, you first need to add the behavior to the collection of behaviors the host maintains for the service type. The ServiceHostBase class offers the Description property of the type ServiceDescription:

public abstract class ServiceHostBase : ...
{
   public ServiceDescription Description
   {get;}
   //More members
}

The service description, as its name implies, is the description of the service with all its aspects and behaviors. ServiceDescription contains a property called Behaviors of the type KeyedByTypeCollection<I> with IServiceBehavior as the generic parameter:

public class KeyedByTypeCollection<I> : KeyedCollection<Type,I>
{
   public T Find<T>( );
   public T Remove<T>( );
   //More members
}
public class ServiceDescription
{
   public KeyedByTypeCollection<IServiceBehavior> Behaviors
   {get;}
}

IServiceBehavior is the interface that all behavior classes and attributes implement. KeyedByTypeCollection<I> offers the generic method Find<T>( ), which returns the requested behavior if it is in the collection, and null otherwise. A given behavior type can only be found in the collection at most once. Example 1-11 shows how to enable the behavior programmatically.

Example 1-11. Enabling the metadata exchange behavior programmatically

ServiceHost host = new ServiceHost(typeof(MyService));

ServiceMetadataBehavior metadataBehavior;
metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>( );
if(metadataBehavior == null)
{
   metadataBehavior = new ServiceMetadataBehavior( );
   metadataBehavior.HttpGetEnabled = true;
   host.Description.Behaviors.Add(metadataBehavior);
}

host.Open( );

First the hosting code verifies that no MEX endpoint behavior was provided in the config file by calling the Find<T>( ) method of KeyedByTypeCollection<I> using ServiceMetadataBehavior as the type parameter. ServiceMetadataBehavior is defined in the System.ServiceModel.Description:

public class ServiceMetadataBehavior : IServiceBehavior
{
   public bool HttpGetEnabled
   {get;set;}
   //More members
}

If the returned behavior is null, the hosting code creates a new ServiceMetadataBehavior, sets HttpGetEnabled to true, and adds it to the behaviors in the service description.

The Metadata Exchange Endpoint

The service can also publish its metadata over a special endpoint called the metadata exchange endpoint, sometimes referred to as the MEX endpoint. Figure 1-7 shows a service with business and a metadata exchange endpoint. However, you typically do not show the metadata exchange endpoint in your design diagrams.

The metadata exchange endpoint

Figure 1-7. The metadata exchange endpoint

That endpoint supports an industry standard for exchanging metadata, represented in WCF by the IMetadataExchange interface:

[ServiceContract(...)]
public interface IMetadataExchange
{
   [OperationContract(...)]
   Message Get(Message request);
   //More members
}

The details of this interface are inconsequential. Like most of these industry standards, it is difficult to implement. Fortunately, WCF can have the service host automatically provide the implementation of IMetadataExchange and expose the metadata exchange endpoint. All you need to do is designate the address and the binding to use, as well as add the service metadata behavior. For the bindings, WCF provides dedicated binding transport elements for the HTTP, HTTPS, TCP, and IPC protocols. For the address, you can provide a full address or use any of the registered base addresses. There is no need to enable the HTTP-GET option, but there is no harm either. Example 1-12 shows a service that exposes three MEX endpoints, over HTTP, TCP, and IPC. For demonstration purposes, the TCP and IPC MEX endpoints use relative addresses and the HTTP one uses an absolute address.

Example 1-12. Adding MEX endpoints

<service name = "MyService" behaviorConfiguration = "MEX">
   <host>
      <baseAddresses>
         <add baseAddress = "net.tcp://localhost:8001/"/>
         <add baseAddress = "net.pipe://localhost/"/>
      </baseAddresses>
   </host>
   <endpoint
      address  = "MEX"
      binding  = "mexTcpBinding"
      contract = "IMetadataExchange"
   />
   <endpoint
      address  = "MEX"
      binding  = "mexNamedPipeBinding"
      contract = "IMetadataExchange"
   />
   <endpoint
      address  = "http://localhost:8000/MEX"
      binding  = "mexHttpBinding"
      contract = "IMetadataExchange"
   />
</service>
<behaviors>
   <serviceBehaviors>
      <behavior name = "MEX">
         <serviceMetadata/>
      </behavior>
   </serviceBehaviors>
</behaviors>

Adding MEX endpoints programmatically

Like any other endpoint, you can only add a metadata exchange endpoint programmatically before opening the host. WCF does not offer a dedicated binding type for the metadata exchange endpoint. Instead, you need to construct a custom binding that uses the matching transport binding element, and then provide that binding element as a construction parameter to an instance of a custom binding. Finally, call the AddServiceEndpoint( ) method of the host providing it with the address, the custom binding, and the IMetadataExchange contract type. Example 1-13 shows the code required to add a MEX endpoint over TCP. Note that before adding the endpoint you must verify the presence of the metadata behavior.

Example 1-13. Adding TCP MEX endpoint programmatically

BindingElement bindingElement = new TcpTransportBindingElement( );
CustomBinding binding = new CustomBinding(bindingElement);

Uri tcpBaseAddress = new Uri("net.tcp://localhost:9000/");
ServiceHost host = new ServiceHost(typeof(MyService),tcpBaseAddress);

ServiceMetadataBehavior metadataBehavior;
metadataBehavior = host.Description.Behaviors.Find<ServiceMetadataBehavior>( );
if(metadataBehavior == null)
{
   metadataBehavior = new ServiceMetadataBehavior( );
   host.Description.Behaviors.Add(metadataBehavior);
}
host.AddServiceEndpoint(typeof(IMetadataExchange),binding,"MEX");
host.Open( );

Streamlining with ServiceHost<T>

You can extend ServiceHost<T> to automate the code in Examples 1-11 and 1-13. ServiceHost<T> offers the EnableMetadataExchange Boolean property that you can call to both add the HTTP-GET metadata behavior and the MEX endpoints:

public class ServiceHost<T> : ServiceHost
{
   public bool EnableMetadataExchange
   {get;set;}
   public bool HasMexEndpoint
   {get;}
   public void AddAllMexEndPoints( );
   //More members
}

When set to true, EnableMetadataExchange adds the metadata exchange behavior, and if no MEX endpoint is available, EnableMetadataExchange adds a MEX endpoint for each registered base address scheme. Using ServiceHost<T>, Examples 1-11 and 1-13 are reduced to:

ServiceHost<MyService> host = new ServiceHost<MyService>( );
host.EnableMetadataExchange = true;
host.Open( );

ServiceHost<T> also offers the HasMexEndpoint Boolean property, which returns true if the service has any MEX endpoint (regardless of transport protocol), and the AddAllMexEndPoints( ) method, which adds a MEX endpoint for each registered base address of the scheme type of HTTP, TCP, or IPC. Example 1-14 shows the implementation of these methods.

Example 1-14. Implementing EnableMetadataExchange and its supporting methods

public class ServiceHost<T> : ServiceHost
{
   public bool EnableMetadataExchange
   {
      set
      {
         if(State == CommunicationState.Opened)
         {
            throw new InvalidOperationException("Host is already opened");
         }
         ServiceMetadataBehavior metadataBehavior;
         metadataBehavior = Description.Behaviors.Find<ServiceMetadataBehavior>( );
         if(metadataBehavior == null)
         {
            metadataBehavior = new ServiceMetadataBehavior( );
            metadataBehavior.HttpGetEnabled = value;
            Description.Behaviors.Add(metadataBehavior);
         }
         if(value == true)
         {
            if(HasMexEndpoint == false)
            {
               AddAllMexEndPoints( );
            }
         }
      }
      get
      {
         ServiceMetadataBehavior metadataBehavior;
         metadataBehavior = Description.Behaviors.Find<ServiceMetadataBehavior>( );
         if(metadataBehavior == null)
         {
            return false;
         }
         return metadataBehavior.HttpGetEnabled;
      }
   }
   public bool HasMexEndpoint
   {
      get
      {
         Predicate<ServiceEndpoint> mexEndPoint= delegate(ServiceEndpoint endpoint)
                                                 {
                return endpoint.Contract.ContractType == typeof(IMetadataExchange);
                                                 };
         return Collection.Exists(Description.Endpoints,mexEndPoint);
      }
   }
   public void AddAllMexEndPoints( )
   {
      Debug.Assert(HasMexEndpoint == false);

      foreach(Uri baseAddress in BaseAddresses)
      {
         BindingElement bindingElement = null;
         switch(baseAddress.Scheme)
         {
            case "net.tcp":
            {
               bindingElement = new TcpTransportBindingElement( );
               break;
            }
            case "net.pipe":
            {...}
            case "http":
            {...}
            case "https":
            {...}
         }
         if(bindingElement != null)
         {
            Binding binding = new CustomBinding(bindingElement);
            AddServiceEndpoint(typeof(IMetadataExchange),binding,"MEX");
         }
      }
   }
}

EnableMetadataExchange verifies that the host has not been opened yet using the State property of the CommunicationObject base class. EnableMetadataExchange does not override the configured value from the config file and will only set the value if no metadata behavior was found in the config file. When reading the value, the property checks if a value is configured. If no metadata behavior is configured at all, EnableMetadataExchange returns false, and if a behavior is configured, it simply returns its HttpGetEnabled value. The HasMexEndpoint property uses an anonymous method[*] to initialize a predicate that checks if a given endpoint’s contract is indeed IMetadataExchange. The property then uses my static Collection class and calls the Exists( ) method, providing the collection of endpoints available with the service host. Exists( ) invokes the predicate on each item in the collection, and returns true if any one of the items in the collection satisfies the predicate (that is, if the invocation of the anonymous method returned true), and false otherwise. The AddAllMexEndPoints( ) method iterates over the BaseAddresses collection. For each base address found, it creates a matching MEX transport-binding element, creates a custom binding, and uses that, as in Example 1-13 to add the endpoint.

The Metadata Explorer

The metadata exchange endpoint provides metadata that describes not just contracts and operations, but also information about data contracts, security, transactions, reliability, and faults. To visualize the metadata of a running service I developed the Metadata Explorer tool, available along with the rest of the source code of this book. Figure 1-8 shows the Metadata Explorer reflecting the endpoints of Example 1-7. To use the Metadata Explorer, simply provide it with the HTTP-GET address or the metadata exchange endpoint of the running service to reflect the returned metadata.

The Metadata Explorer

Figure 1-8. The Metadata Explorer



[*] If you are unfamiliar with anonymous methods, see my MSDN Magazine article “Create Elegant Code with Anonymous Methods, Iterators, and Partial Classes,” May 2004.

Get Programming WCF Services now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.