WCF分布式开发步步为赢(7):WCF数据契约与序列化

时间:2021-03-20 15:30:26

本节继续学习WCF分布式开发步步为赢(7):WCF数据契约与序列化.数据契约是WCF应用程序开发中一个重要的概念,毫无疑问实现客户端与服务端数据契约的传递中序列化是非常重要的步骤。那么序列化是什么?为什么会有序列化机制?或者说它是为了解决什么问题?作用是什么?现有的.NET 序列化机制和WCF序列化机制有什么不同?我们在本节文章里都会详细介绍。本节结构:【0】数据契约【1】序列化基本概念【2】.NET 序列化机制【3】WCF序列化机制【4】代码实现与分析【5】总结。

下面我们正式进入今天的学习阶段,首先来介绍一下数据契约的概念:

【0】数据契约(DataContract):

在WCF服务编程中我们知道,服务契约定义了远程访问对象和可供调用的服务操作方法,数据契约则是定义服务端和客户端之间要传送的自定义数据类型。在WCF项目中,声明一个类型为DataContract,那么该类型就可以被序列化在服务端和客户端之间传送。类只有声明为DataContract,该类型的对象才可以被传送,且只有类的属性会被传送,需要在属性生命前加DataMember声明,这样该属性就可以被序列化传送。默认情况属性是不可传递的。类的方法不会被传送。WCF对定义的数据契约的类型可以进行更加细节的控制,可以把一个成员属性排除在序列化范围以外,客户端程序不会获得被排除在外的成员属性的任何信息,包括定义和数据。  代码如下:


 [DataContract]//数据契约属性声明
    class MyDataContract
    {
        [DataMember(Name = "MyName")]//数据成员标记,支持别名定义
        public string Name
        {
            get;
            set;
        }
        [DataMember(Name = "MyEmail")]//数据成员标记,支持别名定义
        public string Email
        {
            get;
            set;
        }
        [DataMember]//数据成员标记
        public string Mobile
        {
            get;
            set;
        }
        //没有[DataMember]声明,不会被序列化
        public string Address
        {
            get;
            set;
        }
    }
}

上面类声明为DataContract,部分属性声明为DataMember(数据成员)。可以序列化为客户端传送。Address成员属性没有被声明为DataMember,因此在交换数据时,不会传输Address的任何信息。声明为DataMember的成员也可以自定义客户端可见的别名 如:[DataMember(Name = "MyName")]//数据成员标记,支持别名定义。

【1】序列化基本概念:

知道数据契约的一些概念和特性之后,下面来介绍一下序列化的概念。

【1.1】为什么序列化:我们这里先来介绍一下为什么需要序列化。当然这个不是必须的。只是针对特定的开发平台的数据或者信息类型而言,当一个系统或者说平台需要和别的异构的系统或者平台交互的时候,两个系统需要一个特定的公开的可以公用的行业标准来支持这个数据信息的交互。这里目前来说支持这个数据交互传递的语言载体就是XML.

同样WCF作为面向服务的编程框架,它的目标或者特性之一就是实现服务的跨语言、平台,与不同的服务进行信息数据的交互,而不限制客户端的系统或者开发语言。要实现这个目标,WCF服务首先就是要面对信息的传递与共享问题。我们知道WCF服务和客户端可以传递如Int、String等.NET数据类型。但是如何实现用户自定义复杂类型的跨服务边界的传递,这是一个关键问题。数据契约可以发布为服务的元数据,允许客户端转化为本地语言表示。解决的办法就是封送(Marshaling),将对象封送到其它平台。基于WCF的客户端和服务端参数传递的过程如下图:

WCF分布式开发步步为赢(7):WCF数据契约与序列化

主要步骤:客户端序列化参数为XML信息集--传递->服务端反序列化为本地类型--执行结果->序列化结果为XML信息集--传递->客户端序反序列化返回信息为本地类型。

WCF分布式开发必备知识(2):.Net Remoting一节中也介绍了.Net Remoting的通信过程 ,两者也有流程也有部分相似之处。对象封送的概念其实.Net Remoting早有涉及,远程对象(RemoteOject),也就是我们远程要访问的对象.首先定义一个Class,继承MarshalByRefObject,可以使用在remoting应用中,支持对象的跨域边界访问。看过.Net Remoting这节文章应该还有点印象,不同之处是WCF的对象封送是为跨越服务边界,.Net Remoting的封送是为了跨越跨域边界。相关的概念请查阅WCF分布式开发必备知识(2):.Net Remoting或者MSDN,都能找到详细的介绍,这里不在详述。

