By default, the service will not publish its metadata. However, this does not preclude clients that have obtained the metadata via some other mechanism from invoking operations on the service. A service that does not publish metadata is the WCF analogy to an internal class in .NET.
Publishing your service's metadata involves significant effort, since you have to convert CLR types and binding information into WSDL or some other low-level representation, and all that effort does not add any business value. But fortunately, the host already knows everything there is to know about your service and its endpoints, so it can publish the metadata for you if explicitly instructed to do so.
There are two options for publishing a service's metadata: you can provide the metadata over HTTP-GET, a simple text-based protocol that most platforms support, or you can use a dedicated endpoint (as discussed later).
WCF can provide the metadata for your service over HTTP-GET automatically; all you need to do is enable it by adding an explicit service behavior. Behaviors are described fully 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 have the host publish 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 metadata publishing over HTTP-GET.
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"> <serviceMetadatahttpGetEnabled = "true"
/> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
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:
<behavior name = "MEXGET">
<serviceMetadata httpGetEnabled = "true" httpGetUrl
= "http://localhost:8002"/>
</behavior>
Once you have enabled the metadata exchange over HTTP-GET, you can navigate to the address you configured (the HTTP base address, by default, or an explicit address) using a browser. If all is well, you will get a confirmation page like 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.
To programmatically enable 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<T>
, with IServiceBehavior
as the generic type parameter:
public class KeyedByTypeCollection<T> : KeyedCollection<Type,T> { 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 be found in the collection at most once.
Example 1-11 shows how to enable the metadata exchange 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) { Debug.Assert(BaseAddresses.Any(uri=>uri.Scheme == "http")); metadataBehavior = new ServiceMetadataBehavior( ); metadataBehavior.HttpGetEnabled = true; host.Description.Behaviors.Add(metadataBehavior); } host.Open( );
Note the defensive manner in which the hosting code first verifies that no metadata
behavior was provided in the config file, by calling the Find<T>( )
method of KeyedByTypeCollection<I>
and using ServiceMetadataBehavior
as the type parameter. ServiceMetadataBehavior
is defined in the System.ServiceModel.Description
namespace:
public class ServiceMetadataBehavior : IServiceBehavior { public bool HttpGetEnabled {get;set;} public Uri HttpGetUrl {get;set;} //More members }
If the returned behavior is null
, it means the
config file contains no metadata behavior. In this case, the hosting code creates a new
ServiceMetadataBehavior
instance, sets HttpGetEnabled
to true
, and
adds it to the behaviors in the service description. By defensively checking for the
presence of the behavior first, the hosting code avoids overriding the config file and
always allowing the administrator to tweak the behavior or turn it on or off. Note also
that the code asserts the presence of an HTTP base address. The assertion uses the LINQ
Any( )
query on an inline lambda predicate that
checks whether the base addresses collection contains an HTTP base address.
Publishing metadata over HTTP-GET is merely a WCF feature; there are no guarantees that other platforms you interact with will support it. There is, however, a standard way for publishing 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 endpoints and a metadata exchange endpoint. However, you typically do not show the metadata exchange endpoint in your design diagrams.
The MEX 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, but 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, and 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 in doing so. 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 endpoint uses an absolute
address.
Example 1-12. Adding MEX endpoints
<services> <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> </services> <behaviors> <serviceBehaviors> <behavior name = "MEX"><serviceMetadata/>
</behavior> </serviceBehaviors> </behaviors>
Tip
Note in Example 1-12 that all you have to do to have the
host implement the MEX endpoint for your service is include the serviceMetadata
tag in the behavior. If you do 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 IMetadataExchange
for advanced interoperability needs.
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 a 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( );
You can extend ServiceHost<T>
to automate
the code in Example 1-11 and Example 1-13. ServiceHost<T>
offers overloaded versions of the EnableMetadataExchange( )
method, which you can call to both
publish metadata over HTTP-GET and add the MEX endpoints:
public class ServiceHost<T> : ServiceHost { public bool EnableMetadataExchange( ); public bool EnableMetadataExchange(bool enableHttpGet); public bool HasMexEndpoint {get;} public void AddAllMexEndPoints( ); //More members }
The parameter-less EnableMetadataExchange( )
publishes metadata over HTTP-GET, and if no MEX endpoint is available, EnableMetadataExchange( )
adds a MEX endpoint for each
registered base address scheme. Using ServiceHost<T>
, Example 1-11 and Example 1-13 are reduced to:
ServiceHost<MyService> host = new ServiceHost<MyService>( ); host.EnableMetadataExchange( ); host.Open( );
EnableMetadataExchange( )
will not override the
behavior in the config file if one is present.
EnableMetadataExchange( )
can also accept a
parameter controlling publishing over HTTP-GET (that is, MEX endpoints only).
ServiceHost<T>
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 void EnableMetadataExchange( ) { EnableMetadataExchange(true); } public void EnableMetadataExchange(bool enableHttpGet) { if(State == CommunicationState.Opened) { throw new InvalidOperationException("Host is already opened"); } ServiceMetadataBehavior metadataBehavior = Description.Behaviors.Find<ServiceMetadataBehavior>( ); if(metadataBehavior == null) { metadataBehavior = new ServiceMetadataBehavior( ); Description.Behaviors.Add(metadataBehavior); if(BaseAddresses.Any((uri)=>uri.Scheme == "http")) { metadataBehavior.HttpGetEnabled = enableHttpGet; } } AddAllMexEndPoints( ); } public bool HasMexEndpoint { get { return Description.Endpoints.Any( endpoint=>endpoint.Contract.ContractType == typeof(IMetadataExchange)); } } 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. The HasMexEndpoint
property uses the LINQ Any( )
query on an inline lambda predicate that checks
whether a given endpoint's contract is indeed IMetadataExchange
. Any( )
invokes the
predicate on each endpoint in the collection, returning true
if any one of the endpoints in the collection satisfies the predicate
(that is, if the invocation of the lambda expression 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 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, which is 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, and it will reflect the returned metadata.
Get Programming WCF Services, 2nd Edition 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.