从零开始学WCF(7)消息协定

来源:互联网 发布:电子图书阅读软件 编辑:程序博客网 时间:2024/05/21 15:51

消息协定概述

通常,定义消息的架构时使用数据协定就足够了。但有时必须精确控制如何将类型映射到通过网络传输的SOAP消息。
对于这种情况,最常见的方案是插入自定义SOAP标头。另一种常见方案是定义消息头和正文的安全属性,也就是说,确定是否对这些元素进行数字签名和加密。消息样式的操作可提供这种控制。



消息样式的操作最多具有一个参数和一个返回值,其中参数和返回值的类型都是消息类型都是消息类型;也就是说,这两种类型可直接序列化为指定的SOAP消息结构。
可以用MessageContractAttribute标记的任何类型或Message类型。

返回值为void是有效的;而且没有传入参数,但有返回的消息参数也是有效的。

若要为某一类型定义消息协定(即定义该类型和SOAP信封之间的映射)请对该类型应用MessageContractAttribute。然后对该类型中要成为SOAP标头的成员应用MessageHeaderAttribute,并对要成为消息的SOAP正文部分的成员应用MessageBodyMemberAttribute。
以MessageContractAttribute标记的类,可以用来作为以消息方式来传输数据到客户端的承载类型,并且WCF基础结构可以去序列化它
    [MessageContract] //定义消息协定类    public class BankingTransaction    {        [MessageHeader] //定义消息头字段        public Operation operation;        [MessageHeader]        public DateTime transactionDate;        [MessageBodyMember] //定义消息正文字段        private Account sourceAccout;        [MessageBodyMember]        private Account targetAccount;        [MessageBodyMember]        public int amount;    }

可以对所有字段、属性和事件应用MessageHeaderAttribute和MessageBodyMemberAttribute,而不管这些字段、属性和事件是公用的、私有的、受保护的还是内部的。这个和数据协定的规则是一样的。

DEMO

1) 新创建一个"Video7.Demo1.Message“的WCF Service Application项目,删除默认生成的service1.svc后,手动添加”WCF Service“服务——CalculatorService服务类,然后再ICalculatorService.cs里定义WCF服务接口:
using System;using System.Collections.Generic;using System.Linq;using System.Runtime.Serialization;using System.ServiceModel;using System.Text;namespace Video7.Demo1.Message{    //定义服务协定    [ServiceContract(Namespace = "http://Video7.Demo1.Message")]    public interface ICalculatorService    {        [OperationContract(Action = "http://test/MyMessage_action", ReplyAction = "http://test/MyMessage_action")]        //服务方法接受消息协定类型的参数,并且返回一个消息协定的类型MyMessage        MyMessage Calculator(MyMessage request);    }}

2) 创建MyMessage类型的消息协定类型:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.ServiceModel;namespace Video7.Demo1.Message{    //定义消息协定类 可以用来作为以消息方式来传输数据到客户端的承载类型,并且WCF基础结构可以去序列化它    [MessageContract]    public class MyMessage    {        private string operation;        private double n1;        private double n2;        private double result;        //构造方法——创建一个空的message对象        public MyMessage()        {                        }        //构造方法——创建带有参数的构造函数        public MyMessage(double n1,double n2, string operation,double result)        {            this.n1 = n1;            this.n2 = n2;            this.operation = operation;            this.result = result;        }        //构造方法——创建传递message类型参数的构造方法        public MyMessage(MyMessage message)        {            this.n1 = message.n1;            this.n2 = message.n2;            this.operation = message.operation;            this.result = message.result;        }        //定义标头成员        [MessageHeader]        public string Operation        {            get { return operation; }            set { operation = value; }        }        //定义消息正文成员        [MessageBodyMember]        public double N1        {            get { return n1; }            set { n1 = value; }        }        //定义消息正文成员        [MessageBodyMember]        public double N2        {            get { return n2; }            set { n2 = value; }        }        //定义消息正文成员        [MessageBodyMember]        public double Result        {            get { return result; }            set { result = value; }        }        //定义消息标头成员        [MessageHeader(MustUnderstand = true)]        public string str;    }}

3) 实现WCF服务接口类:
using System;using System.Collections.Generic;using System.Linq;using System.Runtime.Serialization;using System.ServiceModel;using System.Text;namespace Video7.Demo1.Message{    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "CalculatorService" in code, svc and config file together.    public class CalculatorService : ICalculatorService    {        public MyMessage Calculator(MyMessage request)        {            MyMessage response = new MyMessage(request);            switch (request.Operation)            {                 case "+":                    response.Result = request.N1 + request.N2;                    break;                case "-":                    response.Result = request.N1 - request.N2;                    break;                case "*":                    response.Result = request.N1 * request.N2;                    break;                default:                    response.Result = 0.0D;                    break;            }            return response;        }    }}

