Service operations can receive messages,
process them, and send them messages. Messages are described using operation
contracts
Out and Ref Parameters:
If the return value alone is not enough
to describe a reply message, out parameters may be used. Additionally, you may use reference parameters to make a
parameter part of both the request and the reply message. The parameters must
be of types that can be serialized (converted to XML). By default, WCF uses a
component called the DataContractSerializer
class to perform this conversion. Most primitive types (such as int, string,
float, and DateTime.) are supported. User-defined types must normally have a
data contract
Message Parameter:
To isolate .NET parameter names from contract
names, you can use the MessageParameterAttribute attribute, and use the Name
property to set the contract name.
[OperationContract]
public int SomeOperation([MessageParameter(Name="fromCity")] string originCity);
Message Contracts:
Message contracts allow you to exercise
more control over resultant messages. For instance, you can decide which pieces
of information should be in the message body and which should be in the message
headers
Describing Messages by Using Streams:
Another way to describe messages in
operations is to use the Stream class or one of its derived classes in an
operation contract or as a message contract body member (it must be the only
member in this case). For incoming messages, the type must be Stream—you cannot
use derived classes.
Instead of invoking the serializer, WCF
retrieves data from a stream and puts it directly into an outgoing message, or
retrieves data from an incoming message and puts it directly into a stream. The
following sample shows the use of streams.
[OperationContract]
public Stream
DownloadFile(string fileName);
You cannot combine Stream and non-stream
data in a single message body. Use a message contract to put the extra data in
message headers. The following example shows the incorrect usage of streams
when defining the operation contract.
//
Incorrect:
//
[OperationContract]
// public void
UploadFile (string fileName, Stream fileData);
The following sample shows the correct
usage of streams when defining an operation contract.
[OperationContract]
public void FileUpload(UploadFileMessage
message);
[MessageContract]
public class UploadFileMessage
{
[MessageHeader]
public string fileName;
[MessageBodyMember]
public Stream fileData;
}
Using Derived Types
You may want to use a base type in an
operation or a message contract, and then use a derived type when actually
invoking the operation. In this case, you must use either the ServiceKnownTypeAttribute attribute or
some alternative mechanism to allow the use of derived types. Consider the
following operation.
[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);
Assume that two types, Book and
Magazine, derive from LibraryItem. To use these types in the
IsLibraryItemAvailable operation, you can change the operation as follows:
[OperationContract]
[ServiceKnownType(typeof(Book))]
[ServiceKnownType(typeof(Magazine))]
public bool IsLibraryItemAvailable(LibraryItem item);
Alternatively, you can use the KnownTypeAttribute attribute when the default DataContractSerializer is in use, as shown in the following example code.
[OperationContract]
public bool IsLibraryItemAvailable(LibraryItem item);
// code
omitted
[DataContract]
[KnownType(typeof(Book))]
[KnownType(typeof(Magazine))]
public class LibraryItem
{
//code
omitted
}
Controlling the Serialization Process
You can do a number of things to
customize the way data is serialized.
Changing Server Serialization Settings
When the default DataContractSerializer is in use, you can control some aspects of
the serialization process on the service by applying the ServiceBehaviorAttribute attribute to the service. Specifically,
you may use the MaxItemsInObjectGraph
property to set the quota that limits the maximum number of objects the DataContractSerializer deserializes.
You can use the IgnoreExtensionDataObject
property to turn off the round-tripping versioning feature.
[ServiceBehavior(MaxItemsInObjectGraph
= 100000)]
public class MyDataService : IDataService
{
public DataPoint[] GetData()
{
//
Implementation omitted
}
}
Shared Type Serialization, Object Graph Preservation, and Custom
Serializers
The DataContractSerializer
serializes using data contract names and not .NET type names. This is
consistent with service-oriented architecture tenets and allows for a great
degree of flexibility—the .NET types can change without affecting the wire
contract. In rare cases, you may want to serialize actual .NET type names,
thereby introducing a tight coupling between the client and the server, similar
to the .NET Framework remoting technology. This is not a recommended practice,
except in rare cases that usually occur when migrating to WCF from .NET
Framework remoting. In this case, you must use the NetDataContractSerializer class instead of the DataContractSerializer class.
The DataContractSerializer
normally serializes object graphs as object trees. That is, if the same object
is referred to more than once, it is serialized more than once. For example,
consider a PurchaseOrder instance that has two fields of type Address called
billTo and shipTo. If both fields are set to the same Address instance, there
are two identical Address instances after serialization and deserialization.
This is done because there is no standard interoperable way to represent object
graphs in XML (except for the legacy SOAP encoded standard available on the XmlSerializer, as described in the
previous section on Style and Use). Serializing object graphs as trees has
certain disadvantages, for example, graphs with circular references cannot be
serialized. Occasionally, it is necessary to switch to true object graph
serialization, even though it is not interoperable. This can be done by using
the DataContractSerializer
constructed with the preserveObjectReferences parameter set to true.