关于数据契约(DataContract)待续

前两天做程序时建一个类来要实例化一些数据的处理时遇到的一个问题,

人懒了一下,就复制了以前的一部分代码,由于漏掉了DataContract的服务契约,程序执行时报错。

虽然找到错在哪,但对于DataContract不太懂,就在网上查了一下。

介绍的版本比较多暂时找了三个版本都复制过来,表示能力确实不咋滴,就等我在研究透彻点在写读书笔记(两天时间内会修改总结)。

容我偷懒两天

这是“在路上”微博的版本:

WCF第一要素就是契约:

服务契约用于声明可用于远程访问的类型。在Interfaceclass开始处使用服务契约标签.

[ServiceContract]

Public interface Iservice

{

 

}

接口调用契约的好处:

1.       同一服务类型可以实现多个不相干的服务契约.

2.       有利于版本升级

3.       按照接口隔离原则,让开发人员可以随时修改服务契约.

服务契约的属性与作用:

Name/Namespace定义该服务契约的自定义名称和命名空间,它会反映到WSDL及客户端的导出类中

ConfigurationName设置信息在配置文件中的名称。默认情况下为类的全名(本例为“WCFDemo.IService”)。

SessionMode服务契约的会话方式,允许的值有AllowedNotAllowedRequired。默认为Allowed值。

CallbackContract设置双工通信时(Duplex)的回调类型。

ProtectionLevel指定消息保护模式,可以对通信的消息进行加密及签名。

使用参数时,例如要指定自定义的Name Namespace,可以编写如下的代码。

