As I mentioned in the previous article, a small framework is being developed to implement unified management of Context information using unified APIs. This framework supports both Web and GUI applications and cross-thread transmission and cross-domain transfer (here, the client-to-server implicit transfer is implemented in the WCF Service call), and context Project (Cont
As I mentioned in the previous article, a small framework is being developed to implement unified management of Context information using unified APIs. This framework supports both Web and GUI applications and cross-thread transmission and cross-domain transfer (here, the client-to-server implicit transfer is implemented in the WCF Service call), and context Project (Cont
As I mentioned in the previous article, a small framework is being developed to implement unified management of Context information using unified APIs. This framework supports both Web and GUI applications and cross-thread and cross-origin transfer (here, the client-to-server implicit transfer is implemented in a WCF Service call), and context projects (ContextItem). The key lies in the support of the following two features, and there is a small problem about serialization. The solution only needs to change a short line of code, but the result has left me busy for a long time.
I. Problem Reproduction
To reproduce my actual problems, I specifically simplified the problem and wrote a simple example (you can download it from here ). In the following code snippet, I created a type named ContextItem to represent a context item to be maintained. Since it is required to implement automatic transfer in a WCF Service call, I define it as DataContract. ContextItem contains three attributes: Key, Value, and ReadOnly. Needless to say, ReadOnly indicates that the ContextItem can be modified. Note the definition of the Set Method of the Value Attribute -- If ReadOnly is used, an exception is thrown.
1: [DataContract(Namespace = "http://www.artech.com")]
2: public class ContextItem
3: {
4: private object value = null;
5: [DataMember]
6: public string Key { get; private set; }
7: [DataMember]
8: public object Value
9: {
10: get
11: {
12: return this.value;
13: }
14: set
15: {
16: if (this.ReadOnly)
17: {
18: throw new InvalidOperationException("Cannot change the value of readonly context item.");
19: }
20: this.value = value;
21: }
22: }
23: [DataMember]
24: public bool ReadOnly { get; set; }
25: public ContextItem(string key, object value)
26: {
27: if (string.IsNullOrEmpty(key))
28: {
29: throw new ArgumentNullException("key");
30: }
31: this.Key = key;
32: this.Value = value;
33: }
34: }
To demonstrate serialization and deserialization, I wrote the following two static help methods. Serialize and Deserialize are used for serialization and deserialization respectively. The former serializes objects into XML and saves them to the specified files. The latter reads XML from the files and deserializes them into the corresponding objects.
1: public static T Deserialize
(string fileName)
2: {
3: DataContractSerializer serializer = new DataContractSerializer(typeof(T));
4: using (XmlReader reader = new XmlTextReader(fileName))
5: {
6: return (T)serializer.ReadObject(reader);
7: }
8: }
9:
10: public static void Serialize
(T instance, string fileName)
11: {
12: DataContractSerializer serializer = new DataContractSerializer(typeof(T));
13: using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8))
14: {
15: serializer.WriteObject(writer, instance);
16: }
17: Process.Start(fileName);
18: }
Our program is very simple. In the following code snippet, we first create a ContextItem object, and then set the ReadOnly attribute to true. Call the Serialize method to Serialize the object to XML and save it in a file named context. xml. Then call the Deserialize method to read the file for deserialization.
1: static void Main(string[] args)
2: {
3: var contextItem1 = new ContextItem("__userId", "Foo");
4: contextItem1.ReadOnly = true;
5: Serialize
(contextItem1, "context.xml");
6: var contextItem2 = Deserialize
("context.xml");
7: }
The serialization operation can be executed normally, but an InvalidOperationException occurs when the program executes Deserialize.
Ii. Problem Analysis
From the above, we can easily see that exceptions are thrown when the Value attribute of the ContextItem object is assigned a Value. If you have some knowledge about the serialization/deserialization rules of the DataContractSerializer serializer, you should be aware of the data member (DataMember) of the data contract (DataContract) based on the Property ), the sequencer initializes the sequencer by calling the Set method during deserialization. In this example, because ReadOnly is True, the Set method will be called when deserializing the Value. However, read-only ContextItem cannot assign values to it, so an exception is thrown.
So how can we solve this problem? My initial idea was to set the ReadOnly attribute to False during serialization, and then add another attribute to save the actual value. No exception occurs because ReadOnly is false during reverse sequence. After deserialization, assign the initial value of ReadOnly. Although the above solution can solve the problem, adding a useful attribute to ContextItem only in the serialization and deserialization process is ugly.
Let's try another way: Exceptions occur when ReadOnly is not True when the Value attribute is serialized. So how can we avoid this situation? If the Value attribute is serialized before the ReadOnly attribute, the initial Value of ReadOnly is False. Will this problem be solved? This is our first solution.
Iii. solution 1: control the attribute deserialization order
So what if attributes are deserialized before they are controlled? This is to understand the serialization and serialization rules of the DataContractSerializer serializer. By default, DataContractSerializer is serialized in the order of data member names. This can be seen from the generated XML structure. The order of XML elements determines the deserialization order.
1:
2:
__userId
3:
true
4:
Foo
5:
In the above example, the ReadOnly of ContextItem is placed before the Value and will be serialized first. So, do we want to update the data members of Value or ReadOnly (DataMember, not the attribute name? This is definitely not the solution we want. In the SOA world, DataMember is part of a contract and often cannot be changed.
If the attribute Value is serialized before ReadOnly without changing the data member name, another deserialization rule of DataContractSerializer is required: the Order attribute of DataMemberAttribute can be used to control the position of serialized attributes in the XML element list.
For this reason, we have an answer. We only need to make a slight change to ContextItem. In the following code, when DataMemberAttribute is applied for the Value and ReadOnly attributes, the Order attributes are set to 1 and 2 respectively, so that the ContextItem object can be serialized, the XML elements corresponding to the Value and ReadOnly attributes will always be divided. It is also worth noting that in the Set Method of the Value attribute, the ReadOnly field is used to determine whether the data is read-only. This is very important. Calling the ReadOnly attribute will force this attribute to be deserialized.
1: [DataContract(Namespace = "http://www.artech.com")]
2: public class ContextItem
3: {
4: private object value = null;
5: private bool readOnly;
6: [DataMember]
7: public string Key { get; private set; }
8:
9: [DataMember(Order = 1)]
10: public object Value
11: {
12: get
13: {
14: return this.value;
15: }
16: set
17: {
18: if (this.readOnly)
19: {
20: throw new InvalidOperationException("Cannot change the value of readonly context item.");
21: }
22: this.value = value;
23: }
24: }
25: [DataMember(Order =2)]
26: public bool ReadOnly
27: {
28: get
29: {
30: return readOnly;
31: }
32: set
33: {
34: readOnly = value;
35: }
36: }
37: //Others
38: }
If you are interested, you can try it yourself. If we make the above changes, the previous program will run normally. Here, some readers may ask, aren't you saying there are only one line of code changes? I think there are more than one line of the above changes. No error. We can make fewer changes to solve the problem.
4. solution 2: Define data members on fields rather than attributes
Another way of thinking is that the exception is caused by calling the Set Method of the Value Attribute during deserialization. If this method is not called during deserialization, isn't it possible? So how can we avoid calling the Set Method of the Value Attribute? The method is simple, that is, to define data members on fields rather than attributes. Attribute-based data members have to call the Set Method to initialize data items during deserialization, field-based data members only need to be directly copied during deserialization.
Based on this idea, we made a simple change to the original ContextItem-moving the DataMemberAttribute feature from the Value attribute to the value field. Note that in order to comply with the original Schema, you need to set the Name attribute of the DataMemberAttribute feature to "Value ".
1: [DataContract(Namespace = "http://www.artech.com")]
2: public class ContextItem
3: {
4: [DataMember]
5: public string Key { get; private set; }
6:
7: [DataMember(Name = "Value")]
8: private object value = null;
9: public object Value
10: {
11: get
12: {
13: return this.value;
14: }
15: set
16: {
17: if (this.ReadOnly)
18: {
19: throw new InvalidOperationException("Cannot change the value of readonly context item.");
20: }
21: this.value = value;
22: }
23: }
24: [DataMember]
25: public bool ReadOnly { get; set; }
26: //Others
27: }
28: }
Summary
Although this is only a small problem, the solution seems so simple. However, this does not mean that this is a problem that can be ignored, and the serialization rules of DataMemberAttribute serialization are hidden behind it.
Author: Artech
Source: http://artech.cnblogs.com