Table of contents

Last updated .

Service metadata

Chapter 1 showcased an example of a very simple WCF service and introduced the basic concepts involved. This chapter goes on with an in-depth treatment of one of these concepts, namely the concept of metadata. Metadata is the data that describes a service in enough detail that a client is able to connect to and call a service. It is usually thought of a WSDL document, which is an XML document that adheres to a standard XML schema.

The metadata describes the contract. These are the requirements of the service. If a client wishes to communicate with the service it is bound to go along with that contract. A service contract can be broken down into three parts:

Service contracts

The service contract is the term used for the interface that the service implements. It also describes the message format used by the service. Thus, a service contract is a set of (presumably related) operations (methods) that a client can invoke. The metadata for an operation includes information about the names and types of parameters and return values. A WCF service implements an arbitrary number of service contracts, although at least one.

The service contract attribute

In .Net, a type is defined as being a WCF service by applying the ServiceContractAttribute to it. This attribute is usually (and preferably) applied to a .Net interface, but that is not an actual requirement. It can be applied to a class as well. The following properties can be set on a System.ServiceModel.ServiceContractAttribute to control how that service will operate and appear on the client side:

NameTypeDescription
CallbackContractTypeDefines another service contract to be used as a callback contract, enabling asynchronous calls from service to client.
ConfigurationNameStringGets or sets the name of the Service element in a configuration file used to configure the service.
NameStringThe name of the service. It defaults to the .Net name of the interface (or class), but the Name property can be used to control the public name of the service independently of implementation.
NamespaceStringThe namespace of the service. It defaults to http://tempuri.org, so it should definitely be set to some other, unique value to disambiguate from other services.
ProtectionLevelProtectionLevel (enum)Defaults to None. Other values are Sign and EncryptAndSign. With Sign, a message is digitally signed before sending. With EncryptAndSign it is also encrypted.
HasProtectionLevelBoolReturns if ProtectionLevel is different from ProtectionLevel.None.
SessionModeSessionMode (enum)Specifies whether the contract requires a binding that supports sessions. A session is a way of correlating a set of messages exchanged between endpoints. Possible values are Allowed, Required and NotAllowed.

It is recommended that you always specify ProtectionLevel explicitly and name explicitly. It is also recommended that a namespace is provided to disambiguate from other services. The only requirement for a namespace is that it be unique. So, you could use a GUID, but it's commonly preferred to use a more readable URI, possibly including parts that can be used for versioning purposes. It is preferable to adopt a naming convention for namespaces and then stick with that.

Operation contracts

A public method of a service contract becomes part of the service interface when it is adorned with an OperationAttribute. The following properties can be used to control features of an individual operation:

Can be used to override the ProtectionLevel set for the service contract.
NameTypeDescription
ActionStringProvides fine-grained control over the Action used for addressing incoming messsages to the operation. The default action value is a combination of the contract namespace (the default value is "http://tempuri.org/"), the contract name, the operation name, and an additional string ("Response") if the message is a correlated response.
ReplyActionStringProvides fine-grained control over theAction used for addressing outgoing messsages from the operation.
NameStringGets or sets the name of the operation, which defaults to the name of the implementing method.
ProtectionLevelProtectionLevel (enum)
HasProtectionLevelBoolReturns if ProtectionLevel is different from ProtectionLevel.None.
IsTerminatingBoolIndicates whether the service operation causes the server to close the session after the replying.
IsInitiatingBoolIndicates whether operation will initiate a server session (if using sessions).
IsOneWayBoolIndicates whether the operations sends a reply message.
AsyncPatternBoolDenotes if the operation adheres to the IAsyncResult asynchronous pattern.

Setting Action or ReplyAction is rarely needed, because WCF sets these to appropiate default values. One case where they can be put to use, though, is if you need to implement a predefined service specification.

The message parameter attribute

The names given in the SOAP message to parameters and the return value can be controlled with the System.ServiceModel.MessageParameter attribute. It has just a single property:

NameTypeDescription
NameStringGets or sets the name of the parameter or return value.

Investigating attributes and generated metadata