4) 修改配置文件Web.config:
<?xml version="1.0"?><configuration>  <system.web>    <compilation debug="true" targetFramework="4.0" />  </system.web>  <system.serviceModel>        <services>      <service name="Video7.Demo1.Message.CalculatorService" behaviorConfiguration="CalculatorServiceBehavior">        <endpoint address="" binding="wsHttpBinding" contract="Video7.Demo1.Message.ICalculatorService"/>        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>      </service>    </services>        <behaviors>      <serviceBehaviors>        <behavior name="CalculatorServiceBehavior">          <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->          <serviceMetadata httpGetEnabled="true"/>          <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->          <serviceDebug includeExceptionDetailInFaults="false"/>        </behavior>      </serviceBehaviors>    </behaviors>    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />  </system.serviceModel> <system.webServer>    <modules runAllManagedModulesForAllRequests="true"/>  </system.webServer>  </configuration>


5) 编译该WCF服务,并部署到IIS里。

6) 创建一个Window控制台程序Client来测试该服务,添加该WCF Service到该项目里。然后再Main方法中编写:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Client.ServiceReference1;namespace Client{    class Program    {        static void Main(string[] args)        {            CalculatorServiceClient client = new CalculatorServiceClient();            //创建一个MyMessage类型的参数            MyMessage request = new MyMessage();            request.N1 = 100D;            request.N2 = 15.99D;            request.Operation = "+";            MyMessage response = ((ICalculatorService)client).Calculator(request);                        Console.WriteLine("Add({0},{1}) = {2}", request.N1, request.N2, response.Result);            client.Close();            Console.ReadKey();        }    }}

源代码: http://download.csdn.net/detail/eric_k1m/6383581

在消息协定内部使用自定义类型

每个单独的消息头和消息正文部分均使用为消息所使用的服务协定选择的序列化引擎进行序列化(转换为XML)。
默认序列化引擎XmlFormatter可以显示处理(通过具有System.Runtime.Serialization.DataContractAttribute)或隐式处理(通过作为基类型具有System.SerializableAttribute等)具有数据协定的任何类型。

可以采用两种方式在消息协定中使用重复元素的数组:
直接在数组上使用MessageHeaderAttribute或MessageHeaderArryAttitude
1) 使用MessageHeaderAttribute:左边是消息协定定义,右边是序列化以后的XML文件


2) 使用MessageHeaderArrayAttribute:左边是消息协定定义,右边是序列化以后的XML文件。可以看出来使用MessageHeaderArrayAttribute后,序列化后的直接使用records作为节点,并没有DepositRecord这个子节点了。


DEMO

1) 新建一个WCF Service Application——Video7.Demo2.Message,删掉默认生成的服务后添加新的WCF Service——CalculatorService,然后再ICalculatorService这个WCF服务接口中定义WCF服务:
using System;using System.Collections.Generic;using System.Linq;using System.Runtime.Serialization;using System.ServiceModel;using System.Text;namespace Video7.Demo2.Message{    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the interface name "ICalculatorService" in both code and config file together.    [ServiceContract(Namespace = "http://Video7.Demo2.Message")]    public interface ICalculatorService    {        [OperationContract(Action = "http://test/MyMessage_action", ReplyAction = "http://test/MyMessage_action")]        MyMessage Calculate(MyMessage request);    }}


