WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化

时间:2021-08-21 00:23:32

原文:WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化

[爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道《天天山海经》为此录制的节目视频(苏州话)]]在本篇文章中,我们将讨论WCF四大契约(服务契约、数据契约、消息契约和错误契约)之一的消息契约(Message Contract)。服务契约关注于对服务操作的描述,数据契约关注于对于数据结构和格式的描述,而消息契约关注的是类型成员与消息元素的匹配关系。

我们知道只有可序列化的对象才能通过服务调用在客户端和服务端之间进行传递。到目前为止,我们知道的可序列化类型有两种:一种是应用了System.SerializableAttribute特性或者实现了System.Runtime.Serialization.ISerializable接口的类型;另一种是数据契约对象。对于基于这两种类型的服务操作,客户端通过System.ServiceModel.Dispatcher.IClientMessageFormatter将输入参数格式化成请求消息,输入参数全部内容作为有效负载置于消息的主体中;同样地,服务操作的执行结果被System.ServiceModel.Dispatcher.IDispatchMessageFormatter序列化后作为回复消息的主体。

在一些情况下,具有这样的要求:当序列化一个对象并生成消息的时候,希望将部分数据成员作为SOAP的报头,部分作为消息的主体。比如说,我们有一个服务操作采用流的方式进行文件的上载,除了以流的方式传输以二进制表示的文件内容外,还需要传输一个额外的基于文件属性的信息,比如文件格式、文件大小等。一般的做法是将传输文件内容的流作为SOAP的主体,将其属性内容作为SOAP的报头进行传递。这样的功能,可以通过定义消息契约来实现。

一、 消息契约的定义

消息契约和数据契约一样,都是定义在数据(而不是功能)类型上。不过数据契约旨在定义数据的结构(将数据类型与XSD进行匹配),而消息契约则更多地关注于数据的成员具体在SOAP消息中的表示。消息契约通过以下3个特性进行定义:System.ServiceModel.MessageContractAttributeSystem.ServiceModel.MessageHeaderAttributeSystem.ServiceModel.MessageBodyMemberAttribute。MessageContractAttribute应用于类型上,MessageHeaderAttribute和MessageBodyMemberAttribute则应用于属性或者字段成员上,表明相应的数据成员是一个基于SOAP报头的成员还是SOAP主体的成员。先来简单介绍一下这3个特性:

1、MessageContractAttribute

通过在一个类或者结构(Struct)上应用MessageContractAttribute使之成为一个消息契约。从MessageContractAttribute的定义来看,MessageContractAttribute大体上具有以下两种类型的属性成员:

  • ProtectionLevel和HasProtectionLevel:表示保护级别,在服务契约中已经对保护级别作了简单的介绍,WCF中通过System.Net.Security.ProtectionLevel枚举定义消息的保护级别。一般有3种可选的保护级别:None、Sign和EncryptAndSign
  • IsWrapped、WrapperName、WrapperNamespace:IsWrapped表述的含义是是否为定义的主体成员(一个或者多个)添加一个额外的根节点。WrapperName和WrapperNamespace则表述该根节点的名称和命名空间。IsWrapped、WrapperName、WrapperNamespace的默认是分别为true、类型名称和http://tempuri.org/
   1: [AttributeUsage(AttributeTargets.Struct | AttributeTargets.Class, AllowMultiple = false)]

   2: public sealed class MessageContractAttribute : Attribute

   3: {  

   4:     //其他成员

   5:     public bool         HasProtectionLevel { get; }

   6:     public ProtectionLevel    ProtectionLevel { get; set; }

   7:  

   8:     public bool     IsWrapped { get; set; }

   9:     public string     WrapperName { get; set; }

  10:     public string     WrapperNamespace { get; set; }

  11: }

