A service contract describes
the operations supported by a service,
the message exchange pattern they use,
and the format of each message.
The service contract is also the main driver for generating a service description.
A valid WCF service implements at least one service contract.
The following attributes play an important role in defining service contracts:
ServiceContractAttribute, OperationContractAttribute,and MessageParameterAttribute.
Collectively,these attributes provide control over the way messages are formatted.
In this section,the first thing you’ll do is complete a lab that exercises each of these attributes.
Following the lab,I’ll summarize the features of each of these attributes and their roles in service contract design.
In the process,I’ll also cover other related concepts such as:
• How service contracts map to WSDL elements
• Service contract design
• Operations signatures and messages
• Versioning scenarios
Lab: Designing a Service Contract
In this lab,you will begin with an existing solution that includes three projects: a service,a host,and a client.
The solution includes a working implementation of a very simple service contract,
and you will edit this implementation to exercise more control over the service contract to control the resulting service description.
In the process, you’ll also test service contract version tolerance.
Exploring service contract compatibility
In the first part of this lab,you’ll modify an existing service contract to use an explicit contract name and namespace
while testing compatibility between proxy and service as changes are made.
1. Start by opening the solution for this lab: <YourLearningWCFPath>\Labs\Chapter2\ServiceContracts\ServiceContracts.sln.
2. Test the solution first to see that it works.
Compile the solution and run the Host project followed by the WinClient project.
From the WinClient application interface,click each button to test the two operations exposed by the service contract: Operation1( ) and Operation2( ).
3. Review how the service you just tested is implemented.
Go to the BusinessServices project and open IServiceA.cs. Example 2-3 shows the service contract,which defines two operations.
Example 2-3. IServiceA service contract
[ServiceContract]
public interface IServiceA
{
[OperationContract]
string Operation1();
[OperationContract]
string Operation2();
}
Now open ServiceA.cs in the code window to view the implementation of this contract.
4. Modify the service contract by providing a friendly name for the contract and a namespace to reduce ambiguity.
Open IServiceA.cs once again and set the Name and Namespace properties of the ServiceContractAttribute,as shown in bold here:
[ServiceContract(Name="ServiceAContract" ,Namespace = "http://www.thatindigogirl.com/samples/2006/06")]
public interface IServiceA
5. Try to test the solution again.
This time when you select either button in the WinClient interface,an ActionNotSupportedException is thrown.
先运行Host.exe然后再运行WinClient,点击Operation1按钮。会出现如下的错误提示
The error message should look similar to the following:
由于 ContractFilter 在 EndpointDispatcher 不匹配,因此 Action 为“http://tempuri.org/IServiceA/Operation1”的消息无法在接收方处理。
这可能是由于协定不匹配(发送方和接收方 Action 不匹配)或发送方和接收方绑定/安全不匹配。
请检查发送方和接收方是否具有相同的协定和绑定(包括安全要求,如 Message、Transport、None)。
Note:
The SOAP action property of a message indicates the operation being invoked at the destination.
Changing the namespace at the service causes the service to expect a different SOAP action for each operation.
Since the client proxy is still using a service contract with a different namespace,messages from the client are now incompatible with the service.
6. Changing the namespace of the service contract also changes the expected format of messages to the service.
To fix this,update the client proxy so that it reflects the namespace change.
Go to the WinClient project node and expand the Service References folder.
Drill down from localhost.cs to Reference.cs and open it in the code window.
Modify the definition of IServiceA so that the ServiceContractAttribute uses the
name and namespace of the service contract. Add the changes shown in bold here:
Modify the definition of IServiceA so that the ServiceContractAttribute uses the name and namespace of the service contract.
Add the changes shown in bold here:
[System.ServiceModel.ServiceContractAttribute(Namespace="http://www.thatindigogirl.com/samples/2006/06",Name="ServiceAContract",ConfigurationName="WinClient.localhost.IServiceA")]
public interface IServiceA
You must also update the Action and ReplyAction properties of the OperationContractAttribute for each operation, as shown here:
[System.ServiceModel.OperationContractAttribute(Action="http://www.thatindigogirl.com/samples/2006/06/ServiceAContract/Operation1",ReplyAction="http://www.thatindigogirl.com/samples/2006/06/ServiceAContract/Operation1Response")]
string Operation1( );
[System.ServiceModel.OperationContractAttribute(Action="http://www.thatindigogirl.com/samples/2006/06/ServiceAContract/Operation2",ReplyAction="http://www.thatindigogirl.com/samples/2006/06/ServiceAContract/Operation2Response")]
string Operation2( );
7. Compile and run the solution again and test each operation.
This time,no exception is thrown since the messages are once again compatible.
8. Synchronize the client proxy with the service by regenerating the proxy.
First,go to the WinClient project and delete the service reference, localhost.
Now run the Host without debugging and generate a new proxy for WinClient.
Go to the WinClient project and add a service reference providing the URI http://localhost:8000 and setting the namespace to localhost.
Note:
You can also right-click localhost and select Update Service Reference,but this operation fails at times depending on the nature of the changes you have made to the service.
9. Close the Host console and try to recompile the solution.
This will fail because the proxy type name,formerly ServiceAClient,has changed to reflect the new service contract name, ServiceAContractClient.
Open Form1.cs in the code window and modify the statement that instantiates the proxy to reflect the changes shown in bold here:
localhost.ServiceAContractClient m_proxy = new WinClient.localhost.ServiceAContractClient( );
Verify that you can recompile the solution.
Customizing message parameters
In this section of the lab,you’ll modify the service contract operations to control how parameters and return values are serialized using the MessageParameterAttribute.
You’ll also test the effect on communications between proxy and service when you add and remove operations at the service.
1. Add a new method to the service contract. Open IServiceA.cs and add this method signature:
[OperationContract]
string NewOperation(string string);//这里的参数名会报错的
2. Implement this new operation on the service type.
Open ServiceA.cs and add this method to the class:
public class ServiceA : IServiceA
{
// other methods
public string NewOperation(string s)
{
return string.Format("IServiceA.NewOperation( ) invoked with {0}", s);
}
}
3. Try to compile the solution.
It won’t compile because string is a type and cannot be used as a parameter name.
4. Use the MessageParameterAttribute to control the serialized name of the string parameter.
Modify the method signature so that the string parameter name is “s”
and supply the name “string” to the MessageParameterAttribute for the parameter:
[OperationContract]
string NewOperation([MessageParameter(Name = "string")]string s);
5. Try to compile the solution—this time,it works.
Run the solution and test Operation1() to verify that the addition of a new operation hasn’t affected the client.
Modifying service operations and dealing with version tolerance
This part of the lab explores how communication between client and service are affected when the service contract is changed.
1. Let’s see what happens when you make a change to an existing operation signature.
Open IServiceA.cs in the code window and change the signature of Operation1( ) to the following:
[OperationContract]
string Operation1(DateTime dt);
Open ServiceA.cs and change the implementation as well:
string IServiceA.Operation1(DateTime dt)
{
return string.Format("IServiceA.Operation1( ) invoked at {0}.",dt);
}
2. Compile and run the solution,and test Operation1( ) once again. //只重新生成解决方案,记住此处不需要更新服务应用
Surprisingly,there will not be an exception even though the client proxy doesn’t share the same signature for Operation1( ).
Instead,the DateTime parameter is initialized to its default when the service operation is invoked.
3. Now,remove Operation2( ) from the service contract.
Do this by commenting the following lines for the definition of IServiceA:
//[OperationContract]
//string Operation2( );
4. Test this result by recompiling and running the solution again.
From the client,test Operation2( ).
This time,an ActionNotSupportedException is thrown since there are no operations on the service contract matching the action header specified by the message.
Now let’s look more closely at what you’ve done in working through this lab.
Mapping Services to WSDL
When a service is hosted,the service model generates a runtime service description that can ultimately be viewed as a WSDL document.
For the most part it isn’t necessary for you to understand WSDL in depth,
but it can be helpful to have a general understanding of the way service contracts,operations,and other service characteristics are mapped to WSDL elements.
首先把项目切换到没有给ServiceContract添加Name和Namespace的版本
The WSDL output in Example 2-4 is representative of the initial sample provided for this lab. //运行Host.exe文件,然后访问http://localhost:8000/?wsdl获取下面的文档
Table 2-1 explains how each of the high-level WSDL elements used in this listing is derived from service characteristics.
Example 2-4. Partial view of the WSDL document generated for the initial lab solution
<wsdl:definitions xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:tns="http://tempuri.org/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" name="ServiceA" targetNamespace="http://tempuri.org/"> //Describes the target namespace for the contract, which ultimately is the target namespace for each message.
<wsdl:types>
<xsd:schema targetNamespace="http://tempuri.org/Imports">
<xsd:import schemaLocation="http://localhost:8000/?xsd=xsd0" namespace="http://tempuri.org/"/>
<xsd:import schemaLocation="http://localhost:8000/?xsd=xsd1" namespace="http://schemas.microsoft.com/2003/10/Serialization/"/>
</xsd:schema>
</wsdl:types>
<wsdl:message name="IServiceA_Operation1_InputMessage">
<wsdl:part name="parameters" element="tns:Operation1"/>
</wsdl:message>
<wsdl:message name="IServiceA_Operation1_OutputMessage">
<wsdl:part name="parameters" element="tns:Operation1Response"/>
</wsdl:message>
<wsdl:message name="IServiceA_Operation2_InputMessage">
<wsdl:part name="parameters" element="tns:Operation2"/>
</wsdl:message>
<wsdl:message name="IServiceA_Operation2_OutputMessage">
<wsdl:part name="parameters" element="tns:Operation2Response"/>
</wsdl:message>
<wsdl:portType name="IServiceA">
Describes the messages for each operation in the service contract within a <wsdl:operation> element. For request/reply messages, a <wsdl:input> and <wsdl:output> message will be listed.
<wsdl:operation name="Operation1">
Describes each operation in the service contract. Each operation can have a <wsdl:input>,<wsdl:output> or <wsdl:fault> associated with it.
These elements lead to a schema describing the message format for input, output and declared faults.
The information to support this schema is derived from operation signatures on the service contract.
<wsdl:input wsaw:Action="http://tempuri.org/IServiceA/Operation1" message="tns:IServiceA_Operation1_InputMessage"/> Indicates which type describes each message. Types are found in the <wsdl:types> section.
<wsdl:output wsaw:Action="http://tempuri.org/IServiceA/Operation1Response" message="tns:IServiceA_Operation1_OutputMessage"/> Indicates which type describes each message. Types are found in the <wsdl:types> section.
</wsdl:operation>
<wsdl:operation name="Operation2">
<wsdl:input wsaw:Action="http://tempuri.org/IServiceA/Operation2" message="tns:IServiceA_Operation2_InputMessage"/> Indicates which type describes each message. Types are found in the <wsdl:types> section.
<wsdl:output wsaw:Action="http://tempuri.org/IServiceA/Operation2Response" message="tns:IServiceA_Operation2_OutputMessage"/> Indicates which type describes each message. Types are found in the <wsdl:types> section.
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="BasicHttpBinding_IServiceA" type="tns:IServiceA">
Describes the transport protocol to reach the endpoint. Also includes a<wsdl:operation> element for each operation on the endpoint (based on the service contract).
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="Operation1">
<soap:operation soapAction="http://tempuri.org/IServiceA/Operation1" style="document"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
<wsdl:operation name="Operation2">
<soap:operation soapAction="http://tempuri.org/IServiceA/Operation2" 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="ServiceA"> Maps to the service type, ServiceA. Includes nested <wsdl:port> elements for each endpoint.
<wsdl:port name="BasicHttpBinding_IServiceA" binding="tns:BasicHttpBinding_IServiceA"> Maps to the service endpoint at the specified address. Also refers to the WSDL binding section.
<soap:address location="http://localhost:8000/ServiceA"/> Maps to the service endpoint at the specified address. Also refers to the WSDL binding section.
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Table 2-1. A look at how service characteristics map to high-level WSDL elements
WSDL element Service characteristic
<wsdl:definitions> (at line 2)
Describes the target namespace for the contract, which ultimately is the target namespace for each message.
<wsdl:service> (at line 39)
Maps to the service type, ServiceA.
Includes nested <wsdl:port> elements for each endpoint.
<wsdl:port> (at lines 40, 41)
Maps to the service endpoint at the specified address.
Also refers to the WSDL binding section.
<wsdl:binding> (at line 29) Describes the transport protocol to reach the endpoint.
Also includes a<wsdl:operation> element for each operation on the endpoint (based on the service contract).
<wsdl:portType> (at line 20)
Describes the messages for each operation in the service contract within a <wsdl:operation> element.
For request/reply messages, a <wsdl:input> and <wsdl:output> message will be listed.
<wsdl:operation> (at lines 21, 27, 31, 37)
Describes each operation in the service contract. Each operation can have a <wsdl:input>,<wsdl:output> or <wsdl:fault> associated with it.
These elements lead to a schema describing the message format for input, output and declared faults.
The information to support this schema is derived from operation signatures on the service contract.
<wsdl:message> (at lines 12,15, 18, 19)
Indicates which type describes each message. Types are found in the <wsdl:types> section.
<wsdl:types> (at line 4)
Either contains or imports the XSD schemas describing each operation message and any other complex types those messages rely on.
You’ll notice that the schemaLocation attribute defines the namespace to which the import refers.
Each service type has an associated service description,thus WSDL.
Remember that WSDL documents and their associated XSD schema can be used by SvcUtil to generate client proxies and related configuration.
Thus,every piece of information necessary for clients to communicate with a service is contained in the WSDL.
The location of each endpoint,the protocols supported by that endpoint,the list of operations for that endpoint,
and the messages associated with each operation are all described here.
Every message has an associated schema,so when something does not go as expected you can compare what was sent on the wire with what is expected by the
WSDL document.
Messages for each operation are described in an imported schema file referred to at line 6 as http://localhost:8000/?xsd=xsd0.
Consider this operation signature that has no parameters and returns a string:
string Operation1( );
If you navigate to this URI in the browser,you’ll see something like the listing shown in Example 2-5
Example 2-5. Partial view of the XSD schema describing messages for IServiceA
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://tempuri.org/">
<xs:element name="Operation1">
<xs:complexType>
<xs:sequence />
</xs:complexType>
</xs:element>
<xs:element name="Operation1Response">
<xs:complexType>
<xs:sequence>
<xs:element minOccurs="0" name="Operation1Result" nillable="true"
type="xs:string" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Operation2">
</xs:schema>
It can also help to look at the WSDL document produced by your services in order to consider naming conventions for the service,its operations and complex types.
After all,this is a document that your business partners will consume,so some level of consistency with naming conventions is appropriate.
Designing Service Contracts
By now you already know that the ServiceContractAttribute and OperationContractAttribute are used to define service contracts.
Service contracts are any interface or type with the ServiceContractAttribute applied.
Operations inside the contract are determined by those methods with the OperationContractAttribute applied.
These attributes have several properties that can be used to control how the service description is generated and how messages should be formatted.
In this section,I’ll provide you with an overview of the capabilities of these attributes and of the MessageParameterAttribute of which this lab makes use.
ServiceContractAttribute
The ServiceContractAttribute is part of the System.ServiceModel namespace primarily used for describing a set of related operations and setting the target namespace for messages.
This attribute also has other features that influence message exchange patterns,message security,and whether the service supports sessions.
Here is a list of the properties the ServiceContractAttribute exposes:
Name
Specifies a different name for the contract,instead of using the interface or class type name.
Namespace
Specifies a target namespace for messages. The default namespace is http://tempuri.org.
CallbackContract
Associates another service contract as a callback contract.
This allows clients to receive asynchronous messages from the service.
Chapter 3 will discuss asynchronous messages and callbacks.
ProtectionLevel
Allows you to control how messages to all operations in the contract are protected on the wire—if they are signed and encrypted.
This is explored in Chapter 7.
SessionMode
Determines if sessions are supported by the endpoint exposing this service contract.
Sessions are discussed in Chapter 5.
At a minimum,you should always provide a Namespace to disambiguate messages on the wire.
In the initial code sample for this lab,a namespace was not provided for the ServiceContractAttribute.
Instead,the default namespace, http://tempuri.org,is used to serialize messages (indicated by the targetNamespace in Example 2-4).
The result is that request and response messages use this namespace as illustrated in Example 2-6.
Example 2-6. Message body for Operation1( ) request and response
// Request message body
<s:Body>
<Operation1 xmlns="http://tempuri.org/"></Operation1>
</s:Body>
// Response message body
<s:Body>
<Operation1Response xmlns="http://tempuri.org/">
<Operation1Result>IServiceA.Operation1( ) invoked.</Operation1Result>
</Operation1Response>
</s:Body>
The namespace can be any unique name,usually in the form of an URI or URN,
but web services usually lean toward a corporate or base application URI,followed by the year,month,
and any other service or application naming conventions.
For example, I use the following namespace for samples in this book:
[ServiceContract(Namespace = "http://www.thatindigogirl.com/samples/2006/06")]
public interface IServiceA
You can also provide a friendly name for the service contract.
This is more of a “nice to have” feature;
the important thing is to be consistent across all service contracts.
If you don’t customize service contract names,CLR interface names will be used in the WSDL contract as shown by this portType from Example 2-4:
<wsdl:portType name="IServiceA">
Being explicit in the contract does have merit.
It makes it clear to developers which name is published in the service description and prevents developers from accidentally causing a breaking change to the service description if they refactor CLR types.
//使用契约的Name属性,可以确保及时Interface的名称改变了,也不影响客户端调用
This lab uses ServiceAContract as the contract name:
[ServiceContract(Name="ServiceAContract" Namespace="http://www.thatindigogirl.com/samples/2006/06")]
public interface IServiceA
As the lab illustrates,when you change the contract name,it affects the name of client proxies generated by SvcUtil.
If the contract name is IServiceA,the client proxy type is named ServiceAClient.
改变契约的Name属性会影响到使用svcutil生成的代理
SvcUtil drops the “I” prefix for you on the client side (other platform tools may not do this).
If the contract is renamed to ServiceAContract,the proxy type becomes ServiceAContractClient.
OperationContractAttribute
The OperationContractAttribute,also part of System.ServiceModel,is used to promote methods to service operations so that they can be included in the service description.
Simply applying this attribute to a method is good enough to opt-in to the contract;
however,there are other features of the attribute that influence how messages are exchanged and which features are supported by the operation.
Name
Specifies a different name for the operation, instead of using the method name.
Action
Controls the action header for messages to this operation.
ReplyAction
Controls the action header for response messages from this operation.
IsOneWay
Indicates whether the operation is one-way and has no reply. When operations are one-way, ReplyAction is not supported.
ProtectionLevel
Allows you to control how messages to this particular operation are protected on the wire.
Setting this property per operation overrides the service contract setting for ProtectionLevel.
IsInitiating
Indicates whether the operation can be used to initiate a session.
Sessions are discussed in Chapter 4.
IsTerminating
Indicates whether the operation terminates a session.
Setting the Name property for an operation is not required,but like with the ServiceContractAttribute,
it does make it clear to developers which name is published in the service description.
If you follow my recommendation in Chapter 1 that service contracts are not coupled to business interfaces,
there is less need to consciously separate what names are used in the service description versus which names are used in code.
You can adopt an approach that service contracts are immutable and developers can never change them,
and provide operation names appropriate for the service description from the start.
As for Action and ReplyAction,these are properties that derive from the service contract namespace and the operation name—you need not specify an explicit value.
Of course,when SvcUtil generates the client proxy,it populates these properties explicitly, based on the service description:
[System.ServiceModel.OperationContractAttribute(Action=
"http://www.thatindigogirl.com/samples/2006/06/ServiceAContract/Operation1",
ReplyAction="http://www.thatindigogirl.com/samples/2006/06/
ServiceAContract/Operation1Response")]
string Operation1( );
Action is generated by concatenating the service contract namespace with the service contract friendly name and the operation name.
The ReplyAction appends “Response” to this.
If you set your target namespace appropriately,and use consistent naming conventions for the service contract and each operation,the generated value should
suffice.
Sometimes,you need to explicitly set these values when you are building a service contract based on a predefined specification.
In this case,there may be a WSDL document that specifies the action header required for incoming and outgoing messages.
Example 2-7 shows the request and response message for Operation1( ),with the action header shown in bold.
Example 2-7. Request and response messages for Operation1( )
// Request message
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<To s:mustUnderstand="1"
xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">
http://localhost:8000/ServiceA</To>
<Action s:mustUnderstand="1"
xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">
http://www.thatindigogirl.com/samples/2006/06/ServiceAContract/Operation1</Action>
</s:Header>
<s:Body>
<Operation1 xmlns="http://www.thatindigogirl.com/samples/2006/06"></Operation1>
</s:Body>
</s:Envelope>
// Response message
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<Action s:mustUnderstand="1"
xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">
http://www.thatindigogirl.com/samples/2006/06/ServiceAContract/Operation1Response
</Action>
</s:Header>
<s:Body>
<Operation1Response xmlns="http://www.thatindigogirl.com/samples/2006/06">
<Operation1Result>IServiceA.Operation1( ) invoked.</Operation1Result>
</Operation1Response>
</s:Body>
</s:Envelope>
When the action header for incoming messages does not match any operation descriptions at the endpoint,
the channel listener at the service cannot process the request.
That’s because by default,the service model expects the action header to exactly match an operation in the contract.
Note:
Services have an address filtering behavior that determines how action headers are evaluated when messages arrive to client and service channels.
Later in this chapter,I’ll show you how to control the address filter mode for an advanced scenario.
MessageParameterAttribute
The MessageParameterAttribute,part of System.ServiceModel,is used to control the name of parameter and return values in the service description.
This attribute has only one property,the Name property.
In the operation signature,parameters become part of the request message body.
Each parameter is serialized to an element named for the parameter name.
The response is serialized to an element named for the operation name with “Response” appended.
Consider this operation signature:
[OperationContract]
string NewOperation(string s);
Example 2-8 shows what the resulting message body looks like for the request and response message.
Example 2-8. Message body for NewOperation( ) request and response
// Request message body
<s:Body>
<NewOperation xmlns="http://www.thatindigogirl.com/samples/2006/06">
<s>hello</s>
</NewOperation>
</s:Body>
// Response message body
<s:Body>
<NewOperationResponse xmlns="http://www.thatindigogirl.com/samples/2006/06">
<NewOperationResult>IServiceA.NewOperation( ) invoked with </NewOperationResult>
</NewOperationResponse>
</s:Body>
You can apply the MessageParameterAttribute to control serialization.
The following code applies this attribute to parameters and return values:
// Operation signature
[OperationContract]
[return: MessageParameter(Name = "responseString")]
string NewOperation([MessageParameter(Name = "string")]string s);
This attribute can be particularly useful when you want to use a keyword or type name in the resulting XSD schema that describes an incoming message.
Likewise, you can control the XML element name for the return value in a response message.
Example 2-9 shows what the resulting messages look like for NewOperation( ) after applying the attribute.
Example 2-9. Request and response messages after applying the MessageParameterAttribute
//Request SOAP body
<s:Body>
<NewOperation xmlns="http://www.thatindigogirl.com/samples/2006/06">
<string>hello</string>
</NewOperation>
</s:Body>
//Reply SOAP body
<s:Body>
<NewOperationResponse xmlns="http://www.thatindigogirl.com/samples/2006/06">
<responseString>IServiceA.NewOperation( ) invoked with hello</responseString>
</NewOperationResponse>
</s:Body>
As with keywords and type names,you may also find MessageParameterAttribute useful for standardizing on XML element naming conventions separate from CLR naming conventions.
For example,you may use camel case for parameter names and prefer Pascal case for XML.
Versioning Service Contracts
The previous section described how to design service contracts using attributes to control the resulting service description.
If any changes are made to the service contract after the first version has been published,it is important that those changes
don’t impact existing clients.
This lab illustrates that the service contracts are version tolerant by default.
Table 2-2 summarizes some possible changes to a service contract and the affect it has on existing clients, if any.
Table 2-2. Service contract changes and their impact on existing clients
Service contract changes Impact on existing clients
Adding new parameters to an operation signature
Client unaffected. New parameters are initialized to default values at the service.
Removing parameters from an operation signature
Client unaffected. Superfluous parameters passed by clients are ignored, and the data is lost at the service.
Modifying parameter types
An exception will occur if the incoming type from the client cannot be converted to the parameter data type.
Modifying return value types
An exception will occur if the return value from the service cannot be converted to the expected data type in the client version of the operation signature.
Adding new operations
Client unaffected. The client will not invoke operations about which it knows nothing.
Removing operations
An exception will occur. Messages sent by the client to the service are considered to be using an unknown action header.
The bulk of these changes have no direct affect on clients,although the results may be undesirable.
The forgiving nature of the service model can be a blessing or a curse depending on how you look at it.
The upside is that developers can make changes without impacting existing clients.
For example,you can add new operations,add new parameters,decide you no longer care about certain parameters—and preexisting clients can still call the service successfully.
Here are some downsides:
• You can unwittingly lose information passed by the client.
• Missing data from the client may not be detected.
• Type conversions may work, yet the data is semantically invalid.
You can remove the risks of these unexpected results by using data contracts as message parameters and return types.
I’ll discuss data contracts in the next section.
Of course,you are entitled to come up with your own strategy for version tolerance,
but I recommend that if the implementation semantics of the service contract have changed
or if new clients are to be given additional features via extended parameters or new operations,
you should version the service contract,rather than accepting version tolerance.
To properly version a service contract you should provide a new contract and modify the namespace specified in the ServiceContractAttribute.
To version the namespace,you can supply a new value for the year and month if you follow the naming conventions I’ve recommended.
In the next two sections,I’ll compare two versioning approaches.
Versioning with contract inheritance
One likely scenario is that you will add new operations without changing the existing service contract.
You may want to support these new operations at the same endpoint,though.
You can achieve this by creating a new contract that inherits the old contract,adding new operations to the new one.
Consider the contract shown in Example 2-10 as the first published version of the service contract.
Example 2-10. Version 1 of the contract, IServiceA
[ServiceContract(Name="ServiceAContract",
Namespace = "http://www.thatindigogirl.com/samples/2006/06")]
public interface IServiceA
{
[OperationContract]
string Operation1( );
[OperationContract]
string Operation2( );
}
The endpoint configuration for the service implementing the contract might look like:
<endpoint address="ServiceA" contract="BusinessServiceContracts.IServiceA" binding="basicHttpBinding" />
When new operations are added to the service to extend its features,
you may want to expose the same endpoint to old and new clients while still somehow tracking the contract version related to new features.
If you extend the original service contract, you can add operations under a new namespace, as shown in Example 2-11.
Example 2-11. Version 2 of the contract, IServiceA2
[ServiceContract(Name="ServiceAContract",
Namespace = "http://www.thatindigogirl.com/samples/2006/08")]
public interface IServiceA2:IServiceA
{
[OperationContract]
string Operation3( );
}
Note the service contract name is the same,but the namespace has changed.
The service type can implement IServiceA2 and it will be able to expose the original operations with their original namespace,
while exposing the new operation with the versioned namespace.
As such,original clients can hit the same endpoint without impact,
while new clients who download the metadata when version 2 is available,can access all operations.
This scenario does not address the following:
• You can’t modify existing operations with contract inheritance. Only new operations are added.
• You can’t differentiate old clients from new clients because they hit the same endpoint.
The next section will discuss an alternative that addresses these issues.
Note:
A sample illustrating this versioning scenario can be found at <YourLearningWCFPath>\Samples\ServiceContracts\ServiceContractVersioning_Inheritance.
Versioning with separate contracts
A stricter approach to versioning would be to version the entire contract and expose a new endpoint for new clients.
In this case,version 2 of the contract might look like Example 2-12.
Example 2-12. Version 2 of the contract, IServiceA2
[ServiceContract(Name="ServiceAContract",
Namespace = "http://www.thatindigogirl.com/samples/2006/08")]
public interface IServiceA2
{
[OperationContract]
string Operation1( );
[OperationContract]
string Operation2( );
[OperationContract]
string Operation3( );
}
In this case,all operations are defined even if they are not changed.
This way,they all gain the new namespace to distinguish calls to version 1 endpoints.
In order to provide a unique WSDL containing only one or the other contract,
a unique service type must be created to implement the new contract.
Both versions of the service can still share implementation behind the scenes.
With this implementation the following restrictions apply:
• You can differentiate old and new clients by service entry point.
• You can modify existing operations in the new interface (not recommended; you should really provide new operation names).
• New clients cannot send messages to the original endpoint.
• Old clients cannot send messages to the new endpoint.
Note:
A sample illustrating this versioning scenario can be found at <YourLearningWCFPath>\Samples\ServiceContracts\ServiceContractVersioning_NoInheritance.
Strict versus non-strict versioning
To summarize the discussion of service contract versioning,
Figures 2-3 and 2-4 illustrate the decision flow for non-strict and strict versioning policy, respectively.
Figure 2-3. Non-strict service contract versioning
Figure 2-4. Strict service contract versioning
Non-strict versioning allows changes to service contracts that do not break existing clients.
This means you can add or remove parameters without versioning the service contract.
The implementation code may need to accommodate how to handle version 1 and version 2 clients.
Though possible,the idea of operation signature changes can be very difficult to track, for example adding and removing parameters.
Using a data contract as the service operation parameter at least allows for explicitly required and nonrequired parameters (discussed in the
upcoming “Data Contracts” section).
With strict service contract versioning,any change to an operation leads to a new service contract.
A new endpoint is always provided to ensure version 2 clients can be differentiated from version 1 clients.