[ServiceContract(Name=”MyService”,Namespace=”http://microsoft.com/wcf/demo”)]

Interface IService{}

定义为服务契约的接口或类的方法可以被声明为OperationContract(操作契约),只有声明为操 作契约的方法才可以被远程调用

[ServiceContract]

Public interface Iservice

{

       [OperationContract]

       String SayHello(string name);

}

 

错误契约(FaultContract)

被标识为FaultContract的方法必须同时已经被声明为OperationContract,否则就没有什么意义。声明一个方法为 FaultContract并指定了响应的类型参数以后,当调用这个方法时产生错误时,就会有一个对应SOAP格式的错误消息返回给调用端

[DataContract]

public class UserFault //自定义错误类

{

        [DataMember]

        public string Message { get; set; }

        [DataMember]

        public int UserId { get; set; }

        public UserFault(int userId, string msg)

        {

            this.UserId = userId;

            this.Message = msg;

        }

}

  [ServiceContract]

  public interface IUserService

  {

        [OperationContract]

        [FaultContract(typeof(UserFault))]

        UserInfo GetUser(int id);

  }

  public UserInfo GetUser(int id)

  {

     try{

            UserInfo info = new UserInfo();

            info.Age = 16;

            info.UserName = "陈翔";

            return info;

        }

        catch (Exception e)

        {

            throw new FaultException<UserFault>(new UserFault(id, e.Message));

        }

  }

 

数据契约(DataContract)

[DataContract]

       public partial class Userinfo

       {    

              private string _xsid = String.Empty;

             

              /// <summary>

              /// 学生编号

              /// </summary>

              [DataMember(Name="xsid")]

              public string Xsid

              {

                     get { return _xsid; }

                     set { _xsid = value; }

              }

       }

服务契约定义了远程访问对象和可供调用的方法,数据契约则是服务端和客户端之间要传送的自定义数据类型。

一个类如果声明了DataContract类型,说明类是可以被传送的,且只有成员属性可以被传送.支持Name/Namespace属性[DataContract(Name=”Name”)]

每一个要传送的成员声明为DataMember类型,同样也可以包含Name,Namespace,IsRequired,Order,EmitDefaultvalue属性

需要传送SOAP消息时可以使用[MessageContract] eg:

[MessageContract]

       public partial class Userinfo

       {    

              private string _xsid = String.Empty;

              private string _njid = String.Empty;

              /// <summary>

              /// 学生编号

              /// </summary>

              [MessageBodyMember(

Name="xsid"

Namespace=”http://www.smodi.com”)]

              public string Xsid

              {

                     get { return _xsid; }

                     set { _xsid = value; }

              }

             

              /// <summary>

              /// 年级编号

              /// </summary>

              [MessageHeader(

Name="njid"

Namespace=”http://www.smodi.com”)]

              public string Njid

              {

                     get { return _njid; }

                     set { _njid = value; }

              }

       }

这个可以生成SOAP消息

<s:Envelope>

    <s:Header>

        <a:Action s:mustUnderstand="1">http:// Userinfo /Action</a:Action>

        <h:AuthKey s:mustUnderstand="1" xmlns:h="http://www.smodi.com">xxxx</h:AuthKey>

    </s:Header>

    <s:Body>

        <UserMessage xmlns="Microsoft.WCF.Documentation">

             <User xmlns="http://www.smodi.com">abcd</User>

       </UserMessage>

    </s:Body>   

</s:Envelope>

 

 

 

这个是子夜微博的版本:

服务契约定义了远程访问对象和可供调用的方法,数据契约则是服务端和客户端之间要传送的自定义数据类型。

一旦声明一个类型为DataContract,那么该类型就可以被序列化在服务端和客户端之间传送,如下所示。

      [DataContract]

     public class UserInfo

     {

          //….

}

只有声明为DataContract的类型的对象可以被传送,且只有成员属性会被传递,成员方法不会被传递。WCF对声明为DataContract的类型提供更加细节的控制,可以把一个成员排除在序列化范围以外,也就是说,客户端程序不会获得被排除在外的成员的任何信息,包括定义和数据。默认情况下,所有的成员属性都被排除在外,因此需要把每一个要传送的成员声明为DataMember,如下所示。

    [DataContract]

    public class UserInfo

    {

        [DataMember]

        public string UserName

        {

            get;

            set;

        }

        [DataMember]

        public int Age

        {

            get;

            set;

        }

        [DataMember]

        public string Location

        {

            get;

            set;

        }

        public string Zodiac

        {

            get;

            set;

        }

}

上面这段代码把UserInfo类声明为DataContract,将UserName、Age、Location这3个属性声明为DataMember(数据成员)。Zodiac成员没有被声明为DataMember,因此在交换数据时,不会传输Zodiac的任何信息。

DataContract也支持Name/Namespace属性,如同ServiceContract,Name和Namespace可以自定义名称和命名空间,客户端将使用自定义的名称和命名空间对DataContract类型进行访问。

声明为DataMember的成员也可以自定义客户端可见的名称,例如:

[DataMember(Name="Name")]

public string UserName

{

     get;

     set;

}

[DataMember(Name="Age")]

public int UserAge

{

          get;

          set;

}

除了Name和Namespace以外,DataMember还有以下参数,它们的含义分别如下。

(1)IsRequired:值为true时,要求序列化引擎检查对象是否存在该值;若无,则会有异常抛出。

(2)Order:bool类型值,值为true时,序列化和反序列化过程将会按成员定义的顺序进行,这对依赖于成员位置的反序列化过程无比重要。

(3)EmitDefaultvalue:为成员属性设置一个默认值。

一般情况下,将类型声明为DataContract就可以满足传送的需求了,不过特殊情况是难以避免的,这时就需要对要传送的SOAP消息进行更加精确的控制,MessageContract可以满足这种需求。

把一个类型声明为MessageContract,意味着它可以被序列化为SOAP消息,可以声明类型的成员为SOAP消息的各个部分,如Header、Body等,如下所示。

    [MessageContract]

    public class UserMessage

    {

        private string user = String.Empty;

        private string authKey = String.Empty;

        [MessageBodyMember(

          Name = "UserName",

          Namespace = "http://www.wcf.com")]

        public string User

        {

            get { return user; }

            set { user = value; }

        }

        [MessageHeader(

          Name = "AuthKey",

          Namespace = "http://www.wcf.com",

          MustUnderstand = true

        )]

        public string AuthKey

        {

            get { return authKey; }

            set { this.authKey = value; }

        }

}

User成员被声明为MessageBody(消息体)的一个成员,AuthKey被声明为消息头(MessageHeader)的一个成员。这个类将可以生成如下的SOAP消息。

<s:Envelope>

    <s:Header>

        <a:Action s:mustUnderstand="1">http://UserMessage/Action</a:Action>

        <h:AuthKey s:mustUnderstand="1" xmlns:h="http://www.wcf.com">xxxx</h:AuthKey>

    </s:Header>

    <s:Body>

        <UserMessage xmlns="Microsoft.WCF.Documentation">

             <User xmlns="http://www.wcf.com">abcd</User>

       </UserMessage>

    </s:Body>   

</s:Envelope>

 

MessageHeader中,MustUnderstand参数表示读取该头的程序必须能够识别头的内容,否则不能继续处理。Name/Namespace的作用与前面的元素相同。另有Relay参数,若为true,头的内容被接收到以后会在响应消息中回发给消息发送端。

 

 

下面是51CTO 的版本:

WCF Data Contract之集合类型

在.Net中将实现了IEnumerable接口的所有类型(包括数组和泛型)都称之为集合类型。把其中实现了IDictionary接口或泛型IDictionary接口的集合类型称为字典集合,剩下的其他集合类型为列表集合。

1、集合的数据契约(协定)缺省名称      

缺省情况下,WCF框架对集合类型是内建支持的,也就说你不需要应用任何属性,就可以将集合应用在数据契约(协定)中,但前提是集合中的元素必须是应用了DataContractAttribute属性或者是可序列化的类型。这时,数据契约(协定)名称和命名空间就依赖集合中包含的元素的类型的名称和命名空间了,它们不受集合类型本身的名称和命名空间的影响。

缺省集合类型数据契约(协定)的格式是(不包括“+”):

列表集合:
名称:ArrayOf+集合中包含的元素类型
循环元素名称:集合中包含的元素类型

字典集合:
    名称:ArrayOfKeyValueOf+集合中Key的类型+集合中包含的对象类型
循环元素名称:KeyValueOf+集合中Key的类型+集合中包含的对象类型

例如:

MyCollection1 : IList{…}的数据契约名称就是:ArrayOfint

MyCollection2 : ICollection{…}的数据契约名称就是:ArrayOfint

MyDictionary1 : Dictionary<int, int="">{…}的数据契约名称就是:ArrayOfKeyValueOfintint

MyCollection3 : ArrayList{…}的数据契约名称就是:ArrayOfanyType

MyDictionary2 : Dictionary<int, object="">{…}的数据契约名称就是:ArrayOfKeyValueOfintanyType

注意:如果是object的话,使用的是anyType,因为在Schema中所有类型的基类是anyType。

如果集合是应用于某个数据契约类型中时,那么它的名称将是字段名称,如下面Customer的定义以及序列化后的表示:

 

[DataContract]
public class Customer
{
[DataMember]
public List<string> addresses = new List<string> {"Beijing","ShangHai" };
[DataMember]
public Dictionary<int, object> telephones = new Dictionary<int, object> {
{ 1, "010-82371234" },
{ 2, "021-56781234" } };
}

<Customer xmlns:i="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://schemas.datacontract.org/2004/07/WCFTestSerializer">
<addresses xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:string>Beijing</d2p1:string>
<d2p1:string>ShangHai</d2p1:string>
</addresses>
<telephones
xmlns:d2p1="http://schemas.microsoft.com/2003/10/Serialization/Arrays">
<d2p1:KeyValueOfintanyType>
<d2p1:Key>1</d2p1:Key>            

<d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema"

i:type="d4p1:string">010-82371234</d2p1:Value>

</d2p1:KeyValueOfintanyType>
<d2p1:KeyValueOfintanyType>
<d2p1:Key>2</d2p1:Key>

<d2p1:Value xmlns:d4p1="http://www.w3.org/2001/XMLSchema"

i:type="d4p1:string">021-56781234</d2p1:Value>

</d2p1:KeyValueOfintanyType>
</telephones>
</Customer>

 

 

 

 

2、集合的契约等价

在WCF中应用了CollectionDataContractAttribute属性的集合称之为定制数据契约集合,否则为非定制数据契约集合。不管是非定制数据契约集合还是定制数据契约集合,只要它们的数据契约名称和循环元素的名称都相同(如果是字典集合其Key和Value也要是相同的),我们就说它们是等价的。由于非定制数据契约集合的数据契约以及循环元素的名称由集合中的类型决定,所以非定制数据契约集合的数据契约等价遵守以下几个规则(关于数据契约等价的其他详细信息请参见我以前的文章:WCF Data Contract之契约等价):

2.1、相同类型的列表集合被认为具有相同的数据契约(协定)例如:List和int[]是等价的。

2.2、具有相同相同键和值类型的所有字典集合也被视为具有相同的数据契约(协定)

2.3、接口和实现该接口的具体集合类的数据契约是等价的。例如IList和List等价。

2.4、非泛型集合与Object类型的泛型集合的数据契约等价。例如:List<Object>与ArrayList等价,ArrayList与Object[]也是等价的。

这就是为什么上面的示例中,实现IList和ICollection的集合类型的数据契约名称都是ArrayOfint的原因。例如下面两个数据契约是等价的:

 

[DataContract(Name="Customer")] public class Customer1
{
[DataMember] public string customerName;
[DataMember] public Collection<string> addresses;
[DataMember] public string[] telephones;
}
[DataContract(Name="Customer")] public class Customer2
{
[DataMember] public string customerName;
[DataMember] public ICollection<string> addresses;
[DataMember] public List<string> telephones;
}

 

注意:Collection和ICollection是等价的,同时string[]和List也是等价的。

所以,针对Customer2的定义,我们可以将实现ICollection接口的任何集合类的实例赋给addresses。同样,我们也可以利用在数据契约中定义集合接口的机制来将WCF不支持的集合类型(如:ReadOnlyCollection,具体见本文中WCF中对集合类型的要求限制一节)来应用到我们的WCF应用中。也就是说我们可以将ReadOnlyCollection的实例赋给Customer2的addresses。

 

3、集合与KnowType

集合类型和非集合类型在多态增加KnowType类型方面是不同的,关于非集合方面增加KnowType的详细情况请我以前的文章:WCF Data Contract之KnowType。集合类型在增加KnowType类型遵守以下规则:

3.1、集合类型是以多态方式来代替其他集合或集合接口的,您不需要将这样的集合类型添加到KnowType类型中。例如上例中Customer2类中的addresses,你可以将List的实例赋值给它,而不需要将List增加到KnowType类型列表中。

3.2、当您以多态方式使用集合来代替非集合类型时,则需要将它们添加到已知类型。 例如,如果您声明一个 Object 类型的数据成员并将其用于发送 ArrayList 的一个实例,则需要将 ArrayList 添加到已知类型中。

3.3、等价的集合只能应用KnowTypeAttribute属性来将其增加到KnowType列表中一次。例如:不能将ArrayList和Object[]都添加到相同类的KnowType列表中

4、定制集合的数据契约(协定)      

我们可以使用CollectionDataContractAttribute的下列属性来指定集合的数据契约的相关名称及命名空间:

4.1、Name属性来指定集合数据契约的名称(如果没有使用此属性,将使用集合类型的名称)

4.2、Namespace属性来指定其命名空间

4.3、ItemName 属性来指定循环元素的名称

4.4、针对字典集合还可以用KeyName和ValueName来指定键和值的名称

例如,我们将第一节的例子更改成如下所示:

 

[CollectionDataContract(Name = " telephones", ItemName = "telephone",

KeyName = "Index", ValueName = "Number")]

public class MyDictionary : Dictionary<int, object="">

{

public new Dictionary<int,object>.Enumerator GetEnumerator()

{

Dictionary<int, object=""> innerObject = new Dictionary<int, object=""> {

{ 1, "010-82371234" },

{ 2, "021-56781234" } };

return innerObject.GetEnumerator();

}

}

 

此类将被序列化成:

 

http://schemas.datacontract.org/2004/07/WCFTestSerializer">

 

1

010-82371234

 

 

2

021-56781234

 

对于定制数据契约的集合类型来说,前面所述的非定制数据契约的集合等价规则将失效。所以要尽量避免使用CollectionDataContractAttribute。

5、集合的反序列化

缺省情况下,使用Svcutil.exe生成客户端代理时,列表集合将反序列化成数组,字典集合将反序列化成Dictionary泛型。我们也可以通过/collectionType 命令行开关(简写形式是 /ct)来指定我们希望反序列化的集合类型(请记住,您还必须使用 /reference 开关(简写形式是 /r)指定引用的集合类型的程序集)。如果该类型是泛型,则必须在类型后面跟有反引号和泛型参数的数目。例如前面的例子中的Customer1类可以通过下面的命令在客户端使用List泛型:SvcUtil http://localhost:8000/ 。

 

/r:C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll
/ct:System.Collections.Generic.List`1

 

6、DataContractAttribute和CollectionContractAttribute      

对于集合而言,WCF框架将隐含地自动的为集合类型应用CollectionDataContractAttribute属性的,这就是为什么你不需要为集合应用任何属性就可以在数据契约中使用的原因。但要注意:

(1)如果我们新建的集合类型是继承已有的集合类型如List,那么我们就不能对新建的集合类型应用DataContractAttribute,否则运行时会抛出InvalidDataContractException,但你可以应用CollectionDataContractAttribute来定制集合类型的数据契约。例如[DataContract]public class MyList:List{…}的集合定义将抛出异常。

(2)如果我们新建的集合类型是实现了集合接口例如IList,IDictionary<int,int>的话,我们可以对此类型应用DataContractAttribute属性,这样的话此类型将作为普通的数据契约类型,而不是将其作为集合类型来处理。也就是WCF框架将只序列化其中应用了DataMemberAttribute属性的成员。当然你也可以不应用任何属性来让系统缺省作为集合类型来处理。(你也可以使用CollectionDataContractAttribute来定制数据契约)

(3)针对应用CollectionDataContractAttribute属性或者缺省不应用任何属性的集合类型,如果其内部有应用了DataMemberAttribute的属性或字段,在序列化时系统将忽略。

7、WCF中对集合类型的要求限制

不是所有的集合类型都可以在WCF中使用,只有满足以下要求才可以使用:

7.1、该集合类型有一个缺省的构造函数

7.2、该集合类型有一个名为Add的方法

这是因为在反序列化集合类型时,WCF框架首先调用该集合类型的无参数的构造函数,然后通过非静态的Add方法来将循环元素增加到集合中。所以以上限制主要是针对反序列化而设定的。

8、集合中的一些高级规则

8.1、WCF框架在序列化时支持集合的集合,也支持数组的数组(交错数组),但不支持多唯数组。

8.2、字节数组和 XmlNode 数组是特殊的数组类型,将被视为基元,而不是集合。 序列化字节数组会产生单个包含一个 Base64 编码数据块的 XML 元素,而不是为每个字节都生成一个单独的元素。(笔者认为这是为了性能的考虑才这么处理的。)

8.3、如果集合类型实现了IXMLSerializable接口,假设类型为MyType:IList,IXMLSerializable{…},WCF框架将根据在数据契约中声明的类型来进行序列化,如果声明的是集(接口)如IList,那么该类型将被认为是列表集合来序列化,如果声明的是IXMLSerializable,那么将按照IXMLSerializable来进行序列化,当然需要将该类型加到KnowType类型列表中。如果声明的是该类型本身(如MyType),那么将按照IXMLSerializable的规则来进行序列化。

8.4、在对集合进行序列化时,将调用集合类的GetEnumerator 方法来得到集合的内容,在反序列化时将首先调用该集合类型的无参数的构造函数,然后通过非静态的Add方法来将循环元素增加到集合中。(注:虽然这与大家在MSDN的帮助文档中看到的不同,认为字典集合将调用get_Keys和get_Values,以及IList将调用索引器,但笔者使用VS2008验证时没有得到以上方法被调用的结论,所以笔者认为是MSDN文档滞后或有误,如果各位看官能得到和MSDN吻合的结论麻烦告诉一声。)

8.5、如果集合类型同时应用了Serialized属性或实现了ISerializable接口,WCF框架将忽略它们;但是如果集合类型不满足集合类型要求(例如缺少Add)方法,那么将按照Serialized或ISerializable来处理;但如果你对该集合同时应用了CollectionDataContract属性而且又不满足集合要求,那么将抛出InvalidDataContractException,而不是按照Serialized或ISerializable来处理。

8.6、不能向实现了IXmlSerializable接口的类型使用CollectionDataContractAttribute属性,否则会抛出InvalidDataContractException.向非集合应用CollectionDataContractAttribute属性以及非字典集合指定KeyName或者ValueName属性也都将抛出此异常。

posted @ 2013-03-18 17:59  Elvirali  阅读(466)  评论(0编辑  收藏  举报