In order to exercise a bit the attributes described above, I made a new solution for testing. It is just the standard Visual Studio WCF test solution, consisting of:

Before writing any specific code, I saved the solution for use as a solution template to use in future WCF test work. The Visual Studio 2015 solution comes with the required references between projects and a bit of scaffold configuration and code. You can download the template as a Zip file.

I also wanted to see how things would work with multiple contracts and bindings for a service, so I constructed three simple interfaces in the ServiceContract project. The first one shows examples of using some of the properties of the ContractAttribute, OperationAttribute and MessageAttribute types:

IContractOne.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
using System.ServiceModel; namespace ServiceContract { [ServiceContract(Name = "ContractOneName", Namespace = "http://mycompany.org/api/sampleservice/2016/01", ConfigurationName = "ContractOneConfigName", SessionMode = SessionMode.Allowed)] public interface IContractOne { [OperationContract(Name = "SayHelloTo")] [return: MessageParameter(Name = "GreetingResponse")] string SayHello([MessageParameter(Name = "GreetingName")] string name); [OperationContract] string SayGoodbye(string name); } }

As you can see, values for the ContractAttribute Name and Namespace properties are given explicitly. There is also a value for ConfigurationName that will be used later in the service configuration. The first operation called "SayHello" is given the name "SayHelloTo", which then becomes the real name that clients will see. The return value of the operation is given a name, and the name of the first and only parameter is aliased to "GreetingName". For the second operation - the "SayGoodbye" method - there is no tinkering with names.

I also defined this second interface:

IContractTwo.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
using System.ServiceModel; namespace ServiceContract { [ServiceContract(Namespace = "http://mycompany.org/api/sampleservice/2016/01")] public interface IContractTwo { [OperationContract] string SayHelloAgain(string name); } }

And a third one:

IContractThree.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
using System.ServiceModel; namespace ServiceContract { [ServiceContract(Namespace = "http://mycompany.org/api/sampleservice/2016/01")] public interface IContractThree { [OperationContract] string SayHelloThirdTime(string someName); } }

As you might expect, the service implementations are really simple. First, there is this class that implements the IContractOne interface:

ContractOneService.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
using ServiceContract; namespace ServiceImplementation { public class ContractOneService : IContractOne { public string SayGoodbye(string name) { return string.Format("Goodbye, {0}!", name); } public string SayHello(string name) { return string.Format("Hello, {0}!", name); } } }

The other service class implements both IContractTwo and IContactThree:

ContractTwoThreeService.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
using System; using ServiceContract; namespace ServiceImplementation { public class ContractTwoThreeService : IContractTwo, IContractThree { public string SayHelloAgain(string name) { return string.Format("Hello second time to {0}!", name); } public string SayHelloThirdTime(string someName) { return string.Format("Hello third time to {0}!", someName); } } }

Now, the services can be configured. I have implemented two service classes, so I'll need two service elements in the configuration:

App.config
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="mexBehavior"> <serviceMetadata /> </behavior> </serviceBehaviors> </behaviors> <services> <service name="ServiceImplementation.ContractOneService" behaviorConfiguration="mexBehavior"> <endpoint address="ContractOneServiceAddress" name="tcpEndpoint" binding="netTcpBinding" contract="ContractOneConfigName" /> <endpoint address="ContractOneServiceAddress" name="httpEndpoint" binding="basicHttpBinding" contract="ContractOneConfigName" /> <endpoint binding="mexHttpBinding" name="mexHttp" address="mex" contract="IMetadataExchange" /> <endpoint binding="mexTcpBinding" name="mexTcp" address="mex" contract="IMetadataExchange" /> <host> <baseAddresses> <add baseAddress="net.tcp://localhost:9002/api"/> <add baseAddress="http://localhost:8001/api"/> </baseAddresses> </host> </service> <service name="ServiceImplementation.ContractTwoThreeService" behaviorConfiguration="mexBehavior"> <endpoint address="ServiceTwoThree" name="IContractTwoEndpoint" binding="basicHttpBinding" contract="ServiceContract.IContractTwo" /> <endpoint address="ServiceTwoThree" name="IContractThreeEndpoint" binding="basicHttpBinding" contract="ServiceContract.IContractThree" /> <endpoint binding="mexHttpBinding" name="mex" address="mex" contract="IMetadataExchange" /> <host> <baseAddresses> <add baseAddress="http://localhost:8002/api"/> </baseAddresses> </host> </service> </services> </system.serviceModel> </configuration>

