WCF足迹2:契约(1)
广告投放★自助友情CMS落伍广告联盟晒乐广告联盟脉动广告联盟品味广告联盟
广告位可自定样式联系QQ:4285248个文字广告月20元广告联系QQ:428524广告位可自定样式
8个文字广告月20元黄金广告位每月20元广告位可自定样式联系QQ:428524广告位可自定样式
左旋肉碱、全国包邮
买二送一、无效退款

文章浏览→编程相关.Net编程→WCF足迹2:契约(1)

WCF足迹2:契约(1)
WCF足迹2:契约(1)

契约是WCF中很重要的概念。它是用一种与平台无关的标准语法来描述WCF服务的功能。当客户端获取服务端WCF服务的时候,会根据服务端声明的契约生成客户端契约的复本,客户端和服务端通过契约来实现沟通。

在WCF中包括了四种契约:服务契约,数据契约,错误契约和消息契约。在这里我们重点来看服务契约和数据契约。
在WCF中契约是以Attribute型式进行声明的。

1.用来定义服务契约的两个Attribute:
[AttributeUsage(AttributeTargets.Interface|AttributeTargets.Class,Inherited= false)]
public sealed class ServiceContractAttribute :Attribute
{
   public stringName
   {get;set;}
   public stringNamespace
   {get;set;}
   //More members
}