下面的代码中将Customer类型通过应用MessageContractAttribute使之成为一个消息契约。ID和Name属性通过应用MessageHeaderAttribute定义成消息报头(Header)成员,而Address属性则通过MessageBodyMemberAttribute定义成消息主体(Body)成员。后面的XML体现的是Customer对象在SOAP消息中的表现形式。

   1: [MessageContract]

   2: public class Customer

   3: {

   4:     [MessageHeader(Name = "CustomerNo", Namespace = "http://www.artech.com/")]

   5:     public Guid ID

   6:     { get; set; }

   7:  

   8:     [MessageHeader(Name = "CustomerName", Namespace = "http://www.artech.com/")]

   9:     public string Name

  10:     { get; set; }

  11:  

  12:     [MessageBodyMember(Namespace = "http://www.artech.com/")]

  13:     public string Address

  14:     { get; set; }

  15: }

   1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">

   2:     <s:Header>

   3:         <a:Action s:mustUnderstand="1">http://tempuri.org/IOrderManager/ProcessOrder</a:Action>

   4:         <h:CustomerName xmlns:h="http://www.artech.com/">Foo</h:CustomerName>

   5:         <h:CustomerNo xmlns:h="http://www.artech.com/">2f62405b-a472-4d1c-8c03-b888f9bd0df9</h:CustomerNo>

   6:     </s:Header>

   7:     <s:Body>

   8:         <Customer xmlns="http://tempuri.org/">

   9:             <Address xmlns="http://www.artech.com/">#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</Address>

  10:         </Customer>

  11:     </s:Body>

  12: </s:Envelope> 

如果我们将IsWrapped的属性设为false,那么套在Address节点外的Customer节点将会从SOAP消息中去除。

   1: [MessageContract(IsWrapped = false)]

   2: public class Customer

   3: {

   4:       //省略成员

   5: }

   1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">

   2:     ......

   3:     <s:Body>

   4:         <Address xmlns="http://www.artech.com/">#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</Address>

   5:     </s:Body>

   6: </s:Envelope>

我们同样可以自定义这个主体封套(Wrapper)的命名和命名空间。下面我们就通过将MessageContractAttribute的WrapperName和WrapperNamespace属性设为Cust和http://www.artech.com/。

   1: [MessageContract(IsWrapped = true, WrapperName = "Cust", WrapperNamespace = "http://www.artech.com/")]

   2: public class Customer

   3: {

   4:     //省略成员

   5: }

   1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">

   2:     ......

   3:     <s:Body>

   4:         <Cust xmlns="http://www.artech.com/">

   5:             <Address>#328, Airport Rd, Industrial Park, Suzhou Jiangsu Province</Address>

   6:         </Cust>

   7:     </s:Body>

   8: </s:Envelope>

2、MessageHeaderAttribute

MessageHeaderAttribute和MessageBodyMemberAttribute分别用于定义消息报头成员和消息主体成员,它们都有一个共同的基类:System.ServiceModel.MessageContractMemberAttribute。MessageContractMemberAttribute定义了以下属性成员:HasProtectionLevel、ProtectionLevel、Name和Namespace。

   1: public abstract class MessageContractMemberAttribute : Attribute

   2: {   

   3:     public bool             HasProtectionLevel { get; }

   4:     public ProtectionLevel     ProtectionLevel { get; set; }

   5:  

   6:     public string             Name { get; set; }

   7:     public string             Namespace { get; set; }

   8: }

通过在属性或者字段成员上应用MessageHeaderAttribute使之成为一个消息报头成员。MessageHeaderAttribute定义了以下3个属性,如果读者对SOAP规范有一定了解的读者,相信对它们不会陌生。

注:在《WCF技术剖析(卷1)》中的第六章有对SOAP 1.2的基本规范有一个大致的介绍,读者也可以直接访问W3C网站下载官方文档。

  • Actor:表示处理该报头的目标节点(SOAP Node),SOAP1.1中对应的属性(Attribute)为actor,SOAP 1.2中就是我们介绍的role属性
  • MustUnderstand:表述Actor(SOAP 1.1)或者Role(SOAP 1.2)定义的SOAP节点是否必须理解并处理该节点。对应的SOAP报头属性为mustUnderstand
  • Relay:对应的SOAP报头属性为relay,表明该报头是否需要传递到下一个SOAP节点
   1: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)]

   2: public class MessageHeaderAttribute : MessageContractMemberAttribute

   3: {

   4:     public string     Actor { get; set; }

   5:     public bool     MustUnderstand { get; set; }

   6:     public bool     Relay { get; set; }

   7: }