The first service, named ContractOneService, implements just one contract, which is exposed on two different endpoints, namely an endpoint that uses netTcpBinding and one using basicHttpBinding. Apart from that, both protocols are also used for metadata exchange endpoints, so that clients can retrieve metadata for the service in either way. There are a couple of things to note here: 1) The value for the address attribute can be anything, 2) The address can be the same for multiple endpoints and 3) In the contract attribute, instead of referring to the name of an interface, I have given the value "ContractOneConfigName". This is the value I gave for the ServiceAttribute.ConfigurationName in the interface file (see above). Notice also that this service needs two base addresses, configured in the baseAddresses node.

As for the second service, named ContractTwoThreeService, I also need two endpoints, but this time it is not for binding purposes. It is because the service implements two interfaces. In this case I chose to expose both using the same basicHttpBinding

Note that the three baseaddresses are using different ports - only one endpoint can use the same port at the same time.

To get this going I also needed a host. Again, I constructed my own host executable in the form of a console app. Only this time it is the host of two services, rather than one, so it is no longer practical to use the C# using statement to ensure that IDisposable.Dispose is called. Other than that, there is nothing new in this code relative to the previous host code:

Program.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
using System; using ServiceImplementation; namespace ServiceHost { class Program { static void Main(string[] args) { System.ServiceModel.ServiceHost host1 = null; System.ServiceModel.ServiceHost host2 = null; try { host1 = new System.ServiceModel.ServiceHost(typeof(ContractOneService)); host1.Open(); host2 = new System.ServiceModel.ServiceHost(typeof(ContractTwoThreeService)); host2.Open(); Console.WriteLine("Services have been started."); Console.WriteLine("Press Enter to terminate service hosts."); Console.ReadLine(); } finally { if (null != host1) ((IDisposable) host1).Dispose(); if (null != host2) ((IDisposable) host2).Dispose(); } } } }

Examining metadata

Now, the host can be started. When the host is running there is a ContractOneService service listening on port 9002 using net.tcp and on port 8001 using a http binding. Another service called ContractTwoThreeService service is listening on port 8002 using http. Both services expose metadata endpoints, so that metadata can be obtained at runtime through the IMetadataExchange interface. We could construct a client to call the services through that interface to get the metadata, but it's easier to use the svcutil command-line utility. To obtain metadata for a service, svcutil can be called with a /t:metadata flag and targeting the appropiate endpoint, which in this case I called mex. It will then spew out a number of wsdl and xsd files, which in unison describe the service. This is the command used to obtain metadata for one of the two services; the ContractTwoThreeService service:

svcutil /t:metadata http://localhost:8002/api/mex

Note that if you target a net.tcp endpoint, you will have to run svcutil with administrator privileges. The above command causes svcutil to output the metadata in the form of the four listed files:

Using svcutil to download service metadata
Using svcutil to download service metadata

Although looking at wsdl files is not terribly exciting, it is still instructive to examine precisely which information is available for a client. Please note that services are completely independent. Metadata for one service has nothing to do with metadata for the other service, even though, in this case, they are hosted by the same app. Let's start with one of the more basic files:

schemas.microsoft.com.2003.10.Serialization.xsd
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns:tns="http://schemas.microsoft.com/2003/10/Serialization/" attributeFormDefault="qualified" elementFormDefault="qualified" targetNamespace="http://schemas.microsoft.com/2003/10/Serialization/" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="anyType" nillable="true" type="xs:anyType" /> <xs:element name="anyURI" nillable="true" type="xs:anyURI" /> <xs:element name="base64Binary" nillable="true" type="xs:base64Binary" /> <xs:element name="boolean" nillable="true" type="xs:boolean" /> <xs:element name="byte" nillable="true" type="xs:byte" /> <xs:element name="dateTime" nillable="true" type="xs:dateTime" /> <xs:element name="decimal" nillable="true" type="xs:decimal" /> <xs:element name="double" nillable="true" type="xs:double" /> <xs:element name="float" nillable="true" type="xs:float" /> <xs:element name="int" nillable="true" type="xs:int" /> <xs:element name="long" nillable="true" type="xs:long" /> <xs:element name="QName" nillable="true" type="xs:QName" /> <xs:element name="short" nillable="true" type="xs:short" /> <xs:element name="string" nillable="true" type="xs:string" /> <xs:element name="unsignedByte" nillable="true" type="xs:unsignedByte" /> <xs:element name="unsignedInt" nillable="true" type="xs:unsignedInt" /> <xs:element name="unsignedLong" nillable="true" type="xs:unsignedLong" /> <xs:element name="unsignedShort" nillable="true" type="xs:unsignedShort" /> <xs:element name="char" nillable="true" type="tns:char" /> <xs:simpleType name="char"> <xs:restriction base="xs:int" /> </xs:simpleType> <xs:element name="duration" nillable="true" type="tns:duration" /> <xs:simpleType name="duration"> <xs:restriction base="xs:duration"> <xs:pattern value="\-?P(\d*D)?(T(\d*H)?(\d*M)?(\d*(\.\d*)?S)?)?" /> <xs:minInclusive value="-P10675199DT2H48M5.4775808S" /> <xs:maxInclusive value="P10675199DT2H48M5.4775807S" /> </xs:restriction> </xs:simpleType> <xs:element name="guid" nillable="true" type="tns:guid" /> <xs:simpleType name="guid"> <xs:restriction base="xs:string"> <xs:pattern value="[\da-fA-F]{8}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{4}-[\da-fA-F]{12}" /> </xs:restriction> </xs:simpleType> <xs:attribute name="FactoryType" type="xs:QName" /> <xs:attribute name="Id" type="xs:ID" /> <xs:attribute name="Ref" type="xs:IDREF" /> </xs:schema>

There is nothing related to the two services in this file. It is just a standard xsd defining the most basic types such as string, datatime and guid. Let's have a look at the other xsd file. As you can see, it is named after the namespace of the service:

mycompany.org.api.sampleservice.2016.01.xsd
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns:tns="http://mycompany.org/api/sampleservice/2016/01" elementFormDefault="qualified" targetNamespace="http://mycompany.org/api/sampleservice/2016/01" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="SayHelloAgain"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="name" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="SayHelloAgainResponse"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="SayHelloAgainResult" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="SayHelloThirdTime"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="someName" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="SayHelloThirdTimeResponse"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="SayHelloThirdTimeResult" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>

This file describes the types of parameters and return values of the service operations. Since this service exposes two methods, each of which takes one parameter and returns a string, this gives four entities in total, all of which happen to be string. Notice that, by default, the set of parameters of an operation are named after the operation, and the return structure has the same name with a "Response" suffix. These four types are referenced in the following wsdl file.

The next wsdl file describes the service in logical terms. It is essentially a listing of the available interfaces, their operations and for each operation the type of input (parameters) and output:

