Chapter 1 of this book mainly describes the knowledge about data contracts. "From an abstract perspective, WCF can host CLR types (interfaces and classes) and expose them as services. It can also use services using local CLR interfaces and classes. The operation of the WCF Service receives and returns the CLR types such as int and string. the WCF client transmits and processes the returned CLR types. However, CLR types are specific to. NET technologies. A core principle of service-oriented is that services cannot expose their implementation technologies when crossing service boundaries. Therefore, no matter what technology the client uses, it can interact with the service. Obviously, this means that WCF cannot expose CLR data types when crossing service boundaries. We need to find a way to convert the CLR data type to a standard platform-independent representation. Such representation is an XML-based style or information set (infoset ). In addition, the Service requires a formal method to declare the conversion between the two. This method is the subject of this chapter-data contract. The first part of this chapter describes how to enable type transfer and convert a data contract, and how to use the level of the Infrastructure processing class and the version control of the data contract. The second part describes how to use different. Net types, such as enumeration, delegation, data tables, and collections as data contracts ."
Data contracts are data transmitted between services. Since cross-process or cross-machine transmission is supported, WCF must perform special processing on data; otherwise, data transmission cannot be implemented. In. in net remoting and web services, data is usually processed using serialization methods. WCF also follows this approach, but in order to better reflect the service-oriented characteristics, datacontract is introduced in particular ). In addition, WCF introduces messagecontract, which is not described in this book.
The serialization mechanism supported by the. NET platform is used for WCF serialization, so it is not repeated here.
Although the serialization mechanism provided by. NET is sufficient to meet the requirements of SOA, there are still many shortcomings. This book summarizes the shortcomings of serializable:
The meaning of "serializable" is that all Members of the type are serializable and these members are part of the data style of the type. However, a better way is to provide an opt-in approach. Only those members explicitly included in the contract should be included in the data contract. The serializable feature requires that the data type be serializable so that the type can be used as a parameter for contract operations, but it cannot implement the service features of the type (it has the ability to become a WCF operation parameter) separation of duties from serialization capability. The serializalbe feature does not support aliases of type names and member names, and cannot map a new type to a predefined data contract. The serializable feature allows you to directly operate on member fields, so that the attribute encapsulated for field access is virtually empty. The best way to access fields is to add their values through properties, while serializable breaks the encapsulation of properties. Finally, the serializable feature does not directly support versioning, but the version control information is expected by the formatter. Undoubtedly, it makes version control difficult ."
The data contract datacontract provided by WCF basically solves the above problems. Generally, datacontract must be used with datamember. Only attributes that apply the datamember feature are published to metadata. Although the datamember feature can be applied to object fields, it is not recommended for WCF because it is the same as the design principle of the class.
Data contracts are similar to service contracts. There is no causal relationship between the access limits of data members or data contracts and WCF. Data contracts can completely contain private data members and other internal types:
[Datacontract]
Struct contact
{
[Datamember]
String m_firstname;
[Datamember]
String m_lastname;
}
Even if the datamember feature is directly applied to fields, the imported client definition is still represented by attributes. The following data contract definitions:
[Datacontract]
Struct contact
{
[Datamember]
Public String firstname;
[Datamember]
Public String lastname;
}
The imported client is defined:
[Datacontract]
Public partial struct contact
{
String firstnamefield;
String lastnamefield;
[Datamember]
Public String firstname
{
Get
{
Return firstnamefield;
}
Set
{
Firstnamefield = value;
}
}
[Datamember]
Public String lastname
{
Get
{
Return lastnamefield;
}
Set
{
Lastnamefield = value;
}
}
}
It uses the field name as the attribute name, while the imported definition adds the field suffix after the attribute name as the field name. However, you can also manually modify the definition of the client.
If the data member of the Data contract is private, the imported client definition is automatically changed to public. "When the datamember feature is applied to an attribute (whether it is a service or a client), this attribute must have get and set accessors. If no, an invaliddatacontractexception exception is thrown during the call. When an attribute itself is a data member, WCF uses this attribute during serialization and deserialization, enabling developers to apply the custom logic to the attribute ."
"Do not apply the datamember feature to both attributes and corresponding fields, which leads to repeated imported member definitions ."
If the data on the server is marked as serializable, datacontract is used when such a definition is imported. Moreover, "Every serializable member, whether public or private, is a data member ."
The traditional formatter Cannot serialize but mark the type of the datacontract feature. To serialize such a type, you must apply both the datacontract and serializable features. For the transmission representation (wire representation) generated for this type, it is as if only the datacontract feature is applied. At the same time, we still need to add the datamember feature to the members.
In the data contract of WCF, it is obvious that WCF cannot fully support the object-oriented design concept. In Chapter 2nd's description of the service contract, the inheritance level processing method of the contract already reflects the clue of this defect. For data contracts, such defects are further exposed.
First, WCF does not support the liskov replacement principle (LSP). "By default, we cannot use the subclass of the Data contract to replace the base class ." Consider the following service contract:
[Servicecontract]
Interface icontactmanager
{
// Cannot accept customer object here:
[Operationcontract]
Void addcontact (contact );
// Cannot return customer objects here:
[Operationcontract]
Contact [] getcontacts ();
}
Assume that the client defines a customer class at the same time:
[Datacontract]
Class Customer: Contact
{
[Datamember]
Public int ordernumber;
}
BelowCodeIt can be compiled successfully, but it will fail during runtime:
Contact contact = new customer ();
Contact. firstname = "Juval ";
Contact. lastname = "Lowy ";
Contactmanagerclient proxy = new contactmanagerclient ();
// Service call will fail:
Proxy. addcontact (contact );
Proxy. Close ();
In this example, we pass a customer object instead of a contact object. Because the service cannot identify the customer object, it cannot deserialize the contact object it receives.
Although WCF introduces known types (known types) to solve this problem, for designers who understand object-oriented ideas, such a design will undoubtedly introduce the coupling between parent classes and child classes. Because when designing the parent class, we must know the definition of the child class in advance. When we need to extend the subclass, we also need to modify the definition of the parent class.
The services introduced by WCF are of known types. Compared with known types, these types are improved to some extent. Because it can reduce the Coupling Degree between parent class and Child class at the level to the method level. However, such coupling is still unacceptable. For example:
[Datacontract]
Class contact
{...}
[Datacontract]
Class Customer: Contact
{...}
[Servicecontract]
Interface icontactmanager
{
[Operationcontract]
[Serviceknowntype (typeof (customer)]
Void addcontact (contact );
[Operationcontract]
Contact [] getcontacts ();
}
Of course, the known types of services can also be applied to the contract interface. At this time, the contract and all the operations contained in the services that implement the contract can receive the known sub-classes.
To solve this problem, WCF provides a method to configure known types. For example:
<System. runtime. serialization>
<Datacontractserializer>
<Declaredtypes>
<Add type = "contact, host, version = 1.0.0.0, culture = neutral,
Publickeytoken = NULL ">
<Knowntype type = "customer, myclasslibrary, version = 1.0.0.0,
Culture = neutral, publickeytoken = NULL "/>
</Add>
</Declaredtypes>
</Datacontractserializer>
</System. runtime. serialization>
Note that in the preceding configuration file, the known type of our configuration must be type fullname. Including the namespace, version number, and culture. Although this method can avoid code modification, re-compilation, and re-deployment when adding subclass, it will undoubtedly increase the burden on developers, especially for configuration file management and later maintenance.
However, "If the known type isProgramSet is an internal (internal) type. To add a known type, you only need to declare it using the configuration file ."
In short, it is not optimal to realize object-oriented polymorphism in WCF. If you can apply the knowntype feature to a subclass and name the parent class it inherits, it is more conducive to class extension. Unfortunately, WCF fails to do this.
If the data contract itself implements an interface, the situation becomes interesting. From the definition of the server, such a data contract can still specify the sub-data contract type that implements the data contract interface on the service contract through the known service types. For example, the data contract contact class implements the interface icontact:
Interface icontact
{
String firstname
{Get; set ;}
String lastname
{Get; set ;}
}
[Datacontract]
Class contact: icontact
{...}
In the service contract that processes the data contract contact, if the contract operation needs to be abstracted to define the icontact type parameter, it must use the serviceknowntype attribute to name its implementation class contact, as shown below:
[Servicecontract]
[Serviceknowntype (typeof (contact)]
Interface icontactmanager
{
[Operationcontract]
Void addcontact (icontact contact );
[Operationcontract]
Icontact [] getcontacts ();
}
Note: you cannot use the knowntype feature to apply it directly to the icontact interface, because the exported metadata cannot contain the interface itself.
The definition of the server is undoubtedly in line with the interface-Oriented Programming philosophy. In addition to adding serviceknowntype, the entire design is still elegant. However, the service contract exported according to this definition is not satisfactory, as shown below:
[Servicecontract]
Public interface icontactmanager
{
[Operationcontract]
[Serviceknowntype (typeof (contact)]
[Serviceknowntype (typeof (object [])]
Void addcontact (object contact );
[Operationcontract]
[Serviceknowntype (typeof (contact)]
[Serviceknowntype (typeof (object [])]
Object [] getcontacts ();
}
In the export definition, the serviceknowntype feature applied to the contract is applied to each operation, and a specific data contract subclass and an object [] type are specified for each operation. Note that in the return values and parameters of the operation, all the original icontact types are converted to the object type. The reason is that the client does not define the icontact interface. Object-based contractual definitions undoubtedly do not provide type security.
The solution is to add the icontact interface definition to the client. In this way, the client definition can be changed:
[Servicecontract]
Public interface icontactmanager
{
[Operationcontract]
[Serviceknowntype (typeof (contact)]
Void addcontact (icontact contact );
[Operationcontract]
[Serviceknowntype (typeof (contact)]
Icontact [] getcontacts ();
}
However, we cannot replace the original object type with the actual data contract type contact. Because it is replaced with a specific data contract type, the client service contract is incompatible with the server service contract. Therefore, the following definition is incorrect:
[Servicecontract]
Public interface icontactmanager
{
[Operationcontract]
Void addcontact (contact );
[Operationcontract]
Contact [] getcontacts ();
}