同样使用上面定义的Customer消息契约,现在我们相应地修改了ID属性上的MessageHeaderAtribute设置:MustUnderstand = true, Relay=true, Actor=http://www.w3.org/ 2003/05/soap-envelope/role/ultimateReceiver。实际上将相应的SOAP报头的目标SOAP节点定义成最终的消息接收者。由于http://www.w3.org/2003/05/soap-envelope/role/ultimateReceiver是SOAP 1.2的预定义属性,所以这个消息契约之后在基于SOAP 1.2的消息版本中有效。后面给出的为对应的SOAP消息。

   1: [MessageContract(IsWrapped =true, WrapperNamespace="http://www.artech.com/")]public class Customer

   2: {

   3:     //其他成员

   4:     [MessageHeader(Name="CustomerNo", Namespace = "http://www.artech.com/" ,MustUnderstand = true, Relay=true, Actor="http://www.w3.org/2003/05/soap-envelope/role/ultimateReceiver" )]

   5:     public Guid ID

   6:     { get; set; }

   7:     

   8: }

   1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">

   2:     <s:Header>

   3:         ......

   4:         <h:CustomerNo s:role="http://www.w3.org/2003/05/soap-envelope/role/ultimateReceiver" s:mustUnderstand="1" s:relay="1" xmlns:h="http://www.artech.com/">5330c91a-7fd7-4bf5-ae3e-4ba9bfef3d4d</h:CustomerNo>

   5:     </s:Header>

   6: ......

   7: </s:Envelope>

http://www.w3.org/2003/05/soap-envelope/role/ultimateReceiver在SOAP1.1中对应的表示为:"http://schemas.xmlsoap.org/soap/actor/ultimateReceiver(具有不同的命名空间)。如果在SOAP 1.1下,ID成员对应的MessageHeaderAttribute应该做如下的改动。从对应的SOAP消息来看,在SOAP 1.2中的role属性变成了actor属性。

   1: [MessageContract(IsWrapped =true, WrapperNamespace="http://www.artech.com/")]public class Customer

   2: {

   3:     //其他成员

   4:     [MessageHeader(Name="CustomerNo", Namespace = "http://www.artech.com/" ,MustUnderstand = true, Relay=true, Actor="http://schemas.xmlsoap.org/soap/actor/ultimateReceiver" )]

   5:     public Guid ID

   6:     { get; set; }    

   7: }

   1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">

   2:     <s:Header>

   3:         ......

   4:         <h:CustomerNo s:actor="http://schemas.xmlsoap.org/soap/actor/ultimateReceiver" s:mustUnderstand="1" xmlns:h="http://www.artech.com/">e48a8897-c644-49f8-b5e7-cd16be4c75b7</h:CustomerNo>

   5:     </s:Header>

   6:     ......

   7: </s:Envelope>

3、MessageBodyMemberAttribute

MessageBodyMemberAttribute应用于属性或者字段成员,应用了该特性的属性或者字段的内容将会出现在SOAP的主体部分。MessageBodyMemberAttribute的定义显得尤为简单,仅仅具有一个Order对象,用于控制成员在SOAP消息主体中出现的位置。默认的排序规则是基于字母排序。

可能细心的读者会问,为什么MessageHeaderAttribute中没有这样Order属性呢?原因很简单,MessageHeaderAttribute定义的是单个SOAP报头,SOAP消息报头集合中的每个报头元素是次序无关的。而MessageBodyMemberAttribute则是定义SOAP主体的某个元素,主体成员之间的次序也是契约的一个重要组成部分。所以MessageHeaderAttribute不叫MessageHeaderMemberAttribute。

   1: [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = false)]

   2: public class MessageBodyMemberAttribute : MessageContractMemberAttribute

   3: {

   4:     public int Order { get; set; }

   5: }