mycompany.org.api.sampleservice.2016.01.wsdl
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
<?xml version="1.0" encoding="utf-8"?> <wsdl:definitions xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:tns="http://mycompany.org/api/sampleservice/2016/01" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://mycompany.org/api/sampleservice/2016/01" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> <wsdl:types> <xsd:schema targetNamespace="http://mycompany.org/api/sampleservice/2016/01/Imports"> <xsd:import namespace="http://mycompany.org/api/sampleservice/2016/01" /> <xsd:import namespace="http://schemas.microsoft.com/2003/10/Serialization/" /> </xsd:schema> </wsdl:types> <wsdl:message name="IContractTwo_SayHelloAgain_InputMessage"> <wsdl:part name="parameters" element="tns:SayHelloAgain" /> </wsdl:message> <wsdl:message name="IContractTwo_SayHelloAgain_OutputMessage"> <wsdl:part name="parameters" element="tns:SayHelloAgainResponse" /> </wsdl:message> <wsdl:message name="IContractThree_SayHelloThirdTime_InputMessage"> <wsdl:part name="parameters" element="tns:SayHelloThirdTime" /> </wsdl:message> <wsdl:message name="IContractThree_SayHelloThirdTime_OutputMessage"> <wsdl:part name="parameters" element="tns:SayHelloThirdTimeResponse" /> </wsdl:message> <wsdl:portType name="IContractTwo"> <wsdl:operation name="SayHelloAgain"> <wsdl:input wsaw:Action="http://mycompany.org/api/sampleservice/2016/01/IContractTwo/SayHelloAgain" message="tns:IContractTwo_SayHelloAgain_InputMessage" /> <wsdl:output wsaw:Action="http://mycompany.org/api/sampleservice/2016/01/IContractTwo/SayHelloAgainResponse" message="tns:IContractTwo_SayHelloAgain_OutputMessage" /> </wsdl:operation> </wsdl:portType> <wsdl:portType name="IContractThree"> <wsdl:operation name="SayHelloThirdTime"> <wsdl:input wsaw:Action="http://mycompany.org/api/sampleservice/2016/01/IContractThree/SayHelloThirdTime" message="tns:IContractThree_SayHelloThirdTime_InputMessage" /> <wsdl:output wsaw:Action="http://mycompany.org/api/sampleservice/2016/01/IContractThree/SayHelloThirdTimeResponse" message="tns:IContractThree_SayHelloThirdTime_OutputMessage" /> </wsdl:operation> </wsdl:portType> </wsdl:definitions>

In the above wsdl file, the two xsd files are imported with a wsdl:types element (lines 17 to 22). After that, the types of input and output messages are described with four wsdl:message elements (lines 23 to 34). Finally, the two interfaces are described with two wsdl:portType elements (lines 35 to 50). For each interface, the available operations are listed in the form of wsdl:operation elements and for each operation the format of input and output is defined through wsaw:Action and message attributes. The value for an input wsdl:Action is formed by concatenating the target namespace with the service name and then the operation name. The same name is used for a wsdl:Action, but with a "Response" suffix.

The targetNamespace attribute on the wsdl:definitinons node (line 15) contains the namespace of the contract. This namespace is brought over from the namespace property that we put on the ServiceContract attribute of the IContractTwo and IContractThree interfaces, which happen to be the same value.

The final file describes the service from a more technical viewpoint, listing the available endpoints, their operations and addresses:

tempuri.org.wsdl
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
<?xml version="1.0" encoding="utf-8"?> <wsdl:definitions xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:tns="http://tempuri.org/" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:i0="http://mycompany.org/api/sampleservice/2016/01" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="ContractTwoThreeService" targetNamespace="http://tempuri.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> <wsdl:import namespace="http://mycompany.org/api/sampleservice/2016/01" location="" /> <wsdl:types /> <wsdl:binding name="IContractTwoEndpoint" type="i0:IContractTwo"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="SayHelloAgain"> <soap:operation soapAction="http://mycompany.org/api/sampleservice/2016/01/IContractTwo/SayHelloAgain" style="document" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:binding name="IContractThreeEndpoint" type="i0:IContractThree"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="SayHelloThirdTime"> <soap:operation soapAction="http://mycompany.org/api/sampleservice/2016/01/IContractThree/SayHelloThirdTime" style="document" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> <wsdl:service name="ContractTwoThreeService"> <wsdl:port name="IContractTwoEndpoint" binding="tns:IContractTwoEndpoint"> <soap:address location="http://localhost:8002/api/ServiceTwoThree" /> </wsdl:port> <wsdl:port name="IContractThreeEndpoint" binding="tns:IContractThreeEndpoint"> <soap:address location="http://localhost:8002/api/ServiceTwoThree" /> </wsdl:port> </wsdl:service> </wsdl:definitions>

In the above document, the two wsdl:binding elements map directly to the two endpoints that we defined for the service. The wsdl:service element describes the service itself, including its address(es).

Consuming the metadata

Every service type has an associated description in the form of metadata files, as we have just seen above. Everything a client needs to connect to and call a service is contained in the metadata.

