Loading

WCF初探-18:WCF数据协定之KnownType

KnownTypeAttribute 类概述

 

  • 在数据到达接收终结点时,WCF 运行库尝试将数据反序列化为公共语言运行库 (CLR) 类型的实例。通过首先检查传入消息选择为反序列化而实例化的类型,以确定消息内容遵循的数据协定。然后反序列化引擎尝试查找实现与消息内容兼容的数据协定的 CLR 类型。反序列化引擎在此过程中允许的侯选类型集称为反序列化程序的“已知类型”集。
  • 让反序列化引擎了解某个类型的一种方法是使用 KnownTypeAttribute。不能将属性应用于单个数据成员,只能将它应用于整个数据协定类型。将属性应用于可能为类或结构的“外部类型”。在其最基本的用法中,应用属性会将类型指定为“已知类型”。只要反序列化外部类型的对象或通过其成员引用的任何对象,这就会导致已知类型成为已知类型集的一部分。可以将多个 KnownTypeAttribute 属性应用于同一类型。

 

以下情形需要使用KnownTypeAttribute修饰数据协定

 

  • 已发送的数据协定源自预期的数据协定。在该情况下,传输的数据没有与接收终结点所预期相同的数据协定。
  • 要传输的信息的声明类型是接口,而非类、结构或枚举。因此,无法预先知道实际发送了实现接口的哪个类型,接收终结点就无法预先确定已传输数据的数据协定。
  • 要传输的信息的声明类型是 Object。由于每个类型都继承自 Object,而且无法预先知道实际发送了哪个类型,因此接收终结点无法预先确定已传输数据的数据协定。这是第一个项的特殊情况:每个数据协定都源自为 Object 生成的默认空数据协定。
  • 某些类型(包括 .NET Framework 类型)具有上述三种类别之一中的成员。例如,Hashtable 使用 Object 在哈希表中存储实际对象。在序列化这些类型时,接收方无法预先确定这些成员的数据协定。

 

数据协定使用KnownType示例

 

  • 解决方案如下:

  

  • 工程结构说明:
  1. Service:类库程序,WCF服务端程序。在服务协定接口IUserInfo.cs中定义数据协定类Person,再定义一个数据协定类User。User派生至Person,继承基类Person的构造方法,定义新的属性成员SayHello。定义操作协定GetInfo和GetInfoEx,两者返回类型都为Person。在UserInfo.cs中实现数据协定,在GetInfo中,我们返回Person对象类型,在GetInfoEx中我们返回派生类User类型。由于在GetInfoEx中我们需要传递派生类User类型,所以要在基类数据协定Person上面加上KnownType(typeof(User))特性标记,这样User就能够客户端进行反序列化,供客户端使用。

         IUserInfo.cs代码如下: 

using System.ServiceModel;
using System.Runtime.Serialization;
using System;


namespace Service
{
[ServiceContract]
public interface IUserInfo
{
[OperationContract]
Person GetInfo(int id,string name);


[OperationContract]
Person GetInfoEx(int id, string name);
}


[DataContract]
[KnownType(typeof(User))]
public class Person
{
[DataMember]
public int ID { get; set; }


[DataMember]
public string Name { get; set; }


public Person(int id, string name)
{
this.ID = id;
this.Name = name;
}
}


[DataContract]
public class User:Person
{
public User(int id, string name): base(id,name){}


[DataMember]
public string SayHello
{
get { return "Hello:" + Name; }
set { throw new NotImplementedException(); }
}
}

}

  UserInfo.cs代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Service
{
public class UserInfo:IUserInfo
{
public Person GetInfo(int id, string name)
{
return new Person(id, name);
}

public Person GetInfoEx(int id, string name)
{
return new User(id, name);
}
}
}

 

  2.  Host:控制台应用程序,服务承载程序。添加对程序集Service的引用,完成以下代码,寄宿服务。Program.cs代码如下:

  
using System;
using System.ServiceModel;
using Service;

namespace Host
{
    class Program
    {
        static void Main(string[] args)
        {
            using (ServiceHost host = new ServiceHost(typeof(UserInfo)))
            {
                host.Opened += delegate { Console.WriteLine("服务已经启动,按任意键终止!"); };
                host.Open();
                Console.Read();
            }
        }
    }
}
View Code

  App.config代码如下:

  
<?xml version="1.0"?>
<configuration>
    <system.serviceModel>
        
        <services>
            <service name="Service.UserInfo" behaviorConfiguration="mexBehavior">
                <host>
                    <baseAddresses>
                        <add baseAddress="http://localhost:1234/UserInfo/"/>
                    </baseAddresses>
                </host>
                <endpoint address="" binding="wsHttpBinding" contract="Service.IUserInfo" />
                <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
            </service>
        </services>

        <behaviors>
            <serviceBehaviors>
                <behavior name="mexBehavior">
                    <serviceMetadata httpGetEnabled="true"/>
                    <serviceDebug includeExceptionDetailInFaults="true"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
    </system.serviceModel>
</configuration>
View Code

  我们通过svcutil.exe工具生成客户端代理类和客户端的配置文件

  svcutil.exe是一个命令行工具,位于路径C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin下,我们可以通过命令行运行该工具生成客户端代理类

  • 在运行中输入cmd打开命令行,输入 cd C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin
  • 输入svcutil.exe /out:f:\UserInfoClient.cs /config:f:\App.config http://localhost:1234/UserInfo

 

  3.  Client:控制台应用程序,客户端调用程序。将生成的UserInfoClient.cs和App.config复制到Client的工程目录下,完成客户端调用代码。Program.cs的代码如下:


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Service;


namespace Client
{
class Program
{
static void Main(string[] args)
{
try
{
UserInfoClient proxy = new UserInfoClient();
Person P1 = proxy.GetInfo(1,"JACK");
Person P2 = proxy.GetInfoEx(2, "TOM");
Console.WriteLine("{0,-10}{1,-20}", "ID", "Message");


Console.WriteLine("{0,-10}{1,-20}", P1.ID, P1.Name);
if (P2 is User)
{
Console.WriteLine("{0,-10}{1,-20}", ((User)P2).ID,((User)P2).SayHello);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}


Console.Read();
}
}
}

 

  4.  程序运行效果如下:

  

 

总结:

 

  • 从上面的示例可以看出,如果我们需要使用的数据契约类派生出了子类,并且在操作协定中使用了子类数据契约类型和基类数据契约类型,那我们必须为子类和基类成员添加必要的DataContract和DataMember修饰,而且还需要在基类数据契约上加上KnownType来告知客户端需要反序列化子类的成员。
posted @ 2015-04-22 10:39  wangweimutou  阅读(1493)  评论(0编辑  收藏  举报