WHAT'S IN THIS CHAPTER?
Explaining contracts
Implementing service contracts
Implementing data contracts
Using different serializers
Implementing message contracts
Contracts in general are very important in our daily lives. Contracts ensure that each party is aware of what his or her efforts will return. If you put your signature at the end of 17 pages of a business document or make a verbal agreement, expectations are given and agreed on. A contract should be well-defined and understandable without leaving any leeway for interpretation.
One thing is certain: in a service-oriented environment where different parties (software components) communicate with each other and use different technologies and platforms, it is indispensable to have a clear definition of the how and what.
WCF (Windows Communication Foundation) uses the concept of contracts to define the service and its operation as a whole, to explain the data which are transferred by wire, and if needed, to directly define the SOAP messages exchanged between the client and the service. WCF uses WSDL and XSD to deliver service metadata.
WCF differentiates between three types of contracts: service, data, and message.
Each of these contracts defines a certain behavior. The one needed depends on your architecture. The most frequently used contract type is the service contract — it can be used in nearly every scenario. The service contract also stands for the C in the ABC of an endpoint. As you may know, a service endpoint is an addressable unit which is typically used by the client to send and receive messages.
Contracts in the context of SOA provide the necessary metadata to communicate with the service — they describe things such as data types, operations, message exchange patterns, and the transport protocol that are used. Contracts are typically delivered in a standardized platform and language-neutral XML format that allow different parties to consume and interpret the definition. In WCF, the service metadata is typically described by a WSDL document (Web Services Description Language). More information is available at http://www.w3.org/TR/wsdl
. This standard allows software vendors such as Microsoft, Sun, and IBM to build tools to help developers produce and consume these contracts.
The service contract defines the functionality which is exposed by your service to the outside world. The functionality is expressed in the form of service operations. In a SOA world, you typically exchange a set of SOAP messages, and the schemas of these messages are defined in the service contract. The service contract further defines the message exchange pattern (Request/Reply, One-Way, Duplex) for each service operation.
You usually pass one or more arguments to your service operation and expect a return value. The structure of a message argument is explained in the data contract as an XSD schema which is part of the WSDL document. The Data Contract defines the structure and content of the information that is exchanged between the client and the service.
If you use your own objects and complex types as method arguments or return values, you have to inform the runtime how to serialize these types into an XML stream by using the DataContract attribute.
Depending on the granularity of your method arguments, you might use only simple .NET types as method arguments. In this case you do not need to worry about serialization and deserialization because the messaging infrastructure already knows how to deal with those simple .NET types.
A message contract is an advanced feature which can be used to gain greater control of the SOAP header and/or the SOAP body. The message contract is used to directly interact with the SOAP message (header and body), not just the method body — as is the case if you use a data contract instead of a message contract.
As a .NET software developer on the service side, it is not your daily job to produce metadata documents in the form of WSDL — you would normally design interfaces and classes, write methods, implement business logic, and then write unit tests to check your logic. To help software developers produce these metadata artifacts from source code without fundamentally changing the programming, WCF offers different tools, attributes, and classes which are explained in more detail on the following pages.
If you use WCF to implement your SOA architecture, you can choose from different approaches — depending on your requirements — to automatically generate those metadata documents from scratch, based on your code. You can use .NET attributes, configuration settings, and directly leverage the API — provided by WCF — to get absolute control of the metadata generation.
But how can you consume those metadata from a client and interact with the service? To complete the story, assume that you have already generated the service specification in the form of a WSDL document. Then assume that the service is up and running. If you want to communicate with the service, you have to create an XML message in the right format, send the messages to the service endpoint, wait for a response message, and finally extract your values from the XML document. Thanks to the standard of WSDL and tools such as svcutil.exe and VS 2010, it is very easy to create source code (a proxy) which mimics the behavior of the service and handles all the communication and serialization details for you.
As mentioned earlier, there are different approaches to instructing the runtime. Knowing which parts of your code should be reflected as metadata in WSDL, without knowing the exact specification of WSDL, is what you need to figure out.
It is helpful to know some basic elements of WSDL to get a good understanding of what is going on under the hood and to interpret the generated metadata document. To get a basic idea of the correlation between elements in the WSDL document and your code, start with a simple example to see which code fragment leads to which element in the WSDL document.
So far contracts in general have been explained — automatic metadata generation and the use of proxies. Now it is time to see all these concepts in action.
To give you a short and compact overview of how to create a service contract, a simplified implementation of a fictive car rental service is shown, explaining in further detail about the basis of the code listings. If you choose the contract-first approach, you typically start by defining the WSDL document and not by writing code.
When using the code-first approach, you start with a .NET interface definition and decorate your service contract with the [ServiceContract]
attribute and your methods with the [OperationContract]
attribute from the System.ServiceModel
namespace.
The service contract definition as shown in Listing 2-1 should live in its own assembly because there are several advantages if you use a separate Class Library for your interface and your service contract — such as versioning, decoupling the abstract definition from the concrete implementation, and deployment.
Example 2.1. Service Contract Definition
using System; using System.ServiceModel; namespace Wrox.CarRentalService.Contracts { [ServiceContract()] public interface ICarRentalService { [OperationContract] double CalculatePrice(DateTime pickupDate, DateTime returnDate, string pickupLocation, string vehiclePreference); } }
As you can see in Listing 2-1, the example service interface is quite simple and contains just one method called CalculatePrice
with four arguments and a return type of double
.
To define a service contract, just apply the [ServiceContract]
attribute to the interface definition and the [OperationContract]
attribute to each method you want included as a service operation in the WSDL document. If the interface contains a method without the [OperationContract]
attribute, the method will not be included in the generated service metadata. This is also called an opt-in model.
It is possible to annotate a class — the concrete service implementation — with the [ServiceContract]
attribute and its methods with the [OperationContract]
attribute. However, there are several reasons why this is not recommended. One reason is that you will mix the "outside world" (the service contract) with the "inside world" (your implementation code), and this breaks the rule about having explicit boundaries. You should always decouple the abstract service definition from the concrete implementation, if you want to produce production code.
If you apply the attribute at the interface level, you can additionally create different implementations of the same service contract and expose those different implementations at various service endpoints.
After compiling the source code, obtain an Assembly called Wrox.CarRentalService.Contracts.dll
which contains the interface/service contract definition.
To give you a better understanding of how each part of the interface definition correlates with the WSDL document, the WSDL document is generated first.
If the service exposes a Metadata Exchange Endpoint (MEX), you can directly browse to this endpoint and see the generated metadata.
Because you do not have a MEX, you will use svcutil.exe to generate the WSDL file. The ServiceModel Metadata Utility Tool (svcutil.exe) can be found at the Windows SDK installation location and provides you with a number of functionalities:
Code generation from running services or static metadata documents
Metadata export from compiled code
Validation of compiled service code
Download of metadata documents from running services
Generation of serialization code
Use the command svcutil.exe Wrox.CarRentalService.Contracts.dll
at the Visual Studio command prompt to get the three files shown in Table 2-1.
Table 2.1. The WSDL and XSD Files
FILENAME | DESCRIPTION |
---|---|
tempuri.org.wsdl (see Listing 2-2) | The WSDL document with a reference to the XSD files, message definitions, port types, and service operations. |
tempuri.org.xsd (see Listing 2-3) | The XSD schema for the method attributes. |
schemas.microsoft.com.2003.10.Serialization.xsd (see Listing 2-4) | The standard XSD schema for simple .NET types. |
Visit the W3C site at http://www.w3.org/XML/Schema
for more information about schemas.
Example 2.2. WSDL
<?xml version="1.0" encoding="utf-8"?> <wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 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" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:tns="http://tempuri.org/" xmlns:wsa="http://schemas.xmlsoap.org/ws/2004/08/addressing" xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy" xmlns:wsap="http://schemas.xmlsoap.org/ws/2004/08/addressing/policy" xmlns:wsaw="http://www.w3.org/2006/05/addressing/wsdl" xmlns:msc="http://schemas.microsoft.com/ws/2005/12/wsdl/contract" xmlns:wsa10="http://www.w3.org/2005/08/addressing" xmlns:wsx="http://schemas.xmlsoap.org/ws/2004/09/mex" xmlns:wsam="http://www.w3.org/2007/05/addressing/metadata" targetNamespace="http://tempuri.org/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> <wsdl:types> <xsd:schema targetNamespace="http://tempuri.org/Imports"> <xsd:import namespace="http://tempuri.org/" /> <xsd:import namespace="http://schemas.microsoft.com/2003/10/ Serialization/" /> </xsd:schema> </wsdl:types> <wsdl:message name="ICarRentalService_CalculatePrice_InputMessage"> <wsdl:part name="parameters" element="tns:CalculatePrice" /> </wsdl:message> <wsdl:message name="ICarRentalService_CalculatePrice_OutputMessage"> <wsdl:part name="parameters" element="tns:CalculatePriceResponse" /> </wsdl:message> <wsdl:portType name="ICarRentalService"> <wsdl:operation name="CalculatePrice"> <wsdl:input wsaw:Action="http://tempuri.org/ICarRentalService/CalculatePrice" message="tns:ICarRentalService_CalculatePrice_InputMessage" /> <wsdl:output wsaw:Action="http://tempuri.org/ICarRentalService/ CalculatePriceResponse" message= "tns:ICarRentalService_CalculatePrice_OutputMessage" /> </wsdl:operation> </wsdl:portType> <wsdl:binding name="DefaultBinding_ICarRentalService" type="tns:ICarRentalService"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" /> <wsdl:operation name="CalculatePrice"> <soap:operation soapAction="http://tempuri.org/ICarRentalService/ CalculatePrice" style="document" /> <wsdl:input> <soap:body use="literal" /> </wsdl:input> <wsdl:output> <soap:body use="literal" /> </wsdl:output> </wsdl:operation> </wsdl:binding> </wsdl:definitions>
Example 2.3. XSD File
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns:tns="http://tempuri.org/" elementFormDefault="qualified" targetNamespace="http://tempuri.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="CalculatePrice"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="pickupDate" type="xs:dateTime" /> <xs:element minOccurs="0" name="returnDate" type="xs:dateTime" /> <xs:element minOccurs="0" name="pickupLocation" nillable="true" type="xs:string" /> <xs:element minOccurs="0" name="vehiclePreference" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="CalculatePriceResponse"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="CalculatePriceResult" type="xs:double" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
Example 2.4. schemas.microsoft.com.2003.10.Serialization
<?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>
As you can see in Listing 2-2 (WSDL) and Listing 2-3 (XSD), different parts of the XSD and WSDL come directly from your source code. To override the default naming behavior and gain further control, you can use the members of the [ServiceContract]
and the [OperationContract]
attributes. Table 2-2 describes the mapping between your code and the WSDL document.
Table 2.2. Mapping between Source Code and WSDL Elements
WSDL ELEMENT | SOURCE CODE |
---|---|
Message name: ICarRentalService_CalculatePrice | Interface name + method name + input/output |
portType name: ICarRentalService | Interface name |
Operation name: CalculatePrice | Method name |
As you have seen so far, you can generate metadata from code by defining an interface, putting some methods in it, and decorating the interface name with the [ServiceContract]
attribute and your methods with the [OperationContract]
attribute. The WCF infrastructure uses reflection to build the WSDL document.
To complete the service side, you need one or more concrete implementations of the service interface. As a recommended practice, the concrete service implementation should be placed in a separate Class Library. As in Listing 2-5, there is no need to use WCF-specific elements in the service implementation code.
You may see the use of the [ServiceBehavior]
and [OperationBehavior]
attributes in the implementation code. Behaviors in general are internal implementation details and therefore are not included in the WSDL file.
Example 2.5. Service Implementation
using System; using Wrox.CarRentalService.Contracts; namespace Wrox.CarRentalService.Implementations.Europe { public class CarRentalService: ICarRentalService { public double CalculatePrice ( DateTime pickupDate, DateTime returnDate, string pickupLocation, string vehiclePreference ) { //call to some internal business logic Random r = new Random(DateTime.Now.Millisecond); return r.NextDouble()*500; } } }
The metadata can be used to generate the necessary client code. For this purpose the client would use svcutil.exe or Add Service Reference from within Visual Studio to obtain a proxy which mimics the functionality of the service. You will find more details about this topic in Chapter 5. Listing 2-6 demonstrates the use of the generated proxy within your client app; Listing 2-7 displays the request SOAP message; and Listing 2-8 displays the response message.
Example 2.6. Client Code
using System; using Wrox.CarRentalService.ConsoleClient.CarRentalProxy; namespace Wrox.CarRentalService.ConsoleClient { class Program { static void Main(string[] args) { using (CarRentalServiceClient carRentalClient = new CarRentalServiceClient()) { double price = carRentalClient.CalculatePrice ( DateTime.Now, DateTime.Now.AddDays(5), "Graz", "Pickup" ); Console.WriteLine("Price {0}",price ); } } } }
Example 2.7. SOAP Messages
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Body> <CalculatePrice xmlns="http://tempuri.org/"> <pickupDate>2010-01-09T19:15:25.2392813+01:00</pickupDate> <returnDate>2010-01-14T19:15:25.2402578+01:00</returnDate> <pickupLocation>Graz</pickupLocation> <vehiclePreference>Pickup</vehiclePreference> </CalculatePrice> </s:Body> </s:Envelope>
The automatically generated WSDL document sometimes needs further enhancements — you should at least provide a meaningful namespace for the service to prevent name clashes with other services. Sometimes there are some internal coding styles and naming patterns for interfaces, classes, and methods which are not allowed or recommended in a service description, or you just don't want to expose your internal names to the outside world.
To further enhance and gain additional control of the generated WSDL document, Table 2-3 (from MSDN) describes the members of the [ServiceContract]
and the [OperationContract]
in detail.
Table 2.3. [ServiceContract] Members
SETTING | DESCRIPTION |
---|---|
| Gets or sets the type of callback contract when the contract is a duplex. |
| Gets or sets the name used to locate the service in an application configuration file. |
| Gets a value that indicates whether the member has a protection level assigned. |
| Gets or sets the name for the |
| Gets or sets the namespace of the |
| Specifies whether the binding for the contract must support the value of the |
| Gets or sets whether sessions are allowed, not allowed, or required. |
As it applies to the [ServiceContract]
attribute, the same goes for the [OperationContract]
attribute — sometimes you need more control over the generation of the WSDL document. See Table 2-4.
Table 2.4. [OperationContract] Members
SETTING | DESCRIPTION |
---|---|
| Specifies the action that uniquely identifies this operation. WCF dispatches request messages to methods based on their action. |
| Indicates that the operation is implemented or can be called asynchronously using a Begin/End method pair. |
| Indicates whether the ProtectionLevel property has been explicitly set. |
| Indicates that the operation only consists of a single input message. The operation has no associated output message. |
| Specifies whether this operation can be the initial operation in a session. |
| Specifies whether WCF attempts to terminate the current session after the operation completes. |
| Specifies the message-level security that an operation requires at runtime. |
Listing 2-9 demonstrates the use of some common attributes. Some of the other attributes are described in Chapter 3.
Example 2.9. [ServiceContract] and [OperationContract] Attributes
using System; using System.ServiceModel; namespace Wrox.CarRentalService.Contracts { [ServiceContract(Namespace="http://wrox/CarRentalService/2009/10", Name="RentalService")] public interface ICarRentalService { [OperationContract(Name = "GetPrice")] double CalculatePrice(DateTime pickupDate, DateTime returnDate, string pickupLocation, string vehiclePreference); [OperationContract( Name = "GetPriceOverloaded")] double CalculatePrice(string pickupLocation, string vehiclePreference); [OperationContract(IsOneWay=true, ProtectionLevel=System.Net.Security.ProtectionLevel.None )] void UpdatePrice(string vehicleId, double newPrice); } }
The result of these attributes is reflected in the WSDL fragment shown in Listing 2-10.
Example 2.10. WSDL Document
<?xml version="1.0" encoding="utf-8"?> <wsdl:definitions . . .. xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace=
"http://wrox/CarRentalService/2009/10" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"> <wsdl:message name="RentalService_GetPrice_InputMessage"> <wsdl:part name="parameters" element="tns:GetPrice" /> </wsdl:message> <wsdl:message name="RentalService_GetPrice_OutputMessage"> <wsdl:part name="parameters" element="tns:GetPriceResponse" /> </wsdl:message> <wsdl:message name="RentalService_GetPriceOverloaded_InputMessage"> <wsdl:part name="parameters" element="tns:GetPriceOverloaded" /> </wsdl:message> <wsdl:message name="RentalService_GetPriceOverloaded_OutputMessage"> <wsdl:part name="parameters" element="tns:GetPriceOverloadedResponse" /> </wsdl:message> <wsdl:message name="RentalService_UpdatePrice_InputMessage"> <wsdl:part name="parameters" element="tns:UpdatePrice" /> </wsdl:message> <wsdl:portType name="RentalService"> <wsdl:operation name="GetPrice"> <wsdl:input wsaw:Action= "http://wrox/CarRentalService/2009/10/RentalService/GetPrice" message="tns:RentalService_GetPrice_InputMessage" /> <wsdl:output wsaw:Action= "http://wrox/CarRentalService/2009/10/RentalService/GetPriceResponse" message="tns:RentalService_GetPrice_OutputMessage" /> </wsdl:operation> <wsdl:operation name="GetPriceOverloaded"> <wsdl:input wsaw:Action= "http://wrox/CarRentalService/2009/10/RentalService/GetPriceOverloaded" message="tns:RentalService_GetPriceOverloaded_InputMessage" /> <wsdl:output wsaw:Action= "http://wrox/CarRentalService/2009/10/RentalService /GetPriceOverloadedResponse" message="tns:RentalService_GetPriceOverloaded_OutputMessage" /> </wsdl:operation> <wsdl:operation name="UpdatePrice"> <wsdl:input wsaw:Action= "http://wrox/CarRentalService/2009/10/RentalService/UpdatePrice" message="tns:RentalService_UpdatePrice_InputMessage" /> </wsdl:operation> </wsdl:portType> </wsdl:definitions>
Previous examples have demonstrated the use of the attributes [OperationContract]
and [ServiceContract]
. The [OperationContract]
attribute essentially determines which operations are provided by your service.
The data which is transferred depends on the individual transfer parameters and the return data type. The specific value of a parameter is typically derived from a .NET object in memory.
This object is converted into a corresponding form and is embedded in the SOAP message. On the other hand, the parameters are extracted from the SOAP message and provided to you as a .NET object. This conversion is performed by special serializer classes.
WCF supports different types of serialization/deserialization, whereby some serializers have been specially developed for WCF, and some have existed in the framework since .NET 1.0/.NET 2.0 and can also be used with WCF.
WCF uses the DataContractSerializer
class from the namespace System.Runtime.Serialization
as standard to serialize or deserialize its objects. This serializer has been specially developed for WCF and is particularly fast, effective, and powerful, which makes it the preferred choice. However, should your requirements exceed the capability of the DataContractSerializer
, you can revert to classical XML serialization.
The DataContractSerializer
supports various data types, including the following:
Primitive data types
Data types with the [DataContract]
attribute
Classes which are marked as serializable
Classes which implement the IXmlSerializable
interface from the namespace System.Xml.Serialization
Enumerations, collections, and generic collections
In the previous examples, there was no need for additional serialization measures because primitive data types are already serializable. However, if you have to work with complex data types as method parameters or return values, you have to use the appropriate attributes ([DataContract], [DataMember]
) to determine how this data is serialized.
These two attributes from the namespace System.Runtime.Serialization
are a design-time construct which tells the DataContractSerializer
how to serialize a special type. Because the structure of the data being exchanged also belongs to the contract, of course, the application of the [DataContract]
and [DataMember]
attributes affects the prepared WSDL document directly. The definition of the data is incorporated into the contract (WSDL) in a format which is independent of platform and programming languages, namely XSD. Therefore, the client and the server cannot agree on the exchange of .NET data types, but are compatible with regard to the neutral XML schema definition format.
To this effect, look at the simple example in Listing 2-11 which does not include the use of the [DataContract]
and [DataMember]
attributes and produces a WSDL document shown in Listing 2-12.
Example 2.11. Service Operation with Simple Types
using System; using System.ServiceModel; namespace Wrox.CarRentalService.Contracts
{ [ServiceContract( Namespace = "http://wrox/CarRentalService/2009/10", Name = "RentalService")] public interface ICarRentalService { [OperationContract] double CheckAvgPricePerDay(string carType); } }
Example 2.12. WSDL Document with Simple Types
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns:tns="http://wrox/CarRentalService/2009/10" elementFormDefault= "qualified" targetNamespace="http://wrox/CarRentalService/2009/10" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="CheckAvgPricePerDay"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="carType" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> </xs:element> <xs:element name="CheckAvgPricePerDayResponse"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" name="CheckAvgPricePerDayResult" type="xs:double" /> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>
In the example defined in Listing 2-11, neither the [Serializable]
attribute nor the [DataContract]
attribute have been used with their corresponding [DataMember]
attribute. This is possible because the DataContractSerializer
class can work with simple .NET data types without the need for any precautionary measures.
However, in most cases you will not use simple data types as the content of your methods in a message-oriented architecture; instead, consideration should be given to the structure of the data being exchanged and make this known in the form of a contract.
In this case you can use the [DataContract]
and [DataMember]
attributes to tell the DataContractSerializer
how and in which form its objects are to be converted.
If you use complex data types in your operations without informing the DataContractSerializer
how they are to be handled, all publicly visible members are automatically serialized. This enables you to serialize POCOs (plain old CLR objects) without additional changes in your code. You can exclude individual members with the [IgnoreDataMember]
attribute. Listing 2-13 shows a service contract with a method called CalculatePrice
, an input parameter of type PriceCalculationRequest
. The return type is defined as PriceCalculationResponse
.
Example 2.13. Service Contract with Complex Types
namespace Wrox.CarRentalService.Contracts { [ServiceContract( Namespace = "http://wrox/CarRentalService/2009/10", Name = "RentalService")] public interface ICarRentalService { [OperationContract] PriceCalculationResponse CalculatePrice ( PriceCalculationRequest request); } } }
Listings 2-14 and 2-15 demonstrate the use of POCOs, the [IgnoreDataMember]
attribute, and the resulting WSDL document.
Example 2.14. POCOs and [IgnoreDataMember]
using System; using System.Runtime.Serialization; using System.Xml.Serialization; namespace Wrox.CarRentalService.Contracts { public class PriceCalculationRequest { public DateTime PickupDate { get; set; } public DateTime ReturnDate { get; set; } public string PickupLocation { get; set; } public string ReturnLocation { get; set; } private string VehicleType { get; set; } [IgnoreDataMember] public string Color { get; set; } } }
Example 2.15. WSDL Document
<?xml version="1.0" encoding="utf-8"?> <xs:schema xmlns:tns="http://schemas.datacontract.org/2004/07/ Wrox.CarRentalService.Contracts"
elementFormDefault="qualified" targetNamespace="http://schemas.datacontract.org/2004/07/ Wrox.CarRentalService.Contracts" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:complexType name="PriceCalculationRequest"> <xs:sequence> <xs:element minOccurs="0" name="PickupDate" type="xs:dateTime" /> <xs:element minOccurs="0" name="PickupLocation" nillable="true" type="xs:string" /> <xs:element minOccurs="0" name="ReturnDate" type="xs:dateTime" /> <xs:element minOccurs="0" name="ReturnLocation" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> <xs:element name="PriceCalculationRequest" nillable="true" type="tns:PriceCalculationRequest" /> <xs:complexType name="PriceCalculationResponse"> <xs:sequence> <xs:element minOccurs="0" name="Flag" type="xs:int" /> <xs:element minOccurs="0" name="Price" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType> <xs:element name="PriceCalculationResponse" nillable="true" type="tns:PriceCalculationResponse" /> </xs:schema>
However, you will only be able to disclose your business objects including all public properties as standard in very rare cases.
It is important to define contracts explicitly in an SOA scenario and to request control of the names including namespaces of your elements, the order in which the individual attributes occur, and whether or not this is absolutely necessary.
Consequently, the data contract and the service contract together serve to form a formal agreement between service and client and provide a detailed description of the structure of the data which is exchanged.
The DataContractSerializer
uses the [DataContract]
and [DataMember]
attributes to control the exact serialization process. A [DataContract]
is an opt-in model which means that only properties or member variables which have been explicitly assigned the [DataMember]
attribute are serialized. The visibility of the properties is of no consequence. Listing 2-16 demonstrates the use of the [DataContract]
and [DataMember]
attributes. Listing 2-17 displays the resulting XSD part.
Example 2.16. [DataContract] and [DataMember] Attributes
using System; using System.Runtime.Serialization; using System.Xml.Serialization; namespace Wrox.CarRentalService.Contracts { [DataContract]
public class PriceCalculationRequest { [DataMember] public DateTime PickupDate { get; set; } [DataMember] public DateTime ReturnDate { get; set; } [DataMember] public string PickupLocation { get; set; } [DataMember] public string ReturnLocation { get; set; } public string Color { get; set; } } }
Example 2.17. XSD Part
<xs:complexType name="PriceCalculationRequest"> <xs:sequence> <xs:element minOccurs="0" name="PickupDate" type="xs:dateTime" /> <xs:element minOccurs="0" name="PickupLocation" nillable="true" type="xs:string" /> <xs:element minOccurs="0" name="ReturnDate" type="xs:dateTime" /> <xs:element minOccurs="0" name="ReturnLocation" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType>
The characteristics of the schema, which are automatically generated by WCF, include the following:
Namespaces: http://schemas.datacontract.org/2004/07/
+ CLR and http://schemas.datacontract.org/2004/07/Wrox.CarRentalService.Contracts
Attributes are arranged in alphabetical order
<xs:element minOccurs="0" name= PickupDate "
<xs:element minOccurs="0" name="PickupLocation "
The minOccurs
attribute is set to a value of 0
, which means that this attribute is optional
The primitive CLR data types are automatically shown in the form of XSD data types. DateTime PickupDate
becomes xs: dateTime
As you have seen from the previous examples, your data classes are typically assigned the [DataContract]
attribute and the individual member variables are assigned the [DataMember]
attribute.
Listing 2-18 demonstrates additional possibilities when using the [DataContract]
and [DataMember]
attributes to influence the generated schema.
Example 2.18. [DataContract] and [DataMember] Attributes
[DataContract( Name="PriceCalculationRequest", Namespace= "http://schemas.datacontract.org/2004/07/ Wrox.CarRentalService.Contracts") ] public class PriceReq { [DataMember(Name="PickupDate",Order=1, IsRequired=true )] private DateTime FromDate { get; set; } [DataMember(Name = "ReturnDate", Order = 3)] public DateTime ToDate{ get; set; } [DataMember( Order = 2)] public string PickupLocation { get; set; } [DataMember(Order = 4)] public string ReturnLocation { get; set; } public string CarType { get; set; } }
Even though the original definition of the PriceCalculationRequest
class has undergone significant changes, no problems occur when serializing an instance followed by comparison with the schema prepared beforehand. The [DataContract]
and [DataMember]
attributes create an XML info set which is valid in accordance with the contract (XSD).
To illustrate this, refer to the newly prepared schema in Listing 2-19 which is compatible with the original schema.
Example 2.19. Equivalent XSD
<xs:complexType name="PriceCalculationRequest"> <xs:sequence> <xs:element minOccurs="0" name="PickupDate" type="xs:dateTime" /> <xs:element minOccurs="0" name="PickupLocation" nillable="true" type="xs:string" /> <xs:element minOccurs="0" name="ReturnDate" type="xs:dateTime" /> <xs:element minOccurs="0" name="ReturnLocation" nillable="true" type="xs:string" /> </xs:sequence> </xs:complexType>
The following observations are made when using the [DataContract]
and [DataMember]
attributes from the namespace System.Runtime.Serialization
:
The default namespace can be overwritten by the namespace property.
The name property of the [DataContract]
attribute serves to define the name of the complex type in XSD.
The access modifier of a member variable is ignored.
CarType
does not appear as an element opt-in model — only member variables which are explicitly assigned the [DataMember]
attribute are serialized.
If the property is IsRequired=true
, the attribute is no longer optional. If this attribute is not located during deserializing, an error occurs.
The order can be determined explicitly with the order property.
The name property for the [DataMember]
attribute is used to determine the name of the XSD element.
The properties of the [DataContract]
and [DataMember]
attributes primarily play an important role during versioning or in interop scenarios. If, for example, you have to keep a default XML schema but do not wish or are unable to make any or only minor changes to your .NET classes, you can attempt to create a compatible data contract with the attributes and properties previously mentioned.
In object-oriented programming you frequently use a reference to an inherited class instead of the base class. This concept of polymorphism is frequently used, although if done in conjunction with WCF, an error occurs if special precautionary measures are not taken.
The method signature typically only contains the base class and consequently, the definition of the inherited classes is not included in the WSDL document either. This naturally also means that the inherited class for the generated proxy code is unknown and can therefore not be deserialized.
The same problem also occurs, for example, if the parameter which you use is a non-generic collection class as shown in Listing 2-20.
Example 2.20. Base Class Response Argument and ArrayList
namespace Wrox.CarRentalService.Contracts { [ServiceContract( Namespace = "http://wrox/CarRentalService/2009/10", Name = "RentalService")] [ServiceKnownType(typeof(PriceCalculationResponseDetailed))] public interface ICarRentalService { [OperationContract] PriceCalculationResponse CalculatePrice ( PriceCalculationRequest request ); [OperationContract] System.Collections.ArrayList GetPrices(); }
However, the specific implementation of the method from Listing 2-20 does not use the PriceCalculationResponse
base class as a return value; it uses an inherited version named PriceCalculationResponseDetailed
. Listing 2-21 shows the inherited class and the concrete service implementation.
Example 2.21. Inherited Class and Service Implementation
[DataContract] public class PriceCalculationResponseDetailed : PriceCalculationResponse { [DataMember] public string Currency { get; set; } } public PriceCalculationResponse CalculatePrice(PriceCalculationRequest request) { PriceCalculationResponseDetailed resp = null; reps=new PriceCalculationResponseDetailed(); resp.Price = 120; resp.Currency = "euro"; return resp; }
Now, if the client proxy calls the method CalculatePrice
the return is not expected and therefore produces the following error:
There was an error while trying to serialize parameter http://wrox/CarRentalService/2009/10:CalculatePriceResult. The InnerException message was 'Type 'Wrox.CarRentalService.Contracts.PriceCalculationResponseDetailed' with data contract name 'PriceCalculationResponseDetailed:http://schemas.datacontract.org/2004/07/Wrox.CarRentalService.Contracts' is not expected. Consider using a DataContractResolver or add any types not known statically to the list of known types — for example, by using the KnownTypeAttribute attribute or by adding them to the list of known types passed to DataContractSerializer.'
As the error message shows, this problem can be solved with the KnownType
attribute. The KnownType
attribute enables the inherited data type to be added to the data contract, thereby facilitating correct serialization and deserialization.
You should use either the [KnownType]
attribute in your data class or the [ServiceKnownType]
attribute in the definition of your service contract or operation contract (regardless of whether you wish to use the inherited class or the base class throughout, or whether you only wish to permit this for special service contracts or service operations).
A simple use of the [KnownType]
attribute exists in enumerating all the derived classes in the data contract for the base class.
Listing 2-22 demonstrates the use of the [KnownType]
attribute.
Example 2.22. KnownType on DataContract
[DataContract] [KnownType(typeof(PriceCalculationResponseDetailed))] public class PriceCalculationResponse { [DataMember] public double Price { get; set; } }
This attribute tells the DataContractSerializer
to include the inherited class in the data contract in addition to the base contract. The inheritance hierarchy is also retained in the XSD, as you can see in Listing 2-23.
Example 2.23. XSD and Inheritance
<xs:complexType name="PriceCalculationResponseDetailed"> <xs:extension base="tns:PriceCalculationResponse"> <xs:sequence> <xs:element minOccurs="0" name="Currency" type="xs:string" /> </xs:sequence>
If you only expect to encounter an instance of an inherited class in certain methods, or if an inherited version is permitted in all the operations of a service contract, you can use the [ServiceKnownType]
attribute either at service level (Listing 2-24) or at the operational level, as shown in Listing 2-25. Using the [ServiceKnownType]
at the operational level means that the inherited class is only accepted for this method. Using the attribute at the service level means that every method accepts the inherited version.
Example 2.24. [ServiceKnownType] for One Method
[OperationContract] [ServiceKnownType(typeof(PriceCalculationResponseDetailed))] PriceCalculationResponse CalculatePrice(PriceCalculationRequest request);
Example 2.25. [ServiceKnownType] at the Service Level
[OperationContract] [ServiceKnownType(typeof(PriceCalculationResponseDetailed))] PriceCalculationResponse CalculatePrice(PriceCalculationRequest request);
Disclosing a more flexible KnownType
variant involves swapping this information to the configuration file in the system.runtime.serialization
section, as shown in Listing 2-26.
Example 2.26. dataContractSerializer Section in the Config File
<system.runtime.serialization> <dataContractSerializer> <declaredTypes> <add type="Wrox.CarRentalService. Contracts.PriceCalculationResponse, Wrox.CarRentalService.Contracts"> <knownType type="Wrox.CarRentalService.Contracts. PriceCalculationResponseDetailed, Wrox.CarRentalService.Contracts"/> </add> </declaredTypes> </dataContractSerializer> </system.runtime.serialization>
There is a certain degree of inflexibility in telling the previous types (KnownType, ServiceKnownType, config
) of WCF which inherited classes are supported. For example, every time you derive a new class, you would have to add an additional [KnownType]
attribute to your base contract. Therefore, WCF offers you other possibilities for disclosing KnownTypes
. Instead of specifying a fixed set of derived classes, you provide the name of a method which returns a list of derived classes. This can be done both with the [KnownType]
and [ServiceType]
attributes. Listing 2-27 displays the use of the [KnownType]
attribute with a static method called GetTypes
.
Software development is particularly given to ongoing changes in requirements and, thus, in the offered functionality and structure of the data being exchanged. In principle, any minor change in your operations or data can bring about a change in the WSDL document, leading to a new version of your service. Generally speaking, however, it is neither organizationally nor technically feasible to update your service components continually and to respond to changes in the services offered.
Therefore, you will be shown techniques and processes in this chapter which permit you to maintain the exchange of compatible messages in spite of changes in the service or data contract. These changes are also regarded as changes which do not lead to a break of the contract. While the contracts do not have to be identical so-to-speak, it is imperative that they are compatible. In the case of a service contract, this would mean, for example, that the addition of a new method would not lead to incompatibilities because the client is unfamiliar with this new method anyway. The same applies in data contracts; if a new optional field is added, for example, this field is simply ignored by the DataContractSerializer
and filled with a default.
However, a new contract is unavoidable in certain situations — for example, if the name of an operation is changed, or if a mandatory field is added. Changes of this type result in a break of contract, creating the need for a new version. In this case, you are advised to adapt the namespaces to the new version and to host your service at a new endpoint.
The WCF or the DataContractSerializer
is very tolerant of changes in the structure of the data contract.
For example, it is easy to add additional data members or to leave out data members in which the IsRequired
attribute is set to false. One benefit to this is that there is no need to worry about these types of minor details; however, in a service-oriented environment it is important to adhere to contracts or, where changes are necessary, to disclose them formally and officially.
Let's assume that a client makes the following changes to version 1 of the service contract/data contract shown in Listing 2-28.
Example 2.28. Service and Data Contract Version 1
[ServiceContract( Namespace = "http://wrox/CarRentalService/2009/10", Name = "RentalService")] public interface ICarRentalService { [OperationContract] PriceCalculationResponse CalculatePrice ( PriceCalculationRequest request, int priority ); } [DataContract] public class PriceCalculationRequest { [DataMember] public DateTime PickupDate { get; set; } [DataMember] public DateTime ReturnDate { get; set; } [DataMember] public string PickupLocation { get; set; } [DataMember] public string ReturnLocation { get; set; } }
The changes to the operation and data contract in Listing 2-29 would lead to compatible contracts.
Example 2.29. Service and Data Contract Version 2
[ServiceContract( Namespace = "http://wrox/CarRentalService/2009/10", Name = "RentalService")] public interface ICarRentalService { [OperationContract] PriceCalculationResponse CalculatePrice ( PriceCalculationRequest request, string someData ); } [DataContract] public class PriceCalculationRequest { [DataMember] public DateTime PickupDate { get; set; } [DataMember] public DateTime ReturnDate { get; set; } [DataMember] public string PickupLocation { get; set; } [DataMember] public string Color { get; set; } }
The following conclusions can be drawn from the previous example:
Method parameters can be deleted or added
Data members can be deleted or added
Data types may be changed provided they remain compatible
Methods can be added
These changes would not lead to any problems of a technical nature. However, you would have to define details of procedures for dealing with missing values, for example. It is possible that a member may no longer be required and can therefore be ignored without any problems.
A problem that arises again and again in connection with the versioning of data contracts is the newly added attribute in which deserialization to an old data contract is ignored.
Let's take a closer look at the original example relating to the price inquiry — the client sends a price inquiry to the service and receives a reply object including a flag and a price. The client can confirm the offered price by calling the ConfirmPrice
method again, as shown in Listing 2-30. Listing 2-31 displays the service contract and data contract.
Example 2.30. ConfirmPrice Service Contract
[ServiceContract( Namespace = "http://wrox/CarRentalService/2009/10", Name = "RentalService")] public interface ICarRentalService { [OperationContract] PriceCalculationResponse CalculatePrice ( PriceCalculationRequest request ); [OperationContract] bool ConfirmPrice(PriceCalculationResponse resp); } [DataContract] public class PriceCalculationResponse { [DataMember] public int Flag { get; set; } [DataMember] public string Price { get; set; } }
Example 2.31. ConfirmPrice Client Code
using (RentalServiceClient carRentalClient = new RentalServiceClient()) { PriceCalculationRequest req = new PriceCalculationRequest(); req.PickupDate = System.DateTime.Now.AddDays(5); req.ReturnDate = System.DateTime.Now.AddDays(7); req.PickupLocation = "Graz"; req.ReturnLocation = "Villach"; PriceCalculationResponse resp; resp = carRentalClient.CalculatePrice(req); Console.WriteLine("Price {0}", resp.Price); Console.WriteLine(carRentalClient.ConfirmPrice(resp)); }
During internationalization, add the Currency
attribute to the data contract.
Your service can still use the client code because the additional field does not lead to any problems if the IsRequired
property does not apply. However, the value of the Currency
attribute is lost on the client side because the client is unfamiliar with this newly added attribute and is also unable to deserialize its content.
The solution to this round-tripping problem is to implement the IExtensibleDataObject
interface and the requisite ExtensionDataObject
field. If the DataContractSerializer
detects unknown elements in the XML document, they are written to the ExtensionDataObject
property bag during deserialization. The content of the ExtensionDataObject
is retained when further use is made of this object, resulting in no data loss between different versions of data contracts.
If you use the Add Service Reference dialog box, the data classes on the client side automatically implement this interface. If you wish to reciprocate on the server side, you have to implement the interface manually in the data classes — the code in Listing 2-32 is sufficient for this.
Example 2.32. IExtensibleDataObject
[DataContract] public class PriceCalculationResponse :IExtensibleDataObject { public ExtensionDataObject ExtensionData { get; set; } [DataMember] public int Flag { get; set; } [DataMember] public double Price { get; set; } [DataMember] public string Currency { get; set; } }
You can also prevent ExtensionData
in general by setting a service behavior in your configuration file with the property <dataContractSerializer ignoreExtensionDataObject="true"/>
. You should take particular care when working with extension data on the server side because it may cause a security gap.
If precise compliance between schemas is required, you should assign a new version to your contract every time a change is implemented.
If there is no need for precise compliance between schemas, you should heed the following points:
You can add new methods at any time
You may not delete any existing methods
The parameter data types must remain compatible
If precise compliance between schemas is required, you should assign a new version to your contract every time a change is implemented.
If there is no need for precise compliance between schemas, you should heed the following points:
Data contracts should not be assigned new versions as a result of inheritance. Create a new independent data class instead.
To facilitate round-tripping, you should implement the IExtensibleDataObject
interface at the start.
If you have to change the name of your data class or of some members, compatible data contracts can be created using the DataContract
or DataMember
attributes.
Do not make subsequent changes to your data types.
Do not change the order in which your data members appear by using the property [DataMember(Order=?)]
.
Leave the default IsRequired
value (false
) unchanged.
You can add additional data members at any time but you should remember that this would change the serialization order. This problem is avoided by setting the Order
property for new members to the current version's value. Therefore, data members added in version 2 should be assigned Order=2
.
Data members should not be deleted.
Subsequent changes should not be made to the IsRequired
property.
Listing 2-33 displays some of the recommended practices.
Example 2.33. Best Practices of Versioning
[DataContract( Name="PriceCalculationRequest", Namespace="http://schemas.datacontract.org/2004/07/ Wrox.CarRentalService.Contracts")] public class PriceReq //Version 1 { [DataMember()] private DateTime PickupDate { get; set; } [DataMember()] public DateTime ReturnDate { get; set; } [DataMember()] public string PickupLocation { get; set; } [DataMember()] public string ReturnLocation { get; set; } } [DataContract( Name = "PriceCalculationRequest", Namespace = "http://schemas.datacontract.org/2004/07/ Wrox.CarRentalService.Contracts")] public class PriceReq //Version 2
{ [DataMember(Name = "PickupDate")] private DateTime FromDate { get; set; } [DataMember(Name = "ReturnDate")] public DateTime ToDate { get; set; } [DataMember(Order = 2)] public string PickupLocation { get; set; } [DataMember()] public string ReturnLocation { get; set; } [DataMember(Order=2)] //from Version 2 public string CarType { get; set; } }
At the start of this chapter, it was explained that SOAP messages are exchanged between the client and the server. However, you have only been able to influence the content of the SOAP body up to now. In most cases, you will be able to manage with data contracts and operation contracts, which is recommended. However, in certain situations you may have to gain total control of the entire SOAP message (header and body). The SOAP header is a suitable method for transferring additional data without extending the data class (for example, a security token).
Message contracts give you complete control over the content of the SOAP header, as well as the structure of the SOAP body. Up to now you have used data contracts as transfer or return parameters — these are now simply replaced by message contracts. If you opt for message contracts, it is important to use message contracts for all the parameters — methods cannot contain a mixture of data contracts and message contracts.
Listing 2-34 shows how the three attributes [MessageContract], [MessageHeader]
, and [MessageBody]
are used. In addition to the price request object, a username is also transferred in the SOAP header.
Example 2.34. [MessageContract], [MessageHeader], and [MessageBody]
[ServiceContract( Namespace = "http://wrox/CarRentalService/2009/10", Name = "RentalService")] public interface ICarRentalService { [OperationContract] PriceCalculationResp CalculatePrice(PriceCalculation request); } [MessageContract] public class PriceCalculation { [MessageHeader]
public CustomHeader SoapHeader { get; set; } [MessageBodyMember] public PriceCalculationRequest PriceRequest { get; set; } } [DataContract] public class CustomHeader { [DataMember] public string Username { get; set; } } [DataContract] public class PriceCalculationRequest { [DataMember] public DateTime PickupDate { get; set; } [DataMember] public DateTime ReturnDate { get; set; } [DataMember] public string PickupLocation { get; set; } [DataMember] public string ReturnLocation { get; set; } }
Listing 2-35 illustrates the usage on the client side and Listing 2-36 shows the SOAP message.
Example 2.35. Message Contracts — Client Side
using (RentalServiceClient carRentalClient = new RentalServiceClient()) { PriceCalculation calc = new PriceCalculation(); PriceCalculationRequest req = new PriceCalculationRequest(); req.PickupDate = System.DateTime.Now.AddDays(5); req.ReturnDate = System.DateTime.Now.AddDays(7); req.PickupLocation = "Graz"; req.ReturnLocation = "Villach"; calc.PriceRequest = req; CustomHeader sHeader = new CustomHeader(); sHeader.Username = "Johann"; calc.SoapHeader = sHeader; PriceCalculationResp resp= carRentalClient.CalculatePrice(calc); }
Example 2.36. SOAP and Message Headers
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> <s:Header> <h:SoapHeader xmlns:h="http://wrox/CarRentalService/2009/10" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <Username xmlns="http://schemas.datacontract.org/2004/07/ Wrox.CarRentalService.Contracts"> Johann </Username> </h:SoapHeader> </s:Header> <s:Body> <PriceCalculation xmlns="http://wrox/CarRentalService/2009/10"> <PriceRequest xmlns:a= "http://schemas.datacontract.org/2004/07/ Wrox.CarRentalService.Contracts" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <a:PickupDate>2010-01-15T14:15:08.1683905+01:00</a:PickupDate> <a:PickupLocation>Graz</a:PickupLocation> <a:ReturnDate>2010-01-17T14:15:08.1693671+01:00</a:ReturnDate> <a:ReturnLocation>Villach</a:ReturnLocation> </PriceRequest> </PriceCalculation> </s:Body> </s:Envelope>
You can find other specific examples of how message contracts are used in Chapter 7.
As already mentioned in the introduction, WCF supports a number of types of serialization. By default, WCF uses the DataContractSerializer
class to serialize your CLR objects. In the previous examples you have also seen the use of different properties in connection with the [DataContract]
and [DataMember]
attributes. To an extent, you can control the structure of the generated XML info set.
The DataContractSerializer
is optimized for performance, although it offers fewer possibilities as far as influencing the structure of the created document is concerned. If the possibilities offered do not meet your needs, you can switch to the XML Serializer. The XML Serializer also offers you more control in interop scenarios or in communication with old ASP.NET Web Services.
To instruct WCF to use the XML Serializer, you can simply assign your service contract the [XmlSerializerFormat]
attribute. However, should you only wish to use the XML Serializer for certain operations, you can also apply this attribute to your methods. Note that the XML Serializer also serializes all public properties by default. You can find further information about these classes in the Framework Documentation on MSDN.
Listing 2-37 demonstrates the use of the [XMLSerializerFormat]
with attributes from the System.XML.Serialization namespace and Listing 2-38 displays the XSD for the PriceCalculationRequest
type.
Example 2.37. XML Serialization
[XmlSerializerFormat] [ServiceContract( Namespace = "http://wrox/CarRentalService/2009/10", Name = "RentalService")] public interface ICarRentalService { [OperationContract] PriceCalculationResponse CalculatePrice ( PriceCalculationRequest request ); } [XmlRoot("PriceRequest")] public class PriceCalculationRequest { [XmlAttribute()] public string PickupLocation { get; set; } [XmlElement(ElementName="ReturnLoc")] public string ReturnLocation { get; set; } [XmlIgnore()] public DateTime PickupDate { get; set; } private DateTime ReturnDate { get; set; } }
Example 2.38. XSD for the PriceCalculationRequest Type
<xs:element name="CalculatePrice"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" maxOccurs="1" name="request" type="tns:PriceCalculationRequest" /> </xs:sequence> </xs:complexType> </xs:element> <xs:complexType name="PriceCalculationRequest"> <xs:sequence> <xs:element minOccurs="0" maxOccurs="1" name="ReturnLoc" type="xs:string" /> </xs:sequence> <xs:attribute name="PickupLocation" type="xs:string" /> </xs:complexType> <xs:element name="CalculatePriceResponse"> <xs:complexType> <xs:sequence> <xs:element minOccurs="0" maxOccurs="1"
name="CalculatePriceResult" type="tns:PriceCalculationResponse" /> </xs:sequence> </xs:complexType> </xs:element> ... </xs:complexType>
3.144.97.187