我们继续,上一回我们了解了数据协定的一部分内容,今天我们接着来做实验。好的,实验之前先说一句:实验有风险,写代码须谨慎。
实验开始!现在,我们定义两个带数据协定的类——Student和AddrInfo。
[DataContract] public class Student { [DataMember] public string Name; [DataMember] public string Phone; [DataMember] public AddrInfo Address; }
[DataContract] public class AddrInfo { [DataMember] public string Province; [DataMember] public string City; [DataMember] public string DetailAddr; }
这两个类有一个特征,Student类的Address字段是一个AddrInfo对象,而我们的服务定义如下:
[ServiceContract(Namespace = "MyNamespace")] public interface IService { [OperationContract] Student GetStudentInfo(); }
public class MyService : IService { public Student GetStudentInfo() { Student stu = new Student(); AddrInfo info = new AddrInfo(); info.Province = "广东省"; info.City = "佛山市"; info.DetailAddr = "火星路-300号"; stu.Name = "小陈"; stu.Phone = "1388888888"; stu.Address = info; return stu; } }
方法返回的Student对象,那么,我们来测试一下,AddrInfo能不能被成功序列化和反序列化。下面是注册和启动服务的代码:
static void Main(string[] args) { // 服务器基址 Uri baseAddress = new Uri("http://localhost:1378/services"); // 声明服务器主机 using (ServiceHost host = new ServiceHost(typeof(MyService), baseAddress)) { // 添加绑定和终结点 WSHttpBinding binding = new WSHttpBinding(); host.AddServiceEndpoint(typeof(IService), binding, "/test"); // 添加服务描述 host.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true }); try { // 打开服务 host.Open(); Console.WriteLine("服务已启动。"); } catch (Exception ex) { Console.WriteLine(ex.Message); } Console.ReadKey(); } }
在客户端生成的代码中,两个类都可以正确生成。
客户端测试代码如下:
static void Main(string[] args) { WS.ServiceClient cli = new WS.ServiceClient(); WS.Student stu = cli.GetStudentInfo(); string msg = "学生姓名:{0} 联系电话:{1} " + "地址信息:----------- " + "省份:{2} " + "市区:{3} " + "详细地址:{4}"; Console.WriteLine(msg, stu.Name, stu.Phone, stu.Address.Province, stu.Address.City, stu.Address.DetailAddr); Console.ReadKey(); }
其运行结果如下:
下面我们继续实验。
每个学生可能有多个学科的成绩,因此,我们为学生类再添加一个成绩属性。
[DataContract] public class Student { [DataMember] public string Name; [DataMember] public string Phone; [DataMember] public AddrInfo Address; [DataMember] public object Scores; }
public class MyService : IService { public Student GetStudentInfo() { Student stu = new Student(); AddrInfo info = new AddrInfo(); info.Province = "广东省"; info.City = "佛山市"; info.DetailAddr = "火星路-300号"; stu.Name = "小陈"; stu.Phone = "1388888888"; stu.Address = info; Dictionary<string, float> m_scores = new Dictionary<string, float>(); m_scores.Add("语文", 97f); m_scores.Add("英语", 64.5f); m_scores.Add("数学", 38f); m_scores.Add("历史", 77.6f); m_scores.Add("地理", 82.3f); stu.Scores = m_scores; return stu; } }
客户端测试代码改为:
static void Main(string[] args) { WS.ServiceClient cli = new WS.ServiceClient(); WS.Student stu = cli.GetStudentInfo(); string msg = "学生姓名:{0} 联系电话:{1} " + "地址信息:----------- " + "省份:{2} " + "市区:{3} " + "详细地址:{4}"; Console.WriteLine(msg, stu.Name, stu.Phone, stu.Address.Province, stu.Address.City, stu.Address.DetailAddr); Console.WriteLine("---------------------------------------"); Console.WriteLine("学生成绩单:"); Dictionary<string, float> scores = stu.Scores as Dictionary<string, float>; foreach (var item in scores) { Console.Write("{0}:{1} ", item.Key, item.Value); } Console.ReadKey(); }
现在来测试,就会发生异常,因为我们为Student类加的成绩属性是object类型,而我们在协定方法中给它赋的是 Dictionary<string, float>类型,这样一来,就算可以序列化也不能被反序列化,因为Dictionary<string, float>无法识别。
现在,我们再为Student类添加一个KnownType特性,看它能不能识别。
[DataContract]
[KnownType(typeof(Dictionary<string, float>))]
public class Student
{
。。。。。
}
这时候,再运行一下。
很可惜,还是报错了,
怎么办呢?不要放弃,我们继续把学生类修改一下。
[DataContract] [KnownType("GetKnowTypes")] public class Student { [DataMember] public string Name; [DataMember] public string Phone; [DataMember] public AddrInfo Address; [DataMember] public object Scores; static Type[] GetKnowTypes() { return new Type[] { typeof(Dictionary<string,float>) }; } }
GetKnowTypes方法是静态方法,KnownType的构造函数中传递该方法的名字。看看这回调用能否成功?
坚持就是胜利,看到了吧,这回OK了!高兴吧。
这里我们可以总结一下,在一些比较复杂的类型无法反序列化(不能识别类型)的时候,就得考虑使用KnownTypeAttribute来标注可能涉及到的外部类型,但如果遇到像泛型这些较为复杂的类型,就要考虑在带数据协定的类中添加一个静态方法,该方法返回Type 的IEnumerable,一般是Type[]就可以了,而在KnownTypeAttribute的构造函数中使用这个方法的名字。