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

本节继续学习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的客户端和服务端参数传递的过程如下图:

主要步骤:客户端序列化参数为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 = "[email protected]";
            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: [email protected] ";
            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服务端的信息交互。如下图:

【5】总结

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

参考文章:

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

2,《programming in WCF》

时间: 2024-10-21 07:53:34

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

WCF分布式开发步步为赢(3)WCF服务元数据交换、配置及编程开发

今天我们继续WCF分布式开发步步为赢(3)WCF服务元数据交换.配置及编程开发的学习.经过前面两节的学习,我们了解WCF分布式开发的相关的基本的概念和自定义宿主托管服务的完整的开发和配置过程.今天我们来详细学习WCF服务元数据交换的相关内容.WCF服务元数据究竟是什么?为什么WCF服务要暴露元数据交换节点?这些和以前的Web Service有什么关系?WCF服务元数据交换的方式有那些?我们如何实现WCF服务元数据交换,本节我们会详细讲解.全文结构如下:[1]WCF服务元数据的基本概念.[2]WC

WCF分布式开发步步为赢(13):WCF服务离线操作与消息队列MSMQ

之前曾经写过一个关于MSMQ消息队列的文章:WCF分布式开发必备知识(1):MSMQ消息队列 ,当时的目的也是用它来作为学习WCF 消息队列MSMQ编程的基础文章.在那篇文章里,我们详细介绍了MSMQ消息队列的基本概念.安装.部署.开发.调试等相关问题.今天我们来学习WCF分布式开发步步为赢(13):WCF服务离线操作与消息队列MSMQ.在WCF框架下使用MSMQ消息队列服务编程.  这里我会给出一个使用WCF MSMQ实现离线请求的DEMO示例程序. 全文结构是:[1]MSMQ基本概念[2]W

WCF分布式开发步步为赢(12):WCF事务机制(Transaction)和分布式事务编程

今天我们继续学习WCF分布式开发步步为赢系列的12节:WCF事务机制(Transaction)和分布式事务编程.众所周知,应用系统开发过程中,事务是一个重要的概念.它是保证数据与服务可靠性的重要机制. 作为面向服务应用的开发平台,WCF也提供了对事物编程模型的支持..NET 2.0提供的System.Transactions类来开发事务应用程序.同样WCF也支持事务特性,WCF事务机制是什么,它与微软已有的技术如Microsoft 分布式事务协调器 (MSDTC)有何关系?与Enterpise

WCF分布式开发步步为赢(4):WCF服务可靠性传输配置与编程开发

今天继续WCF分布式开发步步为赢系列的第4节:WCF服务可靠性传输配置与编程开发.这个章节,我们要介绍什么是WCF服务的可靠性传输,随便介绍网络协议的概念,Web Service为什么不支持可靠性传出,具体的WCF绑定协议和可靠性的关系,实现可靠性传输有什么方式,以及配置和开发的详细实现代码分析部分.[1]可靠性传输[2]配置方式实现可靠性传输[3]编程方式实现可靠性传输[4]编程实现必备有序传递[5]结果分析和总结. 下面进入正式的内容: [1]可靠性传输: [1.0]网络协议基础知识: 这里

WCF分布式开发步步为赢(11):WCF流处理(Streaming)机制

WSE3.0框架提供了数据优化传输机制,WSE3.0构建Web服务安全(4):MTOM消息传输优化和文件上传.下载 疑问里进行了介绍.WCF同样也提供了流操作来支持大数据对象的传输和处理优化机制,今天我们WCF分布式开发步步为赢系列的(4):使用流操作(Streaming Operations)优化传输.本节会详细介绍流操作的相关概念.编程实现过程,以及实际开发过程中需要主要的一些问题.本节结构:[1]流处理的概念[2]流处理的特点[3]示例代码分析[4]总结.最后上传本文的示例代码. Stre

WCF分布式开发步步为赢(14):WCF安全编程--基本概念

WCF安全机制是个非常复杂的问题,因为涉及的知识点较多,所以今天这个文章,会分析进行WCF安全开发应该了解的哪些知识点.如何查看资料.为了更好地理解WCF安全相关知识,我把WCF安全机制主要知识点整理为图表.本章以介绍WCF安全机制的基础概念为主.  要学习WCF安全编程,你应该学习什么首先掌握什么基础知识?很多时候会因为缺乏系统的安全概念,在进行WCF安全编程开发的时候,遇到很多问题,比如所证书,这个概念相信很多初学者第一次接触的时候花费了很多时间.我当时在做WSE安全开发的时候就查阅了很多资

WCF分布式开发步步为赢(6):WCF服务契约继承与分解设计

上一节我们学习了WCF分布式开发步步为赢(5)服务契约与操作重载部分.今天我们来继续学习WCF服务契约继承和服务分解设计相关的知识点.WCF服务契约继承有何优势和缺点?实际项目里契约设计有什么原则和依据?面向对象的设计经验有何值得借鉴的地方?这里我们会一一给出详细的介绍.本文首先介绍的是WCF服务中契约继承的一些概念.例子代码分析,其次来讲解服务契约的设计问题.首先介绍的也是进行服务设计的必要性,服务设计的原则,示例代码分析.最后是全文的总结部分.结构如下:[1]OO面向对象设计原则,[2]服务

WCF分布式开发步步为赢(5)服务契约与操作重载

继上一节WCF分布式开发步步为赢系列的(4):WCF服务可靠性传输配置与编程开发,本节我们继续学习WCF分布式开发步步为赢的第(5)节:服务契约与操作重载.这里我们首先讲解OOP面向对象的编程中方法重载,重载的意义,WCF服务编程开发如何实现操作重载,随后是代码分析部分,给出了服务端服务契约定义和实现操作重载的注意的问题和实现过程,然后详细介绍了客户端实现操作重载的方式.最后是本文的总结部分.本节的结构是:[1]重载概念[2]操作重载[3]代码实现分析[4]运行结果[5]总结 [1]重载概念:

WCF分布式开发步步为赢(15):错误契约(FaultContract)与异常处理(ExceptionHandle)

今天学习WCF分布式开发步步为赢系列的15节:错误契约(FaultContract)与异常处理(ExceptionHandle).本节内容作为WCF分布式开发的一个重要知识点,无论在学习还是项目中都应该有所了解.此前也和多位学习爱好者讨论过WCF异常处理的相关知识.这里就系统整理一下,共大家参考.同时也是对<WCF分布式开发步步为赢>系列文章的完善和补充.   本节主要涉及的知识点就是:[1].NET异常处理[2]WCF异常处理[3]错误契约[4]WCF异常处理扩展[5]示例代码分析,最后是[