In chapter one of this series, I made the code and configuration for a client proxy by running the service and then calling svcutil to generate that code and configuration directly. There is an alternative way: A client proxy can be generated from static metadata, meaning from a set of wsdl and xsd files. This way, the service does not need to be running, in fact it is not involved in the generation of proxy code, only the static files are. In this case we have the two wsdl files and the two xsd files. We could send those files to whoever wanted to make a client app to consume the service. That client app could be made with any technology or platform, be it Linux, Java or whatever. In the Windows world, we can use svcutil to generate the proxy code for us for the ContractTwoThreeService:

svcutil *.wsdl *.xsd /config:app.config /namespace:*,ContractTwoThreeService

This run of svcutil makes it read metadata files in the current directory and from that produce a config file named app.config and a file containing C# code for proxy classes in a namespace called ContractTwoThreeService.

Using svcutil to generate proxy files
Using svcutil to generate proxy files

Before calling svcutil I changed the current directory to the directory where the wsdl and xsd files are located. This causes svcutil to generate an app.config and a code file containing the proxy code for the service. I don't want to show the generated files, because they are just like the equivalent client files shown in chapter 1.

So far, I have generated metadata and client proxy files for just the one service. I repeated the whole procedure for the ContractOneService. For both services I created a subfolder of the Client project to house the metadata and proxy code file, as shown in the solution explorer screen shot on the right. It's not that there is any practical use for the eight metadata files in the project, it's just nice to have them for reference as part of the project and to keep them under source control if that is being used. I had to do a bit of merging of the app.config files for the client project, and this is what it ended up with:

Service reference files in solution explorer
Service reference files in solution explorer
app.config
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.serviceModel> <bindings> <basicHttpBinding> <binding name="IContractTwoEndpoint" /> <binding name="IContractThreeEndpoint" /> <binding name="httpEndpoint" /> </basicHttpBinding> <netTcpBinding> <binding name="tcpEndpoint" /> </netTcpBinding> </bindings> <client> <endpoint address="http://localhost:8002/api/ServiceTwoThree" binding="basicHttpBinding" bindingConfiguration="IContractTwoEndpoint" contract="ContractTwoThreeService.IContractTwo" name="IContractTwoEndpoint" /> <endpoint address="http://localhost:8002/api/ServiceTwoThree" binding="basicHttpBinding" bindingConfiguration="IContractThreeEndpoint" contract="ContractTwoThreeService.IContractThree" name="IContractThreeEndpoint" /> <endpoint address="net.tcp://localhost:9002/api/ContractOneServiceAddress" binding="netTcpBinding" bindingConfiguration="tcpEndpoint" contract="ContractOneService.ContractOneName" name="tcpEndpoint" /> <endpoint address="http://localhost:8001/api/ContractOneServiceAddress" binding="basicHttpBinding" bindingConfiguration="httpEndpoint" contract="ContractOneService.ContractOneName" name="httpEndpoint" /> </client> </system.serviceModel> </configuration>

This is pretty straightforward. There are four endpoints involved here, three of which uses basicHttpBinding and one uses netTcpBinding.

Now, at last, the client app can be implemented, exercising the total of four operations offered by three interfaces, implemented by two service classes:

Program.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
using System; namespace Client { class Program { static void Main(string[] args) { ContractOneService.ContractOneName serviceOne = new ContractOneService.ContractOneNameClient("tcpEndpoint"); ContractTwoThreeService.IContractTwo serviceTwo = new ContractTwoThreeService.ContractTwoClient(); ContractTwoThreeService.IContractThree serviceThree = new ContractTwoThreeService.ContractThreeClient(); Console.WriteLine(serviceOne.SayHelloTo("Bill")); Console.WriteLine(serviceOne.SayGoodbye("Bill")); Console.WriteLine(serviceTwo.SayHelloAgain("Alice")); Console.WriteLine(serviceThree.SayHelloThirdTime("Bob")); Console.WriteLine("Press Enter to quit"); Console.ReadLine(); } } }
Running the client exe
Running the client exe