二、实例演示:基于消息契约的方法调用是如何格式化成消息的?

在WCF体系中,MessageFormatter负责序列化和反序列化任务(在《WCF技术剖析(卷1)》中的第5章对基于MessageFormatter的序列化机制有详细的介绍):ClientMessageFormatter和DispatchMessageFormatter分别在客户端和服务端,根据操作的描述(Operation Description),借助于相应的序列化器(Serializer)实现了方法调用与消息之间的转换。接下来,我将通过一个实实在在的案例程序为大家演示如何通过ClientMessageFormatter将输入参数转换为基于当前服务操作的Message。由于本节的主题是消息契约,所以在这里我们将转换对象限定为消息契约。不过,不论是消息参数还是一般的可序列化对象,其转换过程都是一样的。

步骤一:创建消息契约

本案例模拟一个订单处理的WCF应用,我们首先定义如下一个Order类型。Order是一个消息契约,属性OrderID和Date通过MessageHeaderAttribute定义成消息报头,作为主体的Details的类型OrderDetails被定义成集合数据契约。OrderDetails的元素类型是数据契约OrderDetail,代表订单中每笔产品明细。

   1: using System;

   2: using System.Collections.Generic;

   3: using System.Runtime.Serialization;

   4: using System.ServiceModel;

   5: namespace Artech.TypedMessage

   6: {

   7:     [MessageContract]

   8:     public class Order

   9:     {

  10:         [MessageHeader(Namespace ="http://www.artech.com/")]

  11:         public Guid OrderID

  12:         { get; set; }

  13:  

  14:         [MessageHeader(Namespace ="http://www.artech.com/")]

  15:         public DateTime Date

  16:         { get; set; }

  17:  

  18:         [MessageBodyMember]

  19:         public OrderDetails Details

  20:         { get; set; }

  21:  

  22:       public override string ToString()

  23:         {

  24:             return string.Format("Oder ID: {0}\nDate: {1}\nDetail Count: {2}",this.OrderID,this.Date.ToShortDateString(),this.Details.Count);

  25:         }

  26:     }

  27:  

  28:     [CollectionDataContract(ItemName = "Detail",Namespace ="http://www.artech.com/")]

  29:     public class OrderDetails : List<OrderDetail>

  30:     { }

  31:  

  32:     [DataContract(Namespace ="http://www.artech.com/")]

  33:     public class OrderDetail

  34:     {

  35:         [DataMember]

  36:         public Guid ProductID

  37:         { get; set; }

  38:  

  39:         [DataMember]

  40:         public int Quantity

  41:         { get; set; }

  42:     }

  43: }

步骤二:创建MessageFormatter

本例的目的在于重现WCF如何通过ClientMessageFormatter实现将输入参数序列化成请求消息,以及通过DispatchMessageFormatter实现将请求消息反序列化成输入参数。根据使用的序列化器的不同,WCF中定义了两种典型的MessageFormatter:一种是基于DataContractSerializer的DataContractSerializerOperationFormatter;另一种则是基于XmlSerializer的XmlSerializerOperationFormatter。由于DataContractSerializerOperationFormatter是默认的MessageFormatter,所以我们这个案例就采用DataContractSerializerOperationFormatter。

我们的任务就是创建这个DataContractSerializerOperationFormatter。由于这是一个定义在System.ServiceModel.Dispatcher命名空间下的内部(internal)类型,所以我们只能通过反射的机制调用构造函数来创建这个对象。DataContractSerializerOperationFormatter定义了唯一的一个构造函数,3个输入参数类型分别为:OperationDescription,DataContractFormatAttribute和DataContractSerializerOperationBehavior。

   1: internal class DataContractSerializerOperationFormatter : OperationFormatter

   2: {    

   3:     //其他成员

   4:     public DataContractSerializerOperationFormatter(OperationDescription description, DataContractFormatAttribute dataContractFormatAttribute, DataContractSerializerOperationBehavior serializerFactory);

   5: }