【1.2】什么是序列化:

序列化是指将对象实例的状态存储到存储媒体的过程。在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流。在随后对对象进行反序列化时,将创建出与原对象完全相同的副本。

用户自定义类型要想在WCF服务端和客户端传递就必须声明为DataContract。这样就能实现用户自定义类型的序列化。序列化的目的就是把一种私有的或者某种平台下使用的数据类型转化为标准的可以公开交互的数据信息样式。这个过程就叫序列化。这个也是序列化的作用或者目的之所在。序列化是将对象状态转换为可保持或传输的格式的过程。与序列化相对的是反序列化,它将流转换为对象。这两个过程结合起来,可以轻松地存储和传输数据。序列化的目的:

 

  1、以某种存储形式使自定义对象持久化;

 

  2、将对象从一个地方传递到另一个地方。 序列化就是把本地消息或者数据的类型进行封送,转换为标准的可以跨平台、语言的信息集,为别的系统或者服务所用。

【2】.NET 序列化机制:

.net的序列化。.net是通过反射机制自动实现对象的序列化与反序列化。首先.net能够捕获对象每个字段的值,然后进行序列化,反序列化时,.net创建一个对应类型的新的对象,读取持久化的值,然后设置字段的值。.net对象状态的序列化到Stream中。.NET Framework 提供三种序列化技术:

 

  1.BinaryFormatter二进制序列化保持类型保真度,这对于在应用程序的不同调用之间保留对象的状态很有用。例如,通过将对象序列化到剪贴板,可在不同的应用程序之间共享对象。您可以将对象序列化到流、磁盘、内存和网络等等。远程处理使用序列化“通过值”在计算机或应用程序域之间传递对象。

