Quick WCF Metadata Publication Walkthrough
I've been having a conversation with Scott Klein, who is busy writing a book on WCF (https://www.amazon.com/Professional-WCF-Pr%20ogramming-Development-Communication/dp/0470089849/sr=1-2/qid=1161195651/ref=sr_1_2/102-4898838-8936%20108?ie=UTF8&s=books). He'd been reading and following the documentation about publishing service metadata and had figured out how to do this in code, but for some reason he just didn't understand the errors resulting from his attempt to use the configuration file to do the same thing. Even the book writers don't get things first time! (But that's likely because I need to do a better job writing about it. ;-)
We got in a conversation about this, and it seemed to me that a couple of things made it difficult for him to pick up how this works.
- In the programmatic version, you don't usually have to think about the base addresses; in config, you usually do.
- The fact that HTTP/GET publication just "happens" but WS-MetadataExchange is a real contract with real endpoints.
With Scott's permission, I'm republishing the majority (minus the embarrasing dumb stuff I wrote here and there and with some small edits for sensibility) of a mail to Scott that walks through the process of taking a standard application configuration file and adding different sorts of metadata publication as we go along until at the end we're publishing service metadata at a HTTP/GET address using the ?wsdl convention AND over both HTTP and TCP using WS-MetadataExchange requests. The critical things to watch for:
- All metadata publication using relative addressing requires supporting base addresses for the ServiceHost; otherwise, you can use absolute addresses.
- HTTP/GET publication is an "artifact" of the ServiceMetadataBehavior and the HttpGetEnabled property.
As a side note, because the ServiceMetadataBehavior is a service behavior, it must be not only specified in the configuration file but also referenced by the \service@behaviorConfiguration attribute.
Here we go (and if you walk through this and catch me in a typo, let me know and I'll fix it!):
From: Ralph Squillace
Sent: Tuesday, October 17, 2006 10:42 AM
To: 'Scott Klein'
Subject: RE: ServiceMetadata -- the big picture and walkthrough<snip stupid stuff I wrote/>
Automatic metadata publication must have an address at which to publish. A ServiceHost is not tied to any particular application domain. This means that unlike ASMX files, which always reside at an IIS virtual directory (for example, https://computer/vDirectory), ServiceHost when opened does not know where it is. It must therefore be passed (or infer from absolute addressing) a “base” or “root” address to which all relative addresses used by the host are appended.
When a Service.svc file is hosted in IIS/WAS, the ServiceHost acquires the base address from IIS/WAS, and is therefore automatically whatever the virtual directory is, just like ASMX, plus the “Service.svc” relative address that points to the service implementation.
In all other cases, you must provide a base address in order for automatic metadata publication to work unless you supply an absolute address in your endpoints or for the httpGetUrl property.
Let’s walk through this. Let’s say you haven’t configured any metadata for your service yet and it looks like the following. What is the address for this service?
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="Microsoft.WCF.Documentation.SampleService"
>
<endpoint
address=""
binding="wsHttpBinding"
contract="Microsoft.WCF.Documentation.ISampleService"
/>
</service>
</services>
</system.serviceModel>
</configuration>
The answer is that this throws an exception on Open because no base address exists to create this class unless hosted in IIS/WAS, in which case the service is hosted at the virtual directory. In all other cases, this service throws.
We **could** do this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="Microsoft.WCF.Documentation.SampleService"
>
<endpoint
address="https://computer/Services/SampleService"
binding="wsHttpBinding"
contract="Microsoft.WCF.Documentation.ISampleService"
/>
</service>
</services>
</system.serviceModel>
</configuration>
Now the service WILL be published, but at https://computer/Services/SampleService because we specified an absolute address. Trick question: what is the base address? The answer is that there isn’t one here. There may be one passed to the ServiceHost programmatically, but let's assume that's not the case here.
OK, this runs, now let’s add metadata support for HTTP/GET. We do this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="Microsoft.WCF.Documentation.SampleService"
>
<endpoint
address="https://computer/Services/SampleService"
binding="wsHttpBinding"
contract="Microsoft.WCF.Documentation.ISampleService"
/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="metadataSupport">
<serviceMetadata httpGetEnabled="true" httpGetUrl=""/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
What has changed? The ServiceMetadataBehavior is loaded and we’ve told it to publish HTTP/GET metadata at the address baseHttpAddress + httpGetUrl (because the address we’ve specified there is relative) + “?wsdl”. There are two problems here. First, we haven’t specified this behavior for any service in the configuration file yet, so it’s loaded but not invoked. Second, even when we specify the “metadataSupport” behavior in <service behaviorConfiguration=”metadataSupport”/> property, we’ll throw because – of course – the host does not HAVE an HTTP-based base address. How can we make this work? Well, we can specify an absolute address. Like so:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="Microsoft.WCF.Documentation.SampleService"
behaviorConfiguration=”metadataSupport”
>
<endpoint
address="https://computer/Services/SampleService"
binding="wsHttpBinding"
contract="Microsoft.WCF.Documentation.ISampleService"
/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="metadataSupport">
<serviceMetadata httpGetEnabled="true" httpGetUrl="https://computer/Services/SampleService/Metadata"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Note that here we now provide an absolute address for the HttpGetUrl property (and the behavior is wired up to the service) so that you can view the metadata at “https://computer/Services/SampleService/Metadata?wsdl”. But this is fairly fragile. What we WANT to do is to specify the base addresses so that we can use relative ones. Now we use the base addresses stuff. We do this:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="Microsoft.WCF.Documentation.SampleService"
behaviorConfiguration="metadataSupport">
<host>
<baseAddresses>
<add baseAddress="https://localhost:8080/SampleService" />
</baseAddresses>
</host>
<endpoint
address=""
binding="wsHttpBinding"
contract="Microsoft.WCF.Documentation.ISampleService"
/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="metadataSupport">
<serviceMetadata httpGetEnabled="true" httpGetUrl=""/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Here we have specified that there is a base address of https://localhost:8080/SampleService. Note that the service endpoint HTTP-based address is empty and that it’s relative (because it’s not absolute), so the service is available at the base address.
- The ServiceMetadataBehavior is there...
- ...AND it’s wired up to the service
- ...AND the HttpGetUrl property is empty (and therefore relative)
- ...AND because the http/GET protocol requires an HTTP-transport based base address
- ...AND the base address IS an Http-based address
THEREFORE we can now see the metadata using a browser at https://localhost:8080/SampleService?wsdl. Whew. Lot's of ANDs.
Can we use WS-MetadataExchange yet? No. Can we retrieve metadata from any other transport? No. At this point we only support HTTP/GET at the previously mentioned address. Now let’s add WS-MetadataExchange support. We now add a metadata endpoint, one in which the contract is IMetadataExchange, the binding supports HTTP, and a relative (or absolute!) address. Here we go:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="Microsoft.WCF.Documentation.SampleService"
behaviorConfiguration="metadataSupport">
<host>
<baseAddresses>
<add baseAddress="https://localhost:8080/SampleService" />
</baseAddresses>
</host>
<endpoint
address=""
binding="wsHttpBinding"
contract="Microsoft.WCF.Documentation.ISampleService"
/>
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"
/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="metadataSupport">
<serviceMetadata httpGetEnabled="true" httpGetUrl=""/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
We have added a metadata endpoint with the relative address “mex” (therefore we must have a supporting base address -- and we do), specified the mexHttpBinding (for basic HTTP support -- and the base address IS an HTTP-based address, so we're OK there), and referenced the IMetadataExchange contract. You can now (in addition to using HTTP/GET as before) point svcutil at https://localhost:8080/SampleService/mex and it will retrieve the metadata content in a WS-MEX message.
Now, do we have HTTP/GET support? Yes. Do we have WS-Mex support over HTTP? Yes. Do we have WS-Mex support over TCP? No. To do that, we’ll add another endpoint that uses TCP, like so:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<services>
<service
name="Microsoft.WCF.Documentation.SampleService"
behaviorConfiguration="metadataSupport">
<host>
<baseAddresses>
<add baseAddress="https://localhost:8080/SampleService" />
</baseAddresses>
</host>
<endpoint
address=""
binding="wsHttpBinding"
contract="Microsoft.WCF.Documentation.ISampleService"
/>
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"
/>
<endpoint
address="tcpmex"
binding="mexTcpBinding"
contract="IMetadataExchange"
/>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="metadataSupport">
<serviceMetadata httpGetEnabled="true" httpGetUrl=""/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
Now then, what happens when we run svcutil against net.tcp://localhost:8080/SampleService/tcpmex? We should get 300 exceptions, as you did (OK, it’s really just one). Why? You can guess: The service host does not have a TCP-transport base address AND the TCP-based metadata endpoint uses a relative address. Had we specified an absolute address there:
<endpoint
address="net.tcp://localhost:8081/SampleService/tcpmex"
binding="mexTcpBinding"
contract="IMetadataExchange"
/>
We would have been able to point svcutil at net.tcp://localhost:8081/SampleService/tcpmex and it will work fine. But again, absolute addresses are bad. So how do we use relative addresses? We add a tcp-transport based base address, like so:
<host>
<baseAddresses>
<add baseAddress="https://localhost:8080/SampleService" />
<add baseAddress=”net.tcp://localhost:8081/SampleService” />
</baseAddresses>
</host>
NOW we can point to net.tcp://localhost:8081/SampleService/tcpmex and it will work fine. From here you should be able to add any other WS-MEX endpoint AND you should be able to understand what to check when it doesn’t work. Let’s look at the exception you got in your example:
"Could not find a base address that matches scheme net.tcp for the endpoint with binding MetadataExchangeTcpBinding. Registered base address schemes are []."
<snip from Scott's mail with permission>
So, I added the following section:
<host>
<baseAddresses>
<add baseAddress = "net.pipe://localhost/"/>
<add baseAddress ="net.tcp://localhost:8000/"/>
</baseAddresses>
</host>
Now I get the following on sh.Open()
“The HttpGetEnabled property of ServiceMetadataBehavior is set to true and the HttpGetUrl property is a relative address, but there is no http base address. Either supply an http base address or set HttpGetUrl to an absolute address.”
</snip from Scott's mail with permission>
The answer here is that you’ve set the HttpGetEnabled property to true BUT you have no <add baseAddress=/> property that supports HTTP. We have no transport to use for HTTP! You can either:
- Set the HttpGetUrl property to an absolute address OR
- add a HTTP-based base address to the <baseAddresses> element.
This put everything in perspective for Scott, who immediately went on to bigger and better things, but I wanted to make sure that no one else got stalled out by the simple service requirement. Two other notes. First, if you're wondering where the implementation of the IMetadataExchange contract is (because you know you didn't build one!) it's in the ServiceMetadataExtension, which the behavior adds to the ServiceHost. Second, here we added HTTP and TCP metadata support. But if you're following the abstract point, you should see that an IME endpoint can be any endpoint. You can secure it; auth it; encrypt it; whatever you want. We provide four basic supporting metadata bindings, but you can create custom bindings, too. Hope this all helps! I'll be folding this information back into the documentation as soon as I can...
Comments
Anonymous
March 19, 2007
This is a great walk through. I was stuck on the error message "Could not find a base address that matches scheme...". This really helped me to fix the error but really did a good job to help me understand what was going. Thanks!Anonymous
April 21, 2007
Excellent - thanks for documenting the quirks with WCF's config files :-)Anonymous
March 26, 2008
Yesterday I presented to one of my financial services customers in the city on an overview of what'sAnonymous
August 04, 2009
This was a sweet post!!! Saved me TONs of time!