本来今天打算描述一下数据契约的序列化,毕竟只是单纯的说数据契约的作用也没有太大意义,但是我发现如果单纯的叙述wcf的序列胡DataSerializer 很困难,因为它采用的事xml序列化,所以今天打乱了我的计划,来介绍一下.Net中的xml序列化,毕竟我们在使用序列化器的时候,很多时候生成的都是xml。
契约是交互双方或多方就某个问题达成的一共共识,而信息交换式wcf通信的唯一手段,也是跨平台的关键,所以契约的最根本目的不是定义什么操作方式,而是对消息的结构进行规范、统一,只有通信双方对消息的结构达成了一致,通信才可能进行。
wcf默认的数据交换方式就是xml,虽然说数据在wcf中有CLR对象和Xml两种,但是CLR对象时强类型语言才会有的,而xml才是可以和其他系统通信的手段。CLR对象要传输到远程客户端,需要序列化,默认的使用xml传输,那么就会使用到Xml序列化,今天我们一起来了解一下,.Net中如何把数据序列化成xml的,或者说其中有什么规律存在,因为打乱了我的计划,所以说这篇博客可能会显得有一点乱,没有条理,因为我是想到哪儿,写到哪儿,各位莫怪。
其中有一点,wcf虽然默认的事采用xml序列化,但是不是说不可以采用其他格式序列化,json也可以正常传输,并且可能是我们的wcf服务和手机客户端通信的首选。
在.Net中,.Net framework 就对基于XMl的序列化提供了原生的支持,比如传统的web服务就是通过XmlSerializer进行序列化和反序列化,xmlSerializer也可以应用到wcf中,因为wcf也是作为.Net framework的一个组件提供的。
我们来看一下默认的xml生成规则。这个东西说不是特别好说,还是通过一个简单的示例让我们一起来了解它,有码有真相。项目还是采用我们原来的项目,只是我们这次在控制台运行。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Xml; 6 using System.Xml.Serialization; 7 using System.Diagnostics; 8 9 namespace Chinaer.WcfDemo.ConsoleClient 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 Person person = new Person(25, Guid.NewGuid()) 16 { 17 Date = DateTime.Now 18 }; 19 Serialize<Person>(person, "person.xml"); 20 Console.Read(); 21 } 22 23 /// <summary> 24 /// 序列化方法 25 /// </summary> 26 /// <typeparam name="T"></typeparam> 27 /// <param name="instace"></param> 28 /// <param name="fileName"></param> 29 public static void Serialize<T>(T instace, string fileName) 30 { 31 using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8)) 32 { 33 XmlSerializer serializer = new XmlSerializer(typeof(T)); 34 serializer.Serialize(writer, instace); 35 } 36 Process.Start(fileName); 37 } 38 } 39 /// <summary> 40 /// 定义一个实体类 Person 41 /// </summary> 42 public class Person 43 { 44 //注意我们没有默认的构造函数 45 private double Age { get; set; } //私有字段 年龄 46 47 public Guid ID { get; set; } //公有的随机数 48 49 public DateTime Date { get; set; } 50 51 public Person(double age, Guid id) 52 { 53 this.Age = age; 54 this.ID = id; 55 } 56 } 57 58 59 60 61 62 63 }
请注意,如果你直接运行上面的代码会出现异常信息。
为什么我没有添加默认的空的无参数的构造函数,就会出现异常呢?因为在反序列化的时候需要调用它。在反序列化的时候会调用无参数的空构造函数来生成目标对象,然后填充数值。
下面我们添加空的无参数的构造函数。
1 <?xml version="1.0" encoding="utf-8"?> 2 <Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 3 <ID>1d988f1f-3fe4-463b-84ce-7f771449280d</ID> 4 <Date>2013-03-21T22:19:34.7687866+08:00</Date> 5 </Person>
通过生成的xml文件,对应我们定义在类Person中的属性我们可以得到如下几点有用的信息:
- xml根节点的名称为对象类型的名称,或者说是类名
- 对象属性或字段以xmlElement xml元素的形式输出,名称和他们的名称一致
- 只有public类型的成员才会被序列化,我们在xml文件中没有看到Age,因为它是private 标识的,其他如internal的也不会被序列化
- xml元素出现的顺序和他们在类中存在的顺序是一致的。
下面我们再来思考一个问题,如果属性是只读的或者是只写的,那会出现什么情况呢?程序员的性格就是一切以程序运行为准,那么我们就来测试一下结果吧。请再次注意,这次我做的修改,我更改属性为只读或者只写,并且我还添加了两个属性,但是我在实例化对象的时候,并没有为它赋值,或者我们可以为UserName赋值。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Xml; 6 using System.Xml.Serialization; 7 using System.Diagnostics; 8 9 namespace Chinaer.WcfDemo.ConsoleClient 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 Person person = new Person(25, Guid.NewGuid()) 16 { 17 Date = DateTime.Now 18 }; 19 20 person.UserName = "guozhiqi"; 21 //person.UserPwd = "123"; 22 Serialize<Person>(person, "person.xml"); 23 Console.Read(); 24 } 25 26 /// <summary> 27 /// 序列化方法 28 /// </summary> 29 /// <typeparam name="T"></typeparam> 30 /// <param name="instace"></param> 31 /// <param name="fileName"></param> 32 public static void Serialize<T>(T instace, string fileName) 33 { 34 using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8)) 35 { 36 XmlSerializer serializer = new XmlSerializer(typeof(T)); 37 serializer.Serialize(writer, instace); 38 } 39 Process.Start(fileName); 40 } 41 } 42 /// <summary> 43 /// 定义一个实体类 Person 44 /// </summary> 45 public class Person 46 { 47 private Guid _id; 48 49 private DateTime _date; 50 //注意我们没有默认的构造函数 51 internal double Age { get; set; } //私有字段 年龄 52 53 public Guid ID { get { return _id; } } //公有的随机数 54 55 public DateTime Date { set { _date = value; } } 56 57 public string UserName { get; set; } 58 59 public string UserPwd { get; set; } 60 public Person() { } 61 public Person(double age, Guid id) 62 { 63 this.Age = age; 64 65 } 66 } 67 68 69 70 71 72 73 }
请再次注意我做的更改,添加了两个属性UserName和UserPwd,并且只为UserName赋值,而没有为UserPwd赋值,还有我更改ID为只读,date为只写属性,下面我们来看生成的xml。
<?xml version="1.0" encoding="utf-8"?> <Person xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <UserName>guozhiqi</UserName> </Person>
请再次对比我们生成的xml文件和类之间的关系,由此我们再次总结几点:
- 只读或只写的属性不会被序列化,只有可读写的属性可以序列化。当然字段不存在是否只读或只写,所以是可序列化的,它只受到public等标示符的影响。
- 如果在实例化对象时,不会对象的属性赋值,那么它是不会序列化的。这就告诉我们,如果再传递参数的时候,如果想传递一个空,千万不要认为不为对象赋值就可以,我们必须制定一个值,它才会序列化。
既然说到了访问修饰符、只读或只写,但是我印象中只读或只写我们可以定义,举例来说,如果我们定义只读属性,那么我们设置set为private也应该可以实现只读功能。那么我们就来尝试一下这个是否可以序列化。
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Xml; 6 using System.Xml.Serialization; 7 using System.Diagnostics; 8 9 namespace Chinaer.WcfDemo.ConsoleClient 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 Person person = new Person(25, Guid.NewGuid()) 16 { 17 Date = DateTime.Now 18 }; 19 20 person.UserName = "guozhiqi"; 21 //person.UserPwd = "123"; 22 Serialize<Person>(person, "person.xml"); 23 Console.Read(); 24 } 25 26 /// <summary> 27 /// 序列化方法 28 /// </summary> 29 /// <typeparam name="T"></typeparam> 30 /// <param name="instace"></param> 31 /// <param name="fileName"></param> 32 public static void Serialize<T>(T instace, string fileName) 33 { 34 using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8)) 35 { 36 XmlSerializer serializer = new XmlSerializer(typeof(T)); 37 serializer.Serialize(writer, instace); 38 } 39 Process.Start(fileName); 40 } 41 } 42 /// <summary> 43 /// 定义一个实体类 Person 44 /// </summary> 45 public class Person 46 { 47 private Guid _id; 48 49 private DateTime _date; 50 //注意我们没有默认的构造函数 51 internal double Age { get; set; } //私有字段 年龄 52 53 public Guid ID { get; private set; } //公有的随机数 54 55 public DateTime Date { set; private get; } 56 57 public string UserName { get; set; } 58 59 public string UserPwd { get; set; } 60 public Person() { } 61 public Person(double age, Guid id) 62 { 63 this.Age = age; 64 65 } 66 } 67 68 }
这一次做的修改很小,只是简单的把自动属性的set设置为了private set或者get设置为了private get;这也是实现了只读或只写功能。那么我们运行程序是否可以得到我们想要的结果呢?
不可思议的一幕出现了,只写属性get不写不会被序列化,但是如果加上private get 就会出现异常?这是什么原因呢?我们尝试解决办法,毕竟这个异常信息提供的错误信息太少。
打开异常详细信息,我们看到了一个有用的信息,未将对象引用到对象的示例,可以说这是我调试程序过程中见得最多,但是最难解决的难题。
既然有了目标,那肯定是更改的属性出了问题,我们使用的是自动属性,如果我们更改为普通的字段是否可以呢?试试吧
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Xml; 6 using System.Xml.Serialization; 7 using System.Diagnostics; 8 9 namespace Chinaer.WcfDemo.ConsoleClient 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 Person person = new Person(25, Guid.NewGuid()) 16 { 17 Date = DateTime.Now 18 }; 19 20 person.UserName = "guozhiqi"; 21 //person.UserPwd = "123"; 22 Serialize<Person>(person, "person.xml"); 23 Console.Read(); 24 } 25 26 /// <summary> 27 /// 序列化方法 28 /// </summary> 29 /// <typeparam name="T"></typeparam> 30 /// <param name="instace"></param> 31 /// <param name="fileName"></param> 32 public static void Serialize<T>(T instace, string fileName) 33 { 34 using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8)) 35 { 36 XmlSerializer serializer = new XmlSerializer(typeof(T)); 37 serializer.Serialize(writer, instace); 38 } 39 Process.Start(fileName); 40 } 41 } 42 /// <summary> 43 /// 定义一个实体类 Person 44 /// </summary> 45 public class Person 46 { 47 private Guid _id; 48 49 private DateTime _date; 50 //注意我们没有默认的构造函数 51 internal double Age { get; set; } //私有字段 年龄 52 53 public Guid ID 54 { 55 get { return _id; } 56 private set 57 { 58 _id = value; 59 } 60 } //公有的随机数 61 62 public DateTime Date 63 { 64 private set 65 { 66 _date = value; 67 } 68 get 69 { 70 return _date; 71 } 72 } 73 74 public string UserName { get; set; } 75 76 public string UserPwd { get; set; } 77 public Person() { } 78 public Person(double age, Guid id) 79 { 80 this.Age = age; 81 82 } 83 } 84 85 }
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Xml; 6 using System.Xml.Serialization; 7 using System.Diagnostics; 8 9 namespace Chinaer.WcfDemo.ConsoleClient 10 { 11 class Program 12 { 13 static void Main(string[] args) 14 { 15 Person person = new Person(25, Guid.NewGuid()) 16 { 17 Date = DateTime.Now 18 }; 19 20 person.UserName = "guozhiqi"; 21 //person.UserPwd = "123"; 22 Serialize<Person>(person, "person.xml"); 23 Console.Read(); 24 } 25 26 /// <summary> 27 /// 序列化方法 28 /// </summary> 29 /// <typeparam name="T"></typeparam> 30 /// <param name="instace"></param> 31 /// <param name="fileName"></param> 32 public static void Serialize<T>(T instace, string fileName) 33 { 34 using (XmlWriter writer = new XmlTextWriter(fileName, Encoding.UTF8)) 35 { 36 XmlSerializer serializer = new XmlSerializer(typeof(T)); 37 serializer.Serialize(writer, instace); 38 } 39 Process.Start(fileName); 40 } 41 } 42 /// <summary> 43 /// 定义一个实体类 Person 44 /// </summary> 45 public class Person 46 { 47 private Guid _id; 48 49 private DateTime _date; 50 //注意我们没有默认的构造函数 51 internal double Age { get; set; } //私有字段 年龄 52 53 public Guid ID 54 { 55 get { return _id; } 56 private set 57 { 58 _id = value; 59 } 60 } //公有的随机数 61 62 public DateTime Date 63 { 64 private set 65 { 66 _date = value; 67 } 68 get 69 { 70 return _date; 71 } 72 } 73 74 public string UserName { get; set; } 75 76 public string UserPwd { get; set; } 77 public Person() { } 78 public Person(double age, Guid id) 79 { 80 this.Age = age; 81 82 } 83 } 84 85 }
请注意我这次的修改,只是添加了私有字段,让属性使用,但是我没有更改任何地方,但是运行起来的结果仍然让我很无奈,直呼程序员 真是伤不起啊。
遇到了和刚才一样的异常信息,无奈啊,现在我只是看到那个应用在属性上的private最可疑,我们试着去掉它,那么就和我们上面做的只读或只写的一样的,肯定是正确的,我也测试了,确实正确,不再赘述了。
好了,这次就写这么多,我再写下一篇,控制xml的序列化,因为博客园每隔三个小时才可以发布到博客园首页,所以我先提供一下,记得有时间浏览一下奥。
总结一下,xml的序列化没有什么重点,这篇博客重点就是发现了.Net序列化成xml的过程中我们可能遇到的几个原则。虽然知识点不大,但是对我们以后理解wcf的序列化器也是很有帮助的。
下一篇wcf 基础教程 契约 Contract 控制xml输出 数据契约DataContract序列化前身 XmlSerializer xml序列化