2) 定义消息协定类MyMessage以供WCF服务使用,这里注意ExtType是自定义类型,以供消息协定内部使用。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.ServiceModel;namespace Video7.Demo2.Message{    [MessageContract]    public class MyMessage    {        private string operation;        private double n1;        private double n2;        private double result;        //在消息协定内部定义一个自定义类型        private ExtType et;        private string strNew;        public MyMessage()        {        }        public MyMessage(double n1,double n2,string operation,double result)        {            this.n1 = n1;            this.n2 = n2;            this.operation = operation;            this.result = result;        }        public MyMessage(MyMessage message)        {            this.n1 = message.n1;            this.n2 = message.n2;            this.operation = message.operation;            this.result = message.result;        }        //定义消息协定的标头        [MessageHeader]        public string Operation        {            get { return operation; }            set { operation = value; }        }        //定义消息协定的正文        [MessageBodyMember]        public double N1        {            get { return n1; }            set { n1 = value; }        }        //定义消息协定的正文        [MessageBodyMember]        public double N2        {            get { return n2; }            set { n2 = value; }        }        //定义消息协定的正文        [MessageBodyMember]        public double Result        {            get { return result; }            set { result = value; }        }        //定义消息协定的正文 类型为自定义类型ExtType        
[MessageBodyMember]
public ExtType ExtType { set { et = value; } get { return et; } } //定义消息协定的标头 [MessageHeader(MustUnderstand = true)] public string NewString { get { return strNew; } set { strNew = value; } } }}


3) 定义我们要在消息协定内部使用的自定义类ExtType,这个类需要标记为数据协定DataContractAttribute。
using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.Runtime.Serialization;namespace Video7.Demo2.Message{    [DataContract]    public class ExtType    {        private string name1;        private string name2;        public ExtType()        {        }        public ExtType(string _name1,string _name2)        {            name1 = _name1;            name2 = _name2;        }        [DataMember]        public string Name1        {            get { return name1; }            set { name1 = value; }        }        [DataMember]        public string Name2        {            get { return name2; }            set { name2 = value; }        }    }}


4) 实现该WCF服务接口
using System;using System.Collections.Generic;using System.Linq;using System.Runtime.Serialization;using System.ServiceModel;using System.Text;namespace Video7.Demo2.Message{    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "CalculatorService" in code, svc and config file together.    public class CalculatorService : ICalculatorService    {        public MyMessage Calculate(MyMessage request)        {            MyMessage response = new MyMessage(request);            switch (request.Operation)            {                 case "+":                    response.Result = request.N1 + request.N2;                    break;                default:                    response.Result = 0.0D;                    break;            }            if (!request.ExtType.Equals(null))            {                response.ExtType = new ExtType(request.ExtType.Name1 + "11", request.ExtType.Name2 + "22");            }            return response;        }    }}

5) 部署到IIS上后,创建一个Windows控制台项目Client,然后引用该WCF服务。
6) 在Client项目中的Main方法中调用该WCF服务:
using System;using System.Collections.Generic;using System.Linq;using System.Text;using Client.ServiceReference1;namespace Client{    class Program    {        static void Main(string[] args)        {            CalculatorServiceClient client = new CalculatorServiceClient();            MyMessage request = new MyMessage();            request.N1 = 100D;            request.N2 = 15.99D;            request.Operation = "+";            ExtType et = new ExtType();            et.Name1 = "wang";            et.Name2 = "xiaoming";            request.ExtType = et;            MyMessage reponse = ((ICalculatorService)client).Calculate(request);            Console.WriteLine("Add({0},{1})= {2}", request.N1, request.N2, reponse.Result);            Console.WriteLine("After Process {0},{1}", reponse.ExtType.Name1, reponse.ExtType.Name2);            Console.ReadKey();        }    }}
7) 运行结果为:

源代码: http://download.csdn.net/detail/eric_k1m/6395961

对消息部分进行签名和加密

消息协定可以指示消息头或正文是否应进行数字签名和加密。
通过在MessageHeaderAttribute和MessageBodyMemberAttribute属性(Attribute)上设置System.ServiceModel.MessageContractMemberAttribute.ProtectionLevel属性(property)来完成。
—— System.Net.Security.ProtectionLevel
         1)  None 不加密或签名
         2)  Sign 仅数字签名
         3)  EncryptAndSign  加密并数字签名
   默认值为None

若要让这些安全功能起作用,必须正确配置绑定和行为。如果没有正确配置的情况下使用这些安全(例如,在不提供凭据的情况下试图对消息进行签名),则会在验证时引发异常。
对于消息头,会分别为每个消息头确定其保护级别。
对于消息正文,保护级别可理解为“最低保护级别”。无论包含几个正文部分,正文都只有一个保护级别。正文的保护级别由所有正文部分的最高ProtectionLevel属性设置确定。不过,应该将每个正文部分的保护级别设置为实际要求的最低保护级别。


recordID标头未受保护;
patientName标头为签名Sign级别;
SSN标头进行了加密和签名;
至少有一个正文部分medicalHistory使用了EncryAndSign,所以将对整个消息正文进行加密和签名,即使comments和diagnosis正文部分指定了较低的级别保护。

控制标头和正文部分的名称和命名空间

在消息协定的SOAP表示形式中,每个标头和正文部分都映射为一个具有名称和命名控件的XML元素。
通过操作System.ServiceModel.MessageContractAttribute.Name和System.ServiceModel.MessageContractMemberAttribute.Namespace(在MessageHeaderAttribute和MessageBodyMemberAttribute属性的父类上)可以更改这些默认值。

IsAudited标头位于代码中指定的命名控件中,表示theData成员的正文部分由名为transactionData的XML元素表示。

