The WCF team has gone to great lengths to ensure that it doesn’t tie itself down to a single service architecture or naming. This can be best demonstrated by how WCF refers to its metadata. Most users will think of this metadata as WSDL. And in WCF today, it is WSDL. However, what will WSDL be in 5 years? What happens when something better comes along? Or what if we want some metadata for REST which doesn’t use SOAP? Instead, it is just called Metadata.
So what is the this metadata? Metadata is basically to describe how to interact with the service’s endpoints. This includes metadata about:
- The operations a service can perform
- The structure of the data types that the operations work with or return
- Definitions of all the endpoints implemented by the service and the various options needed for each such as security
By default service metadata is turned off. This has been a problem for some people because you have to manually configure the service to publish the metadata. This is by design because metadata can expose parts of your service you don’t want to expose. Many services out in the wild do not want to publish metadata at all. Personally, I am a bit torn about this. Part of me understands why they implemented it this way, but the more common use is to expose metadata always. It seems that the default should be metadata on, with the option to turn metadata off for the more rare occasions. However, the WCF team really wanted it to be "secure by default", which means it needed to be turned off. This can throw off classic WebService developers because in ASMX you needed to opt-out of exposing metadata, while WCF requires opt-in of exposing metadata.
The metadata can be used to generate proxy classes with svcutil.exe, "Add Service Reference" in Visual Studio, wcftestclient.exe, or any other program that can interpret the metadata and build proxies. This data can also by used to pull out specific information about a service such as its’ endpoints, or simply just for reference information about the service.
Exposing Metadata
To expose metadata you need to turn on the serviceMetaData property in the service behavior that the service is implementing. This can be done declaratively through the xml config files, or programmatically directly in your code:
Declaratively
The minimum you need is to add the serviceMetaData node to the serviceBehavior being used by the service:
<serviceBehaviors>
<behavior name = "MetadataBehavior">
<serviceMetadata />
</behavior>
</serviceBehaviors>
In a full configuration file this may look like something like this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name = "MetadataBehavior">
<serviceMetadata httpGetEnabled = "true"/>
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service name = "MyService"
behaviorConfiguration = "MetadataBehavior">
<host>
<baseAddresses>
<add baseAddress =
"http://localhost:8080/MyService"
"net.tcp://localhost:9000/MyService"/>
</baseAddresses>
</host>
<endpoint address = ""
binding = "wsHttpBinding"
contract = "IMyService"/>
<endpoint address = ""
binding = "netTcpBinding"
contract = "IMyService"/>
<endpoint address = "net.tcp://localhost:9000/MyService/mex"
binding = "mexTcpBinding"
contract = "IMetadataExchange"/>
</service>
</services>
</system.serviceModel>
</configuration>
Programmatically
Adding this in code is similar. You need to add a ServiceMetadataBehavior object to the behaviors of the serviceHost:
ServiceHost host = new ServiceHost(
typeof(MyService),
new Uri("http://localhost:8080/MyService"),
new Uri("net.tcp://localhost:9000/MyService"));
host.AddServiceEndpoint(
typeof(IMyService),
new WSHttpBinding(),
"");
host.AddServiceEndpoint(
typeof(IMyService),
new NetTcpBinding(),
"");
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
host.AddServiceEndpoint(
typeof(IMetadataExchange),
mexBinding,
"net.tcp://localhost:9000/MyService/mex");
ServiceMetadataBehavior metadataBehavior =
new ServiceMetadataBehavior();
metadataBehavior.HttpGetEnabled = true;
host.Description.Behaviors.Add(metadataBehavior);
Enabling the serviceMetaData property is enough to expose metadata through all binding types except http and https. For these you have to specify "HttpGetEnabled=true" or "HttpsGetEnabled=true" depending on if you are using http or https. You must expose at least one endpoint for http or https and declare a base address or you will get a runtime exception with HttpGetEnabled or HttpsGetEnabled enabled.
Http and Https can expose metadata simply by appending "?wsdl" to the end of the base address. For any binding type, you need to specify a metadata binding endpoint to retrieve the metadata from. This is done by creating an endpoint that uses the IMetadataExchange interface, a corresponding "mex" binding for the transport (more on this later), and the address where the metadata published. You can also use an IMetadataExchange address for http and https as well, but normally the "?wsdl" site is enough.
There are a few properties of ServiceMetadata:
- ExternalMetadataLocation: A Uri that contains the location of a WSDL file, which is returned to the user in response to WSDL and MEX requests instead of the auto-generated WSDL. When this attribute is not set, the default WSDL is returned. The default is an empty string.
- HttpGetEnabled: A Boolean value that specifies whether to publish service metadata for retrieval using an HTTP/Get request. The default is false. If the httpGetUrl attribute is not specified, the address at which the metadata is published is the service address plus a "?wsdl". For example, if the service address is "http://localhost:8080/MyService", the HTTP/Get metadata address is "http://localhost:8080/MyService?wsdl". If this property is false, or the address of the service is not based on HTTP or HTTPS, “?wsdl” is ignored.
- HttpGetUrl: A Uri that specifies the address at which the metadata is published for retrieval using an HTTP/Get request.
- HttpsGetEnabled: A Boolean value that specifies whether to publish service metadata for retrieval using an HTTPS/Get request. The default is false. If the httpsGetUrl attribute is not specified, the address at which the metadata is published is the service address plus a "?wsdl". For example, if the service address is https://localhost:8080/MyService, the HTTP/Get metadata address is "https://localhost:8080/MyService?wsdl". If this property is false, or the address of the service is not based on HTTP or HTTPS, “?wsdl” is ignored.
- HttpsGetUrl: A Uri that specifies the address at which the metadata is published for retrieval using an HTTPS/Get request.
Metadata Exchange Endpoint
A metadata exchange endpoint is an endpoint that returns back the metadata about the service. You can have as many metadata exchange endpoints as you want. You could have one for each service endpoint, one that all endpoints share, or 100s that just return the same data. Usually one metadata endpoint is sufficient, but it is common to see one for each service endpoint as well.
Like all endpoints it is comprised of the ABCs: Address, Binding, and Contract:
Address
The address is just the Uri that the endpoint can be can found. In a metdata exchange endpoint this is commonly done by appending "mex" to the base address such as "net.tcp://localhost/MyService/mex", but you can use whatever address you choose.
Binding
There are only 4 bindings that are defined to be used as a metadata endpoint:
- mexHttpBinding: Specifies the settings for a binding used for the WS-MetadataExchange (WS-MEX) message exchange over HTTP.
- mexHttpsBinding: Specifies the settings for a binding used for the WS-MetadataExchange (WS-MEX) message exchange over HTTPS.
- mexNamedPipesBinding: Specifies the settings for a binding used for the WS-MetadataExchange (WS-MEX) message exchange over named pipe.
- mexTcpBinding: Specifies the settings for a binding used for the WS-MetadataExchange (WS-MEX) message exchange over TCP
You can use any of these in the configuration file to define metadata endpoints, such as:
<endpoint address = "net.tcp://localhost:9000/MyService/mex"
binding = "mexTcpBinding"
contract = "IMetadataExchange"/>
In code however, these bindings must be created through the static MetadataExchangeBindings class. It has the following 4 methods:
You can use any of these methods in your code go generate the bindings for the corresponding transport, such as:
Binding mexBinding = MetadataExchangeBindings.CreateMexTcpBinding();
host.AddServiceEndpoint(
typeof(IMetadataExchange),
mexBinding,
"net.tcp://localhost:9000/MyService/mex");
Contract
A contract is just an interface that the endpoint must follow. So, what is this IMetadataExchange interface? It only has 3 methods:
- BeginGet: Starts an asynchronous retrieval of metadata.
- EndGet: Concludes the retrieval of metadata.
- Get: Returns the service metadata
Since BeginGet and EndGet are just an asynchronous version of Get, what does Get do? Well, as you may have guessed, it just returns the metadata for the service.
REST and webHttpBinding
So what about REST based services using webHttpBinding? No where is there defined a way to get metadata about it. Well, since it is REST, there is no WSDL that defines the service and no built in way to generate metadata about it. One common tactic is to have a "Documentation" method on your service that returns a stream of data that is an HTML page which describes how to work with the REST service. This isn’t really metadata that can be consumed to generate a proxy class, but it is informative to other developers and consumers of your REST services.
