As versions evolve, data contracts between the client and the server may have different versions. In WCF, there are two types of Version Control for data contracts: new members and missing members. Adding a Member means that the sender includes a new member. The new member is ignored by default. If a member is missing, the sender lacks a member. The default processing method is to assign a default value to the Member.
When a member is missing, an unexpected error may occur if only the default value is assigned to the member. The reason is that missing members may be necessary for correct operations. To avoid such a situation, you can set the missing members as required members by setting the value to true using the isrequired attribute of the datamember feature. For example:
[Datacontract]
Struct contact
{
[Datamember]
Public String firstname;
[Datamember]
Public String lastname;
[Datamember (isrequired = true)]
Public String address;
}
If a member in a message is marked as a required member, the call will be canceled when the receiver's datacontractserializer cannot find the required information for deserialization. The sender will cause a netdispatcherfaultexception exception. For example, the data contract of the server is defined as above. The address field is a required member, and the data contract of the client is as follows:
[Datacontract]
Struct contact
{
[Datamember]
Public String firstname;
[Datamember]
Public String lastname;
}
In this case, if the client sends a call to the service, the call will not reach the service due to an exception.
Clients and services can mark some or all data members in their data contracts as necessary and are completely independent from each other. The more members are marked as required, the more secure the interaction with the service or client, but at the cost of flexibility and version compatibility.
This book summarizes several situations of Data contract version control, and displays the version compatibility of essential members in a table:
Isrequired |
V1 to V2 |
V2 to V1 |
false |
Yes |
Yes |
true |
NO |
Yes |
Assume that V2 contains all data members of V1 and defines new members. The table covers several versions.
V1 to V2: indicates the absence of members. If isrequired is false, the interaction is normal, and the default value is set for missing members. If isrequired is true, an exception is thrown and the message cannot be sent normally.
V2 to V1: new members are added. No matter whether the isrequired value is true or false, WCF interacts normally by ignoring new members.
For some special data types. To some extent, this limits the design of WCF by CLR developers. These special data types include enumeration, delegation, dataset and datatable, generic, and set.
Enumeration
The support for enumeration in WCF is not bad. First, the enumeration type itself supports serialization. You do not need to set any features. All members of the enumeration type are part of the data contract. If only one part of the enumerated type needs to be part of the data contract, the datacontract and enummember features must be used. For example:
[Datacontract]
Enum contacttype
{
[Enummember]
Customer,
[Enummember]
Vendor,
// Will not be part of data contract
Partner
}
The representation generated by the client is:
Enum contacttype
{
Customer,
Vendor
}
Delegate
WCF does not support delegation and events well. This is because the internal call list structure of the delegate is local, and the client or service cannot share the delegate list structure across service boundaries. In addition, we cannot guarantee that the target objects in the internal list are all serializable, or they are all valid data contracts. This may cause the serialization operation to succeed or fail. Therefore, the best practice is not to use delegated members or events as part of a Data contract.
Datasets and Data Tables
Dataset and able types are serializable, so we can receive or return data tables or datasets in the service contract.
If the service contract uses the dataset and able types, the generated proxy file does not directly use the dataset and datatable types, but contains the definition of the datatable data contract (only contains the datatable style, not anyCode). However, we can manually modify these definitions. For example, a service contract:
[Servicecontract ()]
Public interface icontactmanager
{
[Operationcontract]
Void addcontact (contact );
[Operationcontract]
Void addcontacts (datatable contacts );
[Operationcontract]
Datatable getcontacts ();
}
The generated proxy file may be like this:
Public interface icontactmanager
{
[System. servicemodel. operationcontractattribute (Action = "http://tempuri.org/IContactManager/AddContact", replyaction = "http://tempuri.org/IContactManager/AddContactResponse")]
[System. servicemodel. xmlserializerformatattribute ()]
Void addcontact (contact );
[System. servicemodel. operationcontractattribute (Action = "http://tempuri.org/IContactManager/AddContacts", replyaction = "http://tempuri.org/IContactManager/AddContactsResponse")]
[System. servicemodel. xmlserializerformatattribute ()]
Addcontactsresponse addcontacts (addcontactsrequest request );
[System. servicemodel. operationcontractattribute (Action = "http://tempuri.org/IContactManager/GetContacts", replyaction = "http://tempuri.org/IContactManager/GetContactsResponse")]
[System. servicemodel. xmlserializerformatattribute ()]
Getcontactsresponse getcontacts (getcontactsrequest request );
}
The proxy class is defined as follows:
[System. Diagnostics. debuggerstepthroughattribute ()]
[System. codedom. compiler. generatedcodeattribute ("system. servicemodel", "3.0.0.0")]
Public partial class contactmanagerclient: system. servicemodel. clientbase <icontactmanager>, icontactmanager
{
// The remaining members are omitted;
Public void addcontact (contact)
{
Base. Channel. addcontact (contact );
}
Addcontactsresponse icontactmanager. addcontacts (addcontactsrequest request)
{
Return base. Channel. addcontacts (request );
}
Public void addcontacts (addcontactscontacts contacts)
{
Addcontactsrequest invalue = new addcontactsrequest ();
Invalue. Contacts = contacts;
Addcontactsresponse retval = (icontactmanager) (this). addcontacts (invalue );
}
Getcontactsresponse icontactmanager. getcontacts (getcontactsrequest request)
{
Return base. Channel. getcontacts (request );
}
Public getcontactsresponsegetcontactsresult getcontacts ()
{
Getcontactsrequest invalue = new getcontactsrequest ();
Getcontactsresponse retval = (icontactmanager) (this). getcontacts (invalue );
Return retval. getcontactsresult;
}
}
We can manually change addcontacts () and getcontacts () Methods:
Public void addcontacts (datatable contacts)
{
Addcontactsrequest invalue = new addcontactsrequest ();
Invalue. Contacts = contacts;
Addcontactsresponse retval = (icontactmanager) (this). addcontacts (invalue );
}
Public datatable getcontacts ()
{
Getcontactsrequest invalue = new getcontactsrequest ();
Getcontactsresponse retval = (icontactmanager) (this). getcontacts (invalue );
Return retval. getcontactsresult;
}
Of course, the precondition is that we need to modify the addcontactrequest class and getcontactsresponse. For example, we need to change the contacts Member of the addcontactrequest class from the original addcontactscontacts type to the able type; modify the getcontactsresult member in getcontactsresponse from the original getcontactsresponsegetcontactsresult type to the able type.
The automatically generated proxy classes are very complex. In fact, we can simplify them completely. First, modify the service contract definition of the client to be exactly the same as that of the service contract of the server:
[Servicecontract ()]
Public interface icontactmanager
{
[Operationcontract]
Void addcontact (contact );
[Operationcontract]
Void addcontacts (datatable contacts );
[Operationcontract]
Datatable getcontacts ();
}
Then modify the proxy class contactmanagerclient:
Public partial class contactmanagerclient: system. servicemodel. clientbase <icontactmanager>, icontactmanager
{
Public void addcontact (contact)
{
Base. Channel. addcontact (contact );
}
Public void addcontacts (datatable contacts)
{
Base. Channel. addcontacts (contacts );
}
Public datatable getcontacts ()
{
Return base. Channel. getcontacts ();
}
}
After modification, the running results are identical.
Note that the datarow type cannot be serialized.
In WCF, you can also use the type-safe subclass of datatable and dataset. This document also provides examples. However, the best practice of WCF is to avoid the use of datatable and dataset, as well as the type-safe subclass using datatable and dataset. The book explains the reasons:
"For client and service of WCF, although dataset, datatable and their type-safe derived objects can be used through ADO. NET and Visual Studio Tools, this method is too cumbersome. In addition, these data access types are specific. Net types. During serialization, the data contract style they generate is too complex and it is difficult to interact with other platforms. There is still a defect in using a data table or dataset in a service contract, that is, it may expose internal data structures. At the same time, modifications to the database style will affect the client in the future. Although in the ApplicationProgramData Tables can be transmitted internally, but it is not a good idea to send data tables across applications or public service boundaries. Generally, it is better to expose data operations rather than data itself ."
The best practice is to convert the datatable to the array type. The datatablehelper class is provided in the book to help convert datatable to an array type.
Generic
Unfortunately, we cannot define generics in data contracts. However, WCF uses a compromise method so that we can use generics on the server as usual. However, when a data contract is defined, generics are replaced by specific types, the rename format is:
<Original Name> of <type parameter name>
WCF also supports using custom types as generic parameters. In addition, you can use the name attribute of the Data contract to specify different names for the exported data contract. For example, the following server data contract:
[Datacontract]
Class someclass
{...}
[Datacontract (name = "myclass")]
Class myclass <t>
{...}
[Operationcontract]
Void mymethod (myclass <someclass> OBJ );
The exported data contract is:
[Datacontract]
Class someclass
{...}
[Datacontract]
Class myclass
{...}
[Operationcontract]
Void mymethod (myclass OBJ );
Set
WCF supports generic sets and custom sets. However, unlike traditional. NET programming, WCF has many constraints on set operations. These constraints are clearly described in this book. This article will not go into details.