为此我们定义下面一个辅助方法CreateMessageFormatter<TFormatter, TContract>。TFormatter代表MessageFormatter的两个接口:IClientMessageFormatter和IDispatchMessageFormatter(DataContractSerializerOperationFormatter同时实现了这两个接口),TContract则是服务契约的类型。参数operationName为当前操作的名称。代码不算复杂,主要的流程如下:通过服务契约类型创建ContractDescription,根据操作名称得到OperationDescription对象。通过反射机制调用DataContractSerializerOperationFormatter的构造函数创建该对象。

   1: static TFormatter CreateMessageFormatter<TFormatter, TContract>(string operationName)

   2: {

   3:     ContractDescription contractDesc = ContractDescription.GetContract(typeof(TContract));

   4:     var operationDescs = contractDesc.Operations.Where(op => op.Name == operationName);

   5:     if(operationDescs.Count() == 0)

   6:     {

   7:        throw new ArgumentException("operationName","Invalid operation name.");

   8:     }

   9:     OperationDescription operationDesc = operationDescs.ToArray()[0];

  10:     string formatterTypeName = "System.ServiceModel.Dispatcher.DataContractSerializerOperationFormatter,System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089";

  11:     Type formatterType = Type.GetType(formatterTypeName);

  12:     ConstructorInfo constructor = formatterType.GetConstructor(new Type[] { typeof(OperationDescription), typeof(DataContractFormatAttribute), typeof(DataContractSerializerOperationBehavior) });

  13: return (TFormatter)constructor.Invoke(new object[] { operationDesc, new DataContractFormatAttribute(), null });

  14: }   

MessageFormatter已经创建出来了,序列化与反序列化的问题就很简单了。为此我定义了以下两个辅助方法:SerializeRequest<TContract>和DeserializeRequest<TContract>,具体实现就是调用创建出来的MessageFormatter的同名方法。

   1: static Message SerializeRequest<TContract>(MessageVersion messageVersion, string operationName, params object[] values)

   2: {

   3:     IClientMessageFormatter formatter = CreateMessageFormatter<IClientMessageFormatter, TContract>(operationName);

   4:     return formatter.SerializeRequest(messageVersion, values);

   5: } 

   6:  

   7: static void DeserializeRequest<TContract>(Message message, string operationName, object[] parameters)

   8: {

   9:     IDispatchMessageFormatter formatter = CreateMessageFormatter<IDispatchMessageFormatter, TContract>(operationName);

  10:     formatter.DeserializeRequest(message, parameters);

  11: }

步骤三:通过MessageFormmatter实现消息的格式化

现在我们通过一个简单的例子来演示通过上面创建的MessageFormatter实现对消息的格式化。由于MessageFormatter进行序列化和反序列化依赖于操作的描述(消息的结构本来就是由操作决定的),为此我们定义了一个服务契约IOrderManager。操作ProcessOrder将消息契约Order作为唯一的参数。

   1: using System.ServiceModel;

   2: namespace Artech.TypedMessage

   3: {

   4:     [ServiceContract]

   5:     public interface IOrderManager

   6:     {

   7:         [OperationContract]

   8:         void ProcessOrder(Order order);

   9:     }

  10: }