[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationContractAttribute :Attribute
{
   public stringName
   {get;set;}
   //More members
}

2.用来定义数据契约的两个Attribute:
[AttributeUsage(AttributeTargets.Enum |AttributeTargets.Struct |AttributeTargets.Class, Inherited =false)]
public sealed class DataContractAttribute :Attribute
{
   public stringName
   {get;set;}
   public stringNamespace
   {get;set;}
}

[AttributeUsage(AttributeTargets.Field|AttributeTargets.Property,Inherited = false)]
public sealed class DataMemberAttribute :Attribute
{
   public boolIsRequired
   {get;set;}
   public stringName
   {get;set;}
   public intOrder
   {get;set;}
}

一、服务契约:

1.服务契约的概念
我们可以在接口或者类上声明[ServiceContract]和[OperationContract]来定义服务契约。
[ServiceContract]
interface IMyContract
{
  [OperationContract]
   stringMyMethod(string text);

  //MyOtherMethod方法没有声明[OperationContract],不会成为契约的一部份
  string MyOtherMethod(string text);
}
class MyService :IMyContract
{
   public stringMyMethod(string text)
   {
     return "Hello " + text;
   }
   public stringMyOtherMethod(string text)
   {
     return "Cannot call this method over WCF";
   }
}

ServiceContract声明用来把.NET中的接口声明(CLR格式)映射为与平台无关的契约声明(XML格式),以向外界暴露服务访问入口ServiceContract声明与类的访问修饰符无关,即不管接口(类)的访问修饰符是public/private/protected/internal,只要把该接口(类)声明为ServiceContract,该接口(类)总会变成服务契约暴露给客户端。因为访问修饰符(public/private/protected/internal)定义的是在CLR中的访问边界,而ServiceContract定义的是在WCF中的访问边界。
在WCF中服务契约接口都需要显示声明为ServiceContract,否则,接口不会被当成WCF契约向外界暴露。
即使我们把接口声明为ServiceContract了,但该服务契约现在并不包含任何成员,我们还要在需要作为契约成员的方法上面加上OperationContractAttribute声明。像上面的代码中,MyMethod方法会作为IMyContract契约的成员向外界暴露,而MyOtherMethod方法则不会成为IMyContract契约的成员。
OperationContract 可以应用在成员方法、属性、索引器和事件上面

2.ServiceContract的NameSpace属性和Name属性
在编写WCF服务的时候,我们应当为每个服务契约设置NameSpace属性,如果为服务契约指定NameSpace属性的话,那该服务契约会默认NameSpace="http://tempuri.org"。这里NameSpace的作用与原来CLR中NameSpace的作用一样,都是为了定义一个命名空间,防止命名的冲突
如:
[ServiceContract(Namespace ="http://hi.baidu.com/grayworm")]
interface IMyContract
{...}
对Internet发布的服务契约,命名空间一般使用公司的网址进行命名,对于局域网内发布的服务契约则没有必要按照这种方式进行命名,我们可以使用更有意义单词作为NameSpace。
[ServiceContract(Namespace ="MyNamespace")]
interface IMyContract
{...}


我们还可以为服务契约指定别名。在默认的情况下,服务契约的名称与接口的名称一样,我们可以在ServiceContract声明中使用Name属性为服务契约指定别名。
[ServiceContract(Namespace="http://hi.baidu.com/grayworm",Name="GrayWormCaculate")]
public interface ICaculator
{
   [OperationContract(Name="AddInt")]
   int Add(int arg1, int arg2);
   [OperationContract(Name="AddDouble")]
   double Add(double arg1, double arg2);
}
测试结果:

WCF足迹2:契约 - Tony - Go ahead!
《图2》
从图中我们可以看出服务的名子不再是接口的名子了。

3.服务契约中的方法重载
在面向对象的思想中,我们有方法重载的概念,所谓的方法重载就是指一个类中如果两个方法的方法名相同而方法参数不同,那这两个参数就形成了重载
如:
interface ICalculator
{
   int Add(int arg1,intarg2);
   double Add(double arg1,doublearg2);
}

CLR可以根据方法的能数来区分这两个方法。而在WCF世界中这种方法名相同而参数不同的形式则会引发InvalidOperationException异常,即在WCF中不支持面向对象中的方法重载。
如:
//这是种契约定义是错误的
[ServiceContract]
interface ICalculator
{
   [OperationContract]
   int Add(int arg1,intarg2);

  [OperationContract]
   double Add(double arg1,doublearg2);
}
上面这个服务契约编译的时候是没有问题的,因为它符合CLR的重载要求,但当我们使用HOST发布服务的时候会产生下面的问题:

WCF足迹2:契约 - Tony - Go ahead!
《图1》

下面我们看一下如何解决ClR和WSDL中不统一的情况:
我们可以在OperationContract声明上通过Name属性为方法起别名,将来客户端就会通过这个别名来区分不同方法的。如:
[ServiceContract]
public interface ICaculator
{
   [OperationContract(Name="AddInt")]
    int Add(intarg1, int arg2);
   [OperationContract(Name="AddDouble")]
   double Add(double arg1, double arg2);
}
这样在客户端会把两个Add方法区分为AddInt和AddDouble两个方法。测试结果如下图:

WCF足迹2:契约 - Tony - Go ahead!
《图3》

4.服务契约的继承
下面我们看一下契约的继承。
我先声明一个简单计算器的契约ISimpleCaculator,只能够作加法运算:
[ServiceContract]
public interfaceISimpleCaculator
{
   [OperationContract]
   int Add(int arg1, int arg2);
}

我们再声明一个科学计算器的契约IScientificCaculator,派生自ISimpleCaculator,在简单计算器的功能之上还能够做乘法运算。
[ServiceContract]
public interfaceIScientificCaculator:ISimpleCaculator
{
   [OperationContract]
   int Mutiply(int arg1, int arg2);
}

然后我们再编写一个类实现IScientificCaculator接口。
public class MyCaculator :IScientificCaculator
{
    public intAdd(int arg1, int arg2)
    {
       return arg1 + arg2;
    }
    public intMutiply(int arg1, int arg2)
    {
       return arg1 * arg2;
    }
}

服务契约编写完成后,我们再在宿主程序中配置终结点:
<service name ="MyCalculator">
   <endpoint
     address = "
http://localhost:8001/MyCalculator/"
     binding = "basicHttpBinding"
     contract = "IScientificCalculator"
  />
</service>

这样我们就把WCF服务和宿主程序编写好了,下一步就在客户端添加WCF服务的引用。当添加完对WCF服务的引用后,客户端就会通过元数据终结点获取服务契约的信息,并在客户端生成代理代。
代理类的契约声明代码如下:
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel","3.0.0.0")]
[System.ServiceModel.ServiceContractAttribute(ConfigurationName="SR.IScientificCaculator")]
public interface IScientificCaculator {
   
   [System.ServiceModel.OperationContractAttribute(Action="
http://tempuri.org/ISimpleCaculator/Add",ReplyAction="http://tempuri.org/ISimpleCaculator/AddResponse")]
    intAdd(int arg1, intarg2);
   
   [System.ServiceModel.OperationContractAttribute(Action="
http://tempuri.org/IScientificCaculator/Mutiply",ReplyAction="http://tempuri.org/IScientificCaculator/MutiplyResponse")]
    intMutiply(int arg1, intarg2);
}
从上面的代码中我们看出,虽然在服务端我们编写了两个有继承关系的契约,但在客户端并没有为我们生成两个对应的契约,而是只生成了一个服务契约在这个服务契约中包含了两个OperationContract。
这两个OperationContract分别与服务端两个ServiceContract中的OperationContract相对应。由于这两个OperationContract来源于不同的服务契约,所以在OperationContract的属性中有Action="http://tempuri.org/ISimpleCaculator/Add"和Action="http://tempuri.org/IScientificCaculator/Mutiply"两个属性声明,这个Action属性就是映射该OperationContract服务端的OperationContract。

二、数据契约
1.数据契约的概念。
我们在进行WCF编程的时候,服务端程序难免会与客户端程序之间发生数据交换,由于服务端与客户端可能是两种异质运行环境,这就需要实现服务端的数据类型与客户端代理类数据类型的统一。
在服务器端与客户端交换数据是通过流来实现的,因此在传递对象的时候需要我们把对象转换到流中去,在目的地我们再从流中把数据读取出来重新生成能相应对象,这个思想就是我们序列化的思想。在DotNET序列化中是通过Serialization声明来标识类允许被实例化的,这种序列化只是把数据序列化到流中去,而在WCF中不仅仅要把数据序列化到流中去还应包含数据类型的描述。因为客户端的程序可能与服务器端的程序不样而无法实现数据准确的序列化和反序列化,比如服务器端我是用WCF开发的,而客户端是用JavaEE开发的,现在需要从服务器端返回一个Dog对象给客户端。如果只使用简单的序列化和反序列化的话,可能会产生问题:服务器端DotNET序列化的数据在客户端JavaEE不能识别流的格式,无法实现返序列化。
数据契约的作用就是实现一种与平台无关的序列化,即在序列化过程中实现在schema与CLR类型之间转换。
许多内置类型都默认可以被序列化,但自定义类型我们就需要使用数据契约来显式指明其可被序列化。

数据契约使用DataContract和DataMember来声明。
DataContract:修饰可被序列化的数据类型。
DataMember:修饰可被序列化的成员,可以修饰成员变量也可以修饰属性。

[DataContract]
struct Contact
{
  [DataMember]
   public stringFirstName;

  [DataMember]
   public string LastName;
}
或者
[DataContract]
struct Contact
{
   string m_FirstName;
   string m_LastName;

  [DataMember]
  public string FirstName
   {
     get
     {...}
     set
     {...}
    }

  [DataMember]
  public string LastName
   {
     get
     {...}
     set
     {...}
    }
}

与服务契约一样,使用DataContract和DataMember声明的数据契约也与访问修饰符(public,private,protected...)无关。

2.数据契约的传递
a.命名空间
服务器端定义了一个数据契约,客户端在生成代理类的时候也会生成一个对等的数据契约的复本,但这个契约的复本和服务器端的契约还是有稍许的不同,但命名空间默认是一样的。
如:
服务器端的数据契约定义
namespace MyNamespace
{
  [DataContract]
   struct Contact
   {...}

  [ServiceContract]
  interface IContactManager
   {
     [OperationContract]
     void AddContact(Contact contact);

     [OperationContract]
     Contact[] GetContacts( );
   }
}

传递到客户端的数据契约复本为:
namespace MyNamespace
{
  [DataContract]
  struct Contact
   {...}
}
[ServiceContract]
interface IContactManager
{
  [OperationContract]
   voidAddContact(Contact contact);

  [OperationContract]
  Contact[] GetContacts( );
}

如果要想改变传递到客户端的数据契约的命名空间,我们可以在服务器端数据契约声明的时候加上NameSpace属性
namespace MyNamespace
{
  [DataContract(Namespace ="MyOtherNamespace")]
  struct Contact
   {...}
}

这样传递到客户端的数据契约就会变为
namespace MyOtherNamespace
{
  [DataContract]
  struct Contact
   {...}
}

b.DataMember声明的使用
DataMember声明可以加在成员变量上,也可以加在属性上。不管怎样使用DataMember声明,总会在客户端代理类中生成带有DataMember声明的相关属性。

服务端DataContract成员变量加上DataMember声明的时候,客户端代理类会产生对应的DataMember声明的属性。客户端代理类DataMember属性的名子与服务端DataMember成员变量名子相同,并在客户端动态生成对应的成员变量,成员变量命名是在DataMember属性名子的后面加Feild的形式。

如服务端数据契约的声明如下:
[DataContract]
public class Book
{
  [DataMember]
   public string BookNO;
  [DataMember]
   public string BookName;
  [DataMember]
   public decimal BookPrice;
}

客户端生成代理类中数据契约的声明如下:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization","3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="Book",Namespace="
http://schemas.datacontract.org/2004/07/Services")]
[System.SerializableAttribute()]
public partial class Book :object, System.Runtime.Serialization.IExtensibleDataObject,System.ComponentModel.INotifyPropertyChanged {
   
   [System.Runtime.Serialization.OptionalFieldAttribute()]
    privatestring BookNOField;
   
   [System.Runtime.Serialization.OptionalFieldAttribute()]
    privatestring BookNameField;
   
   [System.Runtime.Serialization.OptionalFieldAttribute()]
    privatedecimal BookPriceField;
   
  [System.Runtime.Serialization.DataMemberAttribute()]
   public string BookNO {
       get {...}
       set {...}
       }
    }
   
  [System.Runtime.Serialization.DataMemberAttribute()]
   public string BookName {
       get {...}
       set {...}
       }
    }
   
  [System.Runtime.Serialization.DataMemberAttribute()]
   public decimal BookPrice {
       get {...}
       set {...}
       }
    }
   //其它的属性和方法  
}

当服务端DataContract属性上加上DataMember声明的时候,客户端代理类会产生对应的DataMember声明的属性。客户端代理类DataMember属性的名子与服务端DataMember属性名子相同,并在客户端动态生成对应的成员变量,成员变量命名是在属性名子的后面加Feild的形式。

如服务端数据契约的声明如下:
[DataContract]
publicclass Book
{
    privatestring _BookNO;
  [DataMember]
   public string BookNO
    {
       get { return _BookNO; }
       set { _BookNO = value; }
    }
    privatestring _BookName;
  [DataMember]
   public string BookName
    {
       get { return _BookName; }
       set { _BookName = value; }
    }
    privatedecimal _BookPrice;
  [DataMember]
   public decimal BookPrice
    {
       get { return _BookPrice; }
       set { _BookPrice = value; }
    }
}

客户端生成代理类中数据契约的声明如下:
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Runtime.Serialization","3.0.0.0")]
[System.Runtime.Serialization.DataContractAttribute(Name="Book",Namespace="
http://schemas.datacontract.org/2004/07/Services")]
[System.SerializableAttribute()]
public partial class Book : object,System.Runtime.Serialization.IExtensibleDataObject,System.ComponentModel.INotifyPropertyChanged {
   
   [System.Runtime.Serialization.OptionalFieldAttribute()]
    privatestring BookNOField;
   
   [System.Runtime.Serialization.OptionalFieldAttribute()]
    privatestring BookNameField;
   
   [System.Runtime.Serialization.OptionalFieldAttribute()]
    privatedecimal BookPriceField;
   
   [System.Runtime.Serialization.DataMemberAttribute()]
   public string BookNO {
       get {...}
       set {...}
       }
    }
   
  [System.Runtime.Serialization.DataMemberAttribute()]
   public string BookName {
       get {...}
       set {...}
       }
    }
   
   [System.Runtime.Serialization.DataMemberAttribute()]
   public decimal BookPrice {
       get {...}
       set {...}
       }
    }
   //其它的属性和方法  
}

凡是被DataMember声明修饰的属性,必须要有get和set访问器,如果没有这两个访问器在调用的时候会产生InvalidDataContractException异常信息,因为在序列化的过程中需要通过get和set访问器来操作对象的数据。
要注意的是不要在成员变量和对应属性上都加上DataMember声明,这样会在客户端代理类中产生重复的成员。

 

所属分类:编程相关.Net编程    作者:新浪博客    时间:2010-11-20 0:00:00

文章导航