控制是否包装SOAP正文部分

默认情况下,SOAP正文部分会在包装元素内部进行序列化:

上面的消息协定序列化之后的XML是这样:

若要取消包装元素,请将IsWrapped属性设置为false。
若要取消包装元素的名称和命名控件,请使用WrapperName和WrapperNameSapace属性。


SOAP标头属性

SOAP标准定义了下列可存在于标头上的属性:
Actor/Role(在SOAP1.1中为Actor,在SOAP1.2中为Role): 指定要使用给定标头的节点的统一资源标示符(URI)。
MustUnderstand: 指定处理标头的节点是否必须理解该标头。有点像数据协定里的IsRequired属性。就是必须要在客户端和服务端都必须要有的意思。
Relay: 指定要将标头中继到下游节点。

WCF不会对传入消息的这些属性执行任何处理(MustUnderstand除外)。

静态方式将这些属性设置为任何需要的值:


通过代码以动态方式控制这些属性:


如果同时使用动态和静态控制机制,则静态设置用作默认设置,但可以在以后使用动态机制重写。

SOAP正文部分的顺序

默认情况下,正文元素采用字母顺序。
可以通过System.ServiceModel.MessageBodyMemberAttribute.Order属性进行控制,这个和数据协定也是类似的。但是在消息协定中,基类型正文成员不排列在派生类型正文成员之前。

消息协定版本管理

更改消息协定: 
应用程序的新版本可能会向消息中添加额外的标头。在从新版本应用程序向旧版本应用程序发送消息时,系统必须处理额外的标头;同样,反方向操作时系统必须处理缺少的标头。

下面的规则适用于标头的版本管理:
WCF不反对缺少标头,相应的成员将保留其默认值。
WCF还忽略意外的额外标头。此规则的一种列外情况是在传入的SOAP消息中,额外标头的MustUnderstand属性设置为true。在这种情况下,由于存在一个无法处理但必须理解的标头,因此会引发异常。

消息正文具有类似的版本管理规则,即忽略缺少和附加的消息正文部分。

性能注意事项

没个消息头和消息正文部分相互独立地进行序列化。因此可以为每个标头和正文部分重新声明相同的命名空间。为了提高性能,特别是对于消息在网络上的大小,请将多个标头和正文部分合并成一个标头或正文。

上面这个消息协定的性能差,是因为有一个标头和多个正文,这样就导致序列化的时候有很多的XML信息。
为了提高性能,可以把他的正文部分全都合并成一个数据协定类,这样可以提高性能。


DEMO

1) 将“Video7.Demo2.Message”这个项目的服务端的消息协定里的NewString由原来的[MessageHeader(MustUnderstand=ture)]修改为[MessageHeader],
        //定义消息协定的标头        [MessageHeader]        public string NewString        {            get { return strNew; }            set { strNew = value; }        }

2) 然后再编译该服务,发布到IIS上后,在更新客户端的服务引用,在客户端生成的Reference.cs代理类里,注释掉该服务的标头。
        //[System.ServiceModel.MessageHeaderAttribute(Namespace = "http://Video7.Demo2.Message")]        //public string NewString;

3) 这时候在运行该客户端的话,是不会有异常发生的,这是因为没有标记为MustUnderstand的标头,在客户端是可有可无的,并不是引发异常。
4) 但是如果把NewString这个标头添加上MustUnderstand=true,然后再编译替换服务后,在注释掉客户端代理类的NewString的情况下是会引发异常的。这是因为在服务端标记了MustUnderstand=true的消息协定标头,必须在客户端也有相应的代理类存在。
        //定义消息协定的标头        [MessageHeader(MustUnderstand=true)]        public string NewString        {            get { return strNew; }            set { strNew = value; }        }

5) 下面测试一下消息协定的Order属性,在消息协定中,并不指定消息正文N1,N2,Result的顺序,但是在客户端的代理类里,修改他们的顺序如下:
        [System.ServiceModel.MessageBodyMemberAttribute(Namespace="http://Video7.Demo2.Message", Order=0)]        public Client.ServiceReference1.ExtType ExtType;                [System.ServiceModel.MessageBodyMemberAttribute(Namespace="http://Video7.Demo2.Message", Order=1)]        public double N1;                [System.ServiceModel.MessageBodyMemberAttribute(Namespace="http://Video7.Demo2.Message", Order=4)]        public double N2;                [System.ServiceModel.MessageBodyMemberAttribute(Namespace="http://Video7.Demo2.Message", Order=3)]        public double Result;

则这个时候运行的结果是0,这是由于客户端的order顺序与服务端不一致,导致无法获取服务端的数据。
原创粉丝点击