在下面的代码中,先调用SerializeRequest<IOrderManager>方法将Order对象进行序列化并生成Message对象,该过程实际上体现了WCF的客户端框架是如何通过ClientMessageFormatter将操作方法调用连同输入参数转换成请求消息的。随后,调用DeserializeRequest<IOrderManager>方法将Message对象反序列化成Order对象,该过程则代表WCF的服务端框架是如何通过DispatchMessageFormatter将请求消息反序列化成输入参数的。

   1: OrderDetail detail1 = new OrderDetail

   2: {

   3:     ProductID = Guid.NewGuid(),

   4:     Quantity = 666

   5: }; 

   6:  

   7: OrderDetail detail2 = new OrderDetail

   8: {

   9:     ProductID = Guid.NewGuid(),

  10:     Quantity = 999

  11: }; 

  12:  

  13: Order order = new Order

  14: {

  15:     OrderID = Guid.NewGuid(),

  16:     Date = DateTime.Today,

  17:     Details = new OrderDetails { detail1, detail2 }

  18: }; 

  19: //模拟WCF客户端的序列化

  20: Message message = SerializeRequest<IOrderManager>(MessageVersion.Default, "ProcessOrder", order);

  21: MessageBuffer buffer = message.CreateBufferedCopy(int.MaxValue);

  22: WriteMessage(buffer.CreateMessage(), "message.xml"); 

  23:  

  24: //模拟WCF服务端的反序列化

  25: object[] DeserializedOrder = new object[]{ null };

  26: DeserializeRequest<IOrderManager>(buffer.CreateMessage(), "ProcessOrder", DeserializedOrder);

  27: Console.WriteLine(DeserializedOrder[0]);

下面的XML表示调用SerializeRequest<IOrderManager>生成的SOAP消息。程序最终的输出结果也表明了反序列化的成功执行。

   1: <s:Envelope xmlns:a="http://www.w3.org/2005/08/addressing" xmlns:s="http://www.w3.org/2003/05/soap-envelope">

   2:     <s:Header>

   3:         <a:Action s:mustUnderstand="1">http://tempuri.org/IOrderManager/ProcessOrder</a:Action>

   4:         <h:Date xmlns:h="http://www.artech.com/">2008-12-21T00:00:00+08:00</h:Date>

   5:         <h:OrderID xmlns:h="http://www.artech.com/">cd94a6f0-7e21-4ace-83f7-2ddf061cfbbe</h:OrderID>

   6:     </s:Header>

   7:     <s:Body>

   8:         <Order xmlns="http://tempuri.org/">

   9:             <Details xmlns:d4p1="http://www.artech.com/" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">

  10:                 <d4p1:Detail>

  11:                     <d4p1:ProductID>bc2a186d-569a-4146-9b97-3693248104c0</d4p1:ProductID>

  12:                     <d4p1:Quantity>666</d4p1:Quantity>

  13:                 </d4p1:Detail>

  14:                 <d4p1:Detail>

  15:                     <d4p1:ProductID>72687c23-c2b2-4451-b6c3-da6d040587fc</d4p1:ProductID>

  16:                     <d4p1:Quantity>999</d4p1:Quantity>

  17:                 </d4p1:Detail>

  18:             </Details>

  19:         </Order>

  20:     </s:Body>

  21: </s:Envelope>

   1: Oder ID: cd94a6f0-7e21-4ace-83f7-2ddf061cfbbe

   2: Date: 12/21/2008

   3: Detail Count: 2

作者:Artech
出处:http://artech.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