Notice that in constructing the proxy for the ContractOneService (line 9), I must specify which binding to use, since that service is available at more than one endpoint. The constructor for the ContractOneNameClient proxy has an overload that allows me to pass the name (as specified in app.config) of the preferred binding. Notice also the name of ContractOneName applied to the first service interface (line 9) - this is the name that was specified in the Name property of the ServiceContract attribute of the IContractOne interface. The other two service interfaces just have their implementation name, since no name was specified for them.

This example clearly shows that, in WCF, the interface is the service. In the service implementation, I implemented the IContractTwo and IContractThree interfaces in a single class called ContractTwoThreeService. But still, from the client's viewpoint, the two interfaces are just two unrelated services; one cannot be cast to the other. For example, referring to the above client code, you could not write serviceTwo = (ContractTwoThreeService.IContractTwo) serviceThree. Well, you could, but you would get a runtime exception.

The names I invented for the endpoints in the host app.config recur in the client app.config, for example "tcpEndPoint". Looking at it from a client's perspective, I think I should have come up with some more specific and descriptive names for endpoints. After all, the metadata is what a prospective client application developer will see, so I think it's important to be descriptive and consistent with names and namespaces.

If you've read so far as here, through xsd and wsdl files, I hope you've got an impression of how the code and configuration for services are expressed in metadata. And also how that metadata is then used to generate configuration and proxy code for a client application.

Get the code for this chapter's first example as a Zip file (Visual Studio 2015).

Versioning of service contracts

If you've published a service contract that clients have started using, it is likely that you will soon find a need to modify or extend the contract. Perhaps clients have uttered change requests, or maybe new business processes lead to new requirements. In any case, while it is important to be able to improve services and the contracts they depend on, it's just as important that you don't inadvertently break the service for existing clients. This section discusses how to accomplish both aims at the same time.

In type-safe languages such as C# we are used to the fact that making even the smallest change to a method will break the code that calls the method. WCF services are much more lenient, in fact service contracts are change tolerant by default. The table below lists the possible changes you might make to a service contract and what the consequences are.

Type of changeImpact on clients
Add a parameterClient unaffected. New parameters are initialized to default values at the service end.
Remove a parameterClient unaffected. Values for unknown parameters are discarded and lost at the service end.
Change a parameter typeThe incoming value will be converted to the appropiate type if possible, otherwise an exception will be thrown.
Change a return typeThe returned value will be converted to the appropiate type if possible, otherwise an exception will be thrown.
Add an operationClient unaffected. The client will not know about the new operation.
Remove an operationThe client will get an exception.

It's perfectly possible to rely on the lenient nature of WCF service contracts, but it is also a bit dangerous. For example, it is questionable whether it's "ethical" to lose data that the client is sending and type conversion may work, but the conversion result may still be unexpected. For these reasons, it is recommended that you version the service contract, i.e. make a new one to live side by side with existing ones. There are a couple of different strategies you can adopt in versioning.

Versioning with contract inheritance

Probably the most commonly needed type of modification is the addition of operations. Let's say you have this existing service contract that you want to update:

ISomeService.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
using System.ServiceModel; namespace ServiceContract { [ServiceContract(Namespace = "http://mycompany.org/api/sampleservice/2016/01")] public interface ISomeService { [OperationContract] string SayHello([MessageParameter(Name = "name")] string name); } }

Now, you want to update the interface in two ways:

  1. Add a parameter to the SayHello operation and
  2. Add an operation.

The first change can be accomplished by simply adding the parameter; this will not break exisiting clients, only they will not be passing any value for that parameter, so it will always have its default value for "old" clients.

ISomeService.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
using System.ServiceModel; namespace ServiceContract { [ServiceContract(Namespace = "http://mycompany.org/api/sampleservice/2016/01")] public interface ISomeService { [OperationContract] string SayHello([MessageParameter(Name = "name")] string name, [MessageParameter(Name = "upperCase")] bool upperCase = false); } }

As for the second required change, a new interface with a new SayGoodbye operation can be defined that inherits from the existing interface:

ISomeServiceExtension.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
using System.ServiceModel; namespace ServiceContract { [ServiceContract(Namespace = "http://mycompany.org/api/sampleservice/2016/02")] public interface ISomeServiceExtension : ISomeService { [OperationContract] string SayGoodbye([MessageParameter(Name = "name")] string name); } }