2.调用System.Runtime.Serialization.Formatters.Soap空间下的SoapFormatter类进行序列化和反序列化,序列化之后的文件是Soap格式的文件(简单对象访问协议(Simple Object Access Protocol,SOAP),WCF分布式开发必备知识(4):Web Service也进行了介绍,SOAP是一种轻量的、简单的、基于XML的协议,它被设计成在WEB上交换结构化的和固化的信息。 SOAP 可以和现存的许多因特网协议和格式结合使用,包括超文本传输协议(HTTP),简单邮件传输协议(SMTP),多用途网际邮件扩充协议(MIME)。它还支持从消息系统到远程过程调用(RPC)等大量的应用程序。 SOAP使用基于XML的数据结构和超文本传输协议(HTTP)的组合定义了一个标准的方法来使用Internet上各种不同操作环境中的分布式对象。可以实现跨平台数据的交互。

 

  3.XML 序列化需要引用System.Xml.Serialization,使用XmlSerialize类进行序列化和反序列化操作。它仅序列化公共属性和字段,且不保持类型保真度。基于XML 标准,支持跨平台数据交互。

BinaryFormatter二进制序列化的优点:1. 所有的类成员(包括只读的)都可以被序列化;2. 性能高。

SoapFormatter、XmlSerialize的优点:1. 跨平台,互操作性好;2. 不依赖二进制;3. 可读性强。

当然.NET原有的序列化器也有自己的局限性,因为他们除了要序列化对象的状态信息外,还要将程序集的信息和版本信息持久化到流中,这样才能保证对象被反序列化为正确的对象类型副本。这要求客户端必须拥有使用.NET程序集。不能满足跨平台的WCF数据交互的需求。

【3】WCF序列化机制:

由于.NET格式化器固有的缺陷,WCF不得不提供自己的格式化,以满足其面向服务的需求。在WCF程序集里,提供了专门用户序列化和反序列化操作的类:DataContractSerializer,在System.Runtime.Serialization命名空间里。使用Reflector查看其定义信息如下:


public abstract class XmlObjectSerializer
{
    // Methods
    protected XmlObjectSerializer()
    {
    }
    internal static void CheckNull(object obj, string name)
    {
        if (obj == null)
        {
            throw new ArgumentNullException(name);
        }
    }
    internal virtual Type GetSerializeType(object graph)
    {
        if (graph != null)
        {
            return graph.GetType();
        }
        return null;
    }
    private static string GetTypeInfoError(int errorMessage, Type type, Exception innerException)
    {
        string str = (type == null) ? string.Empty : SR.GetString(1, new object[] { DataContract.GetClrTypeFullName(type) });
        string str2 = (innerException == null) ? string.Empty : innerException.Message;
        return SR.GetString(errorMessage, new object[] { str, str2 });
    }     public abstract bool IsStartObject(XmlDictionaryReader reader);
    public virtual bool IsStartObject(XmlReader reader)
    {
        CheckNull(reader, "reader");
        return this.IsStartObject(XmlDictionaryReader.CreateDictionaryReader(reader));
    }     public virtual object ReadObject(XmlDictionaryReader reader)
    {
        return this.ReadObject(reader, true);
    }
    public virtual object ReadObject(XmlReader reader)
    {
        CheckNull(reader, "reader");
        return this.ReadObject(XmlDictionaryReader.CreateDictionaryReader(reader));
    }
    public abstract object ReadObject(XmlDictionaryReader reader, bool verifyObjectName);
    public virtual object ReadObject(XmlReader reader, bool verifyObjectName)
    {
        CheckNull(reader, "reader");
        return this.ReadObject(XmlDictionaryReader.CreateDictionaryReader(reader), verifyObjectName);
    }     public abstract void WriteEndObject(XmlDictionaryWriter writer);
    public virtual void WriteEndObject(XmlWriter writer)
    {
        CheckNull(writer, "writer");
        this.WriteEndObject(XmlDictionaryWriter.CreateDictionaryWriter(writer));
    }
    public virtual void WriteObject(XmlDictionaryWriter writer, object graph)
    {
        CheckNull(writer, "writer");
        try
        {
            this.WriteStartObject(writer, graph);
            this.WriteObjectContent(writer, graph);
            this.WriteEndObject(writer);
        }
        catch (XmlException exception)
        {
            throw new SerializationException(GetTypeInfoError(0, this.GetSerializeType(graph), exception), exception);
        }
        catch (FormatException exception2)
        {
            throw new SerializationException(GetTypeInfoError(0, this.GetSerializeType(graph), exception2), exception2);
        }
    }
    public virtual void WriteObject(XmlWriter writer, object graph)
    {
        CheckNull(writer, "writer");
        this.WriteObject(XmlDictionaryWriter.CreateDictionaryWriter(writer), graph);
    }
    public abstract void WriteObjectContent(XmlDictionaryWriter writer, object graph);
    public virtual void WriteObjectContent(XmlWriter writer, object graph)
    {
        CheckNull(writer, "writer");
        this.WriteObjectContent(XmlDictionaryWriter.CreateDictionaryWriter(writer), graph);
    }
    public abstract void WriteStartObject(XmlDictionaryWriter writer, object graph);
    public virtual void WriteStartObject(XmlWriter writer, object graph)
    {
        CheckNull(writer, "writer");
        this.WriteStartObject(XmlDictionaryWriter.CreateDictionaryWriter(writer), graph);
    }
}

我们可以再服务操作里使用数据契约类型参数或者返回此类型的结果,WCF框架会自动使用DataContractSerializer对参数或者结果进行序列化合反序列化。.NET 的内建基本类型如String、int等默认具有数据契约的特性,支持公开的标准,因此都是可以序列化的。

【4】代码实现与分析:

下面是示例代码的具体实现过程,这里做简要的讲解.

【4.1】数据契约:

我们定义了一个数据契约类UserDataContract,包含简单的几个属性Name、Email、Mobile,分别用来存储用户名、电子邮件、手机信息,代码如下:


  [DataContract]//数据契约属性声明
    class UserDataContract
    {
        [DataMember(Name = "UserName")]//数据成员标记,支持别名定义
        public string Name
        {
            get;set;
        }
        [DataMember(Name = "UserEmail")]//数据成员标记,支持别名定义
        public string Email
        {
            get;set;
        }
        [DataMember]//数据成员标记
        public string Mobile
        {
            get;set;
        }
        //没有[DataMember]声明,不会被序列化
        public string Address
        {
            get; set;
        }
    }

【4.2】服务契约:

服务契约我们定义里连个操作AddNewUser(UserDataContract user)和UserDataContract GetUserByName(string name);分别为了测试数据契约的序列化和传递特性,服务类里给出了简单的实现,实际项目应用过程中,我们可以访问数据库进行数据持久化操作,或者调用封装其他的业务逻辑。代码如下:


 //1.服务契约
    [ServiceContract(Namespace = "http://www.cnblogs.com/frank_xl/")]
    public interface IWCFService
    {
        //操作契约
        [OperationContract]
        bool AddNewUser(UserDataContract user);
        //操作契约
        [OperationContract]
        UserDataContract GetUserByName(string name);
    }
    //2.服务类,继承接口。实现契约
    public class WCFService : IWCFService
    {
        //实现接口定义的方法
        public bool AddNewUser(UserDataContract user)
        {
            //这里可以定义数据持久化操作,访问数据库等
            //不给出具体代码
            Console.WriteLine("Hello! ,This an DataContract demo for WCF Service ");
            return true;
        }
        //实现接口定义的方法
        public UserDataContract GetUserByName(string name)
        {
            UserDataContract userDataContract = new UserDataContract();
            userDataContract.Address = "ShangHai China";
            userDataContract.Email = "frank_xl@163.com";
            userDataContract.Name = "Frank Xu Lei";
            Console.WriteLine("Hello! {0},This an overloading demo WCF Service ", name);
            return userDataContract;
        }
    }
   

【4.3】客户端:

托管宿主的配置前面已经介绍过具体的配置过程,这里不再详述。客户端添加服务引用,反序列化生成的客户端数据契约代码如下:

Code

我们可以看到客户端数据契约的属性都添加了[System.Runtime.Serialization.DataMemberAttribute()]标记,并且我们在服务端数据契约使用别名属性客户端也做了相应的调整。并且生成了相应的私有数据成员字段。

客户端编写代码进行测试,分别测试增加客户端传递数据契约对象和从服务端返回数据契约对象,代码如下:


 class WCFClient
    {
        static void Main(string[] args)
        {
            //实例化客户端服务代理Tcp
            WCFServiceClient wcfServiceProxy =
                new WCFServiceClient("WCFDataContractFormatting.IWCFService");
            Console.WriteLine("Test call service using TCP--------------------.");
            //通过代理调用服务,分别传递不同的参数,进行测试
            //实例化数据契约对象,设置信息
            UserDataContract userDataContract = new UserDataContract();
            userDataContract.UserName = "WCF Client: Frank Xu Lei";
            userDataContract.UserEmail = "WCF Client: frank_xl@163.com ";
            userDataContract.Mobile = "WCF Client:1666666666";
            //调用代理服务,增加用户操作:
            wcfServiceProxy.AddNewUser(userDataContract);             //查询用户信息:
            string name = "WCF Client: Frank Xu";
            UserDataContract userData = wcfServiceProxy.GetUserByName(name);
            if (userData != null)
            {
                Console.WriteLine(userData.UserName);
                Console.WriteLine(userData.UserEmail);
                Console.WriteLine(userData.Mobile);
            }             //Debuging
            Console.WriteLine("Press any key to continue");
            Console.Read();         }
    }
}

显示结果正确,通过数据契约实现了客户端与WCF服务端的信息交互。如下图:

WCF分布式开发步步为赢(7):WCF数据契约与序列化

【5】总结

以上就是本节的全部内容。数据契约的设计和使用在WCF面向服务的分布式应用系统开发中有重要的意义。全文首先介绍了数据契约的基本概念,然后对现有的三种.NET 序列化机制进行了简单的介绍和对比,指出其相关特性。然后对WCF序列化机制做了详细介绍,WCF开发自己的面相服务的格式化器是因为现有的.NET 格式化器的功能上的局限性。最后给出了在WCF服务中使用数据契约进行服务操作的代码实现,并给出了相应的分析。最后是本文的示例代码供大家下载
/Files/frank_xl/WCFServiceDataContractFormattingFrankXuLei.rar .欢迎留言交流。

参考文章:

1,《序列化》http://baike.baidu.com/view/160029.htm

2,《programming in WCF》