WCF技术剖析之十八:消息契约(Message Contract)和基于消息契约的序列化的更多相关文章

  1. WCF技术剖析之十六:数据契约的等效性和版本控制

    原文:WCF技术剖析之十六:数据契约的等效性和版本控制 数据契约是对用于交换的数据结构的描述,是数据序列化和反序列化的依据.在一个WCF应用中,客户端和服务端必须通过等效的数据契约方能进行有效的数据交 ...

  2. WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇)

    原文:WCF技术剖析之十九:深度剖析消息编码(Encoding)实现(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话 ...

  3. WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用

    原文:WCF技术剖析之十五:数据契约代理(DataContractSurrogate)在序列化中的作用 [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经> ...

  4. WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇)

    原文:WCF技术剖析之十四:泛型数据契约和集合数据契约(下篇) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话)]]在.NE ...

  5. WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)

    原文:WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济 ...

  6. WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理

    原文:WCF技术剖析之十:调用WCF服务的客户端应该如何进行异常处理 在前面一片文章(服务代理不能得到及时关闭会有什么后果?)中,我们谈到及时关闭服务代理(Service Proxy)在一个高并发环境 ...

  7. WCF技术剖析之三十:一个很有用的WCF调用编程技巧&lbrack;上篇&rsqb;

    原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[上篇] 在进行基于会话信道的WCF服务调用中,由于受到并发信道数量的限制,我们需要及时的关闭信道:当遇到某些异常,我们需要强行中止(Abor ...

  8. WCF技术剖析之三十:一个很有用的WCF调用编程技巧&lbrack;下篇&rsqb;

    原文:WCF技术剖析之三十:一个很有用的WCF调用编程技巧[下篇] 在<上篇>中,我通过使用Delegate的方式解决了服务调用过程中的异常处理以及对服务代理的关闭.对于<WCF技术 ...

  9. WCF技术剖析之十三:序列化过程中的已知类型(Known Type)

    原文:WCF技术剖析之十三:序列化过程中的已知类型(Known Type) [爱心链接:拯救一个25岁身患急性白血病的女孩[内有苏州电视台经济频道<天天山海经>为此录制的节目视频(苏州话) ...

随机推荐

  1. 我的2013 Q&period;E&period;D

    "旧历的年底毕竟最像年底,村镇上不必说,就在天空中也显出将到新年的气象来.灰白色的沉重的晚云中间时时发出闪光,接着一声钝响,是送灶的爆竹:近处燃放的可就更强烈了,震耳的大音还没有息,空气里已 ...

  2. Odd Even Linked List

    Given a singly linked list, group all odd nodes together followed by the even nodes. Please note her ...

  3. WebService 用户名密码验证

    原文:WebService 用户名密码验证 在项目开发的过程中,WebService是经常要用的,当调用WebService方法时,需要经过服务的验证才可以调用,一般就是用户名/密码验证,还有一个就是 ...

  4. leetcode Binary Tree Right Side

    Given a binary tree, imagine yourself standing on the right side of it, return the values of the nod ...

  5. sklearn—特征工程

    sklearn实战-乳腺癌细胞数据挖掘(博主亲自录制视频) https://study.163.com/course/introduction.htm?courseId=1005269003& ...

  6. 不同路径II&lpar;一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为&OpenCurlyDoubleQuote;Start” )。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为&OpenCurlyDoubleQuote;Finish”)。 现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?网格中的障碍物和空位置分别用 1 和 0 来表示。&rpar;

    示例 1: 输入: [   [0,0,0],   [0,1,0],   [0,0,0] ] 输出: 2 解释: 3x3 网格的正中间有一个障碍物. 从左上角到右下角一共有 2 条不同的路径: 1. 向 ...

  7. C&num;ImageList和ListView的使用

    一.ImageList  ImageList组件,又称为图片存储组件,它主要用于存储图片资源,然后在控件上显示出来,这样就简化了对图片的管理.ImageList组件的主要属性是Images,它包含关联 ...

  8. 48- java Arrays&period;sort和collections&period;sort&lpar;&rpar;再次总结

    今天又碰到一个新BUG,记下来. 一直报空指针异常,我就很奇怪了,怎么就空指针了呢,我输出时,也能输出东西呀. 原来Arrays.sort() 和 Collections.sort() 都是对整个数组 ...

  9. node&period;js使用redis储存session(详细步骤)

    转储session的原因 网上有许多session需要用数据库储存的原因,对我来说原因很简单,仅仅只是node的生产环境不允许将session存到服务器的内存中.会报一个内存溢出的风险警告.所以我决定 ...

  10. 解决node使用中8080端口被占用

    1.首先按快捷键windows+R,在运行框里输入cmd,如图所示,进入黑色界面后,输入netstat -ano,查看端口. 2.找到8080端口,查看正在运行程序的pid,如图所示. 3.回到桌面, ...