Notice that this new interface has a different namespace than the "old" one, to indicate that this is new version. Now, the service implementation can be modified. The new parameter can be added to the existing operation and instead of implementing the "old" ISomeService interface, it will implement the new ISomeServiceExtension, which in turn inherits ISomeService. This new service implementation represents the version 2 of the service that new clients can use. Existing clients will continue to use the version 1 that has only a single operation.

SomeService.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
using ServiceContract; namespace ServiceImplementation { public class SomeService : ISomeServiceExtension { public string SayGoodbye(string name) { return string.Format("Goodbye {0}!", name); } public string SayHello(string name, bool upperCase = false) { return upperCase ? string.Format("Hello {0}!", name).ToUpper() : string.Format("Hello {0}!", name); } } }

Now there will be two endpoints in the configuration file for the service; one for the version one interface and one for the new interface. The two endpoints have different addresses, names and contracts:

app.config
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="mexBehavior"> <serviceMetadata /> </behavior> </serviceBehaviors> </behaviors> <services> <service name="ServiceImplementation.SomeService" behaviorConfiguration="mexBehavior"> <endpoint address="SomeService" name="SomeServiceHttpEndpoint" binding="basicHttpBinding" contract="ServiceContract.ISomeService" /> <endpoint address="SomeServiceV2" name="SomeServiceV2HttpEndpoint" binding="basicHttpBinding" contract="ServiceContract.ISomeServiceExtension" /> <endpoint binding="mexHttpBinding" address="mex" name="mex" contract="IMetadataExchange" /> <host> <baseAddresses> <add baseAddress="http://localhost:8001/api"/> </baseAddresses> </host> </service> </services> </system.serviceModel> </configuration>

The following code for a client app is a composite of possible code in an existing client and in a client that uses the new contract:

program.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
using System; namespace Client { class Program { static void Main(string[] args) { // Old client using version 1: var client = new SomeServiceProxy.SomeServiceClient(); Console.WriteLine(client.SayHello("Bent")); // Newer client using version 2: var client2 = new SomeServiceV2Proxy.SomeServiceExtensionClient(); Console.WriteLine(client2.SayHello("Bent", true)); Console.WriteLine(client2.SayGoodbye("Bent")); Console.WriteLine("Press Enter to quit"); Console.ReadLine(); } } }
Running the client exe
Running the client exe

Versioning with separate services

An alternative to the strategy outlined above is to simply make a completely new service for the new version while keeping the existing one around as long as someone is still using it. Continuing the above example, let's imagine we need to make a version 3 of the service with one operation removed and a new operation added in its place. Instead of inheriting from an existing interface we are going to make a new interface that does not inherit from anything and then implement it in a service that is unrelated to the previous ones. The interface might look like this:

ISomeServiceV3.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
using System.ServiceModel; namespace ServiceContract { [ServiceContract(Namespace = "http://mycompany.org/api/sampleservice/2016/03")] public interface ISomeServiceV3 { [OperationContract] string SayGoodbye([MessageParameter(Name = "name")] string name); [OperationContract] string SayGoodMorning([MessageParameter(Name = "name")] string name); } }

Compared with version 2, one method has been removed while another has been added. Again, the use of a different namespace signals that this is a different version. A new implementation is then needed:

SomeServiceV3.cs
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
using ServiceContract; namespace ServiceImplementation { public class SomeServiceV3 : ISomeServiceV3 { public string SayGoodbye(string name) { return string.Format("Goodbye {0}!", name); } public string SayGoodMorning(string name) { return string.Format("Good morning {0}!", name); } } }

A whole new service section will have to be added in the configuration file for the host, using a baseaddress with a different port than the version 1 and 2 services.

This latter approach of making a completely new service interface and implementation has the benefit of providing a whole new slate with every possibility for adding, removing and changing operations and parameters. The downside is that the service code base grows for every new version and there is a risk of code duplication.

Get the code for this chapter's second example showcasing contract versioning as a Zip file (Visual Studio 2015).