Service metadata provides a description of a service that is used by developers when creating applications that call the service. This metadata is used to drive the generation of common language runtime (CLR) types such developers can program against functionality exposed by the remote service in the same way they might use functionality from a referenced assembly. The most common way to acquire this metadata is by asking the service for it directly—that is, the service is configured to publish its metadata directly to clients. However, sometimes there is a need to expose the metadata for services where asking the service for it directly is not an option. In this article, we take a look at such a scenario and an approach to solve it.
Let’s begin by putting this into context with a scenario. Imagine that you have a topology like that shown in Figure 1. Basically, clients can never call your services directly. Their calls go through some intermediary (let’s call it a routing boundary) that is responsible for getting the message to the correct service. This may be a load balancer or a router. One of the main design points of this topology is that clients use a single endpoint for their interaction with the services—they don’t have to (and probably shouldn’t) know the address of each service behind the routing boundary. There could be a myriad of reasons for choosing to perform this type of service aggregation: auditing access, security, simplifying configuration, and versioning are just a few. However, if you’ve ever used Visual Studio to Add Service Reference to a client project, you are probably used to entering the address of the service. If these services are all behind a single endpoint and not directly accessible, then how can you expose their metadata through either the same endpoint they use for operation or another endpoint dedicated to serving up metadata?
Before we dive too deeply into a solution, let’s establish what we are talking about with respect to service metadata and how it gets published in the default case. Service metadata is generally described by two types of metadata documents: WSDLs (web service description language) and XSDs (Xml Schema Documents). In a nutshell, the WSDL describes the service contract (e.g., its name, address, and operations it exposes, along with their signatures), the configuration bindings a client needs to use to communicate with the service, the structure of the messages used as input and output to service operations, and references to the related XSD files that describe any types used within these messages. Publishing service metadata adds an endpoint to your service that makes the metadata available using standard protocols, specifically WS-MetadataExchange (MEX) or HTTP/GET requests. In the case of MEX, the service metadata is provided by a service itself. This service implements the IMetadataExchange interface and exposes, among other things, an operation named Get that will retrieve the service metadata. The alternative to using MEX is to send an HTTP GET request. This type of request is the one you can view when you enter the address of your service in the browser, followed by the wsdl parameter (e.g., http://localhost/services/myService.svc?wsdl), which results in the service’s WSDL being displayed in the browser window (as Figure 2 shows).
A Simple Approach
Returning to our scenario, how can we make the WSDLs and XSDs for our service available separately from the service they describe? One simple solution is to copy them to another location that is publicly available, such as another website.
The procedure to do this is as follows. First, you need to download a copy of the metadata files themselves. You can do this with SvcUtil.exe, which is included with your installation of Visual Studio 2010. The easy way to run this is from the Start Menu: select Visual Studio 2010, Visual Studio Tools, Visual Studio Command Prompt. Within the command prompt, navigate to a directory where you want the files saved. Then run SvcUtil as shown below:
svcutil /t:metadata http://<server>/<applicationName>/<xamlx or svc>
For example, to export the metadata for a workflow service you would run
svcutil /t:metadata http://localhost/WFSoapService/Service1.xamlx
The result of a successful execution will be the WSDL and XSD files created from your service. There is one thing you need to fix before you can put these on a website. The WSDL will contain hard-coded, absolute URI references to the XSDs. Since these URIs will not be available to outside callers, they will be invalid. Take the snippet of a WSDL that Figure 3 shows, for example.
The schemaLocation attribute on the import element will need to be updated to point to wherever your XSDs actually end up.
As Figure 4 shows, you will also want to update the address of the service so that it points to the shared endpoint, instead of the actual service address. To do this, update the value of the location attribute of the address element. With the WSDL fixed up, now you can put these on the website of your choosing.
Making use of the service metadata to create a client is easy. Within the Solution Explorer of your client project in Visual Studio, right click the project and choose Add Service Reference. In the address field, enter the URL to the WSDL and click Go. The dialog should update and you should see a representation of your service in the tree view. Clicking OK will generate the code behind types and update your app.config, just as if you had added a reference directly to the service.
A Better Approach
While the previous approach was simple and could be easy to automate, it suffers from a problem that occurs when updating an existing service or creating new services—you have to copy over the metadata files for the affected services. This maintenance overhead can become burdensome and has the potential to introduce subtle errors if a service gets out of sync with its published metadata.
A better approach is to do as we suggest in Figure 1, which is to re-expose the metadata endpoints at the routing boundary while allowing the services themselves to actually provide the metadata. We can accomplish this by using the WCF Routing Service, a feature of .NET 4.
I introduced the WCF Routing Service in the article Routing in AppFabric. If you are new to the WCF Routing Service, you should review that article for a more complete background on the WCF Routing Service. For our purposes, the WCF Routing Service treats the metadata endpoints just like any other service, and it can be configured as intermediary that intelligently forwards metadata requests to the appropriate service behind the routing boundary.
Not that the WCF Routing Service cannot route REST requests. This means it cannot be used to route requests for metadata that use the HTTP GET approach. However, it can route requests for metadata that happen across a MEX endpoint, so we first need to ensure our services expose a MEX endpoint.
Configuring a MEX Endpoint for a Service
When you create a new WCF Service Application or a Workflow Service Application in Visual Studio 2010, the web.config that is provided for you includes the serviceMetadata behavior, which has its HttpGetEnabled set to true by default so that the service exposes its metadata via HTTP GET requests. This will not work with the WCF Routing Service. Instead, we can explicitly add a MEX endpoint to the service.
Doing so is a matter of editing the web.config. Figure 5 shows a complete web.config with a MEX endpoint added. To go from the default web.config to this, you will need to add a services element and within it define a service element to configure your service (matching the name attribute to the name of your service). Within this service element, add an endpoint with the address mex, a binding of mexHttpBinding and a contract of IMetadataExchange. This is all it takes to add in a MEX endpoint for a service. By adding this service entry, we are forgoing the default configuration that WCF applies based on the scheme (http://, tcp://, etc.) , so we also have to explicitly define the endpoint the service will use for its operation (we show exposing a basicHttpBinding in the figure). With our service properly configured, we can now turn our attention to configuring the WCF Routing Service to leverage the MEX endpoint.
Configuring the WCF Routing Service
The WCF Routing Service can be configured in many ways to route metadata requests to the appropriate MEX endpoint. In this article, we take the approach that most developers will simply want to access the metadata of service by the service name. In other words, the address they would enter in the Add Service Reference dialog would be of the form http://localhost/metadatarouter/router.svc/WfSoapService, where the first part (up to router.svc) is the actual address of the WCF Routing Service endpoint, and everything after that is the path that can be used to uniquely identify a service. In our case, we can just use a name, like WfSoapService.
Refer to my previous article on how to create this SVC endpoint for the routing service. We will focus on the changes you need to make to the web.config to support routing based on an address like the one just shown.
Figure 6 shows the complete listing of the web.config that configures the routing service accordingly. We will walk through the key aspects. The routing service is primarily configured by its service endpoint, the client endpoints it will talk to, the filters it will apply in choosing an endpoint, and a filter table that lists the order the filters are applied to an incoming request.
When routing to MEX endpoints exposed via http, both the service endpoint (seen in the figure as the only endpoint within the services element) and the client endpoints must have their binding attribute set to a value of “mexHttpBinding.” The client endpoints must specify the address of the MEX endpoint. When we configured our service in the previous step, we gave it an address “MEX,” so the absolute address used for the address value actually appends /mex after the SVC or XAMLX extension (e.g., http://localhost/WFSoapService/Service1.xamlx/mex).
The WCF Routing Service supports an Endpoint Address filter type, which will take the complete address at which the request arrived and match it against the value specified in the filterData attribute. The matching rules are defined in the filters collection. When a request comes in, the filterTable is evaluated from top to bottom, each time referring to a filter defined in the filters collection, and stops on the first filter that matches. That endpoint is chosen, and the request (a call to the Get method) is forwarded there.
As an example, using the web.config shown, if we received a request at http://conviction/MetadataRouter/Router.svc/WFSoapService, the first filter would not match (because of the trailing 1), but the second entry in the filterTable would. This would result in the request being forwarded to the endpoint named WFSoapService_v2.
Using the Routed MEX Endpoint
With that configuration in place, you can create the client in Visual Studio by using the Add Service Reference feature and providing an address that matches a filterData value in the filterTable. Figure 6 also shows how you could use the same approach to provide metadata for different version of the same service. In this case, you would enter the corresponding version’s address in the Add Service Reference dialog (e.g., http://localhost/metadatarouter/router.svc/WfSoapService/1).
Customize What Metadata Is Shown
The WCF Routing Service gets its routing configuration from web.config, and within that you can decide what metadata is shown. For example, you can choose to expose only the latest or structure the filter data so that clients only need to know the service name, or you can get more sophisticated and allow clients to request the metadata for specific versions. Admittedly, there is a manual step of applying these changes to the web.config—the WCF Routing Service can be extended to pull its configuration from another source, such as a table in a database. In the end, however, by using the WCF Routing Service, you can expose a single endpoint (the same routing service endpoint could also route the requests to service operations) that provides access to the metadata of all your services. Since the services are really the ones providing the metadata, you don’t have the potential for stale metadata that occurs when you copy the metadata files to another location.