从零开始学WCF(2)设计和实现服务协定

来源:互联网 发布:深圳迈瑞工资待遇知乎 编辑:程序博客网 时间:2024/04/30 11:39

创建服务协定

WCF术语

1. 消息: 消息是一个独立的数据单元,他可能由几个部分组成,包括消息正文和消息头。

2. 服务: 服务是一个构造,它公开一个或多个终结点,其中每个终结点都公开一个或多个服务操作。

3. 终结点: 终结点是用来发送或接受消息(或执行这两种操作)的构造。终结点包括一个定义消息可以发送到的目的地的位置(地址Address);一个描述消息应如何发送的通信机制规范(绑定Binding)以及对于可以在该位置发送或接收(或两者皆可)的一组消息的定义(服务协定Contract)——该定义还描述了可以发送何种消息。可以简答记忆为ABC。

WCF服务作为一个终结点集合向外界公开。

 

类或接口都可以定义服务协定,建议使用接口,因为接口可以直接对服务协定建模。

服务协定接口具有托管接口的所有优点:

1. 服务协定接口可以扩展任何数量的其他服务协定接口。

2. 一个类可以通过实现服务协定接口来实现任意数量的服务协定。

3. 可以通过更改接口实现来修改服务协定的实现,而让服务协定保持不变。

4. 可以通过实现旧接口和新接口来确定服务的版本。老客户端连接到原始版本,而新客户端则可以连接到教新的版本。

 

定义服务协定:在类或接口上使用ServiceContractAttribute属性标记。

定义服务操作:在方法上使用OperationContractAttribute属性对其标记。

参数和返回值:

(1)每个操作都有一个返回值和一个参数,即使他们为void。可以使用局部方法将对象的引用从一个对象传递到另一个对象,但它局部方法不同的是,服务操作不会传递对对象的引用,它们传递的只是对象的副本。这一点很重要,这是因为参数或返回值中使用的每个类型都必须是可序列化的,换而言之,该类型的对象必须能够转换成字节流,并能够从字节流转换为对象(这也就是可序列化的意义)。默认情况下,基元类型是可序列化的,.NET Framework中的很多类型都是可序列化的。

 

服务操作的消息模式1——请求/答复

通过请求/答复模式,请求发送方(客户端应用程序)将接收与请求相关的答复。这是默认的模式,因为它既支持传入操作(一个活多个参数传递到该操作中),也支持返回操作(该操作将一个或多个输出值传回给调用方法)。

        [OperationContract]        string GetData(int value);


请注意,除非指定其他基础消息模式,否则,及时服务操作返回void(在VB中为Nothing),也属于请求/答复消息交换。

操作的结果是:除非客户端异步调用操作,否则客户端将停止处理,直到收到返回消息,即使该消息正常情况下为空时也是如此。

缺点:如果执行操作需要很长的时间,则会降低客户端性能和响应能力。

优点:响应消息中可返回SOAP错误,这表明可能在通信或处理中发生了一些与服务有关的错误状况。

 

服务操作的消息模式2——单向

如果WCF服务应用程序的客户端不必等待操作完成,并且不处理SOAP错误,则该操作可以指定单向消息模式。

单向操作是客户端调用操作并在WCF将消息写入网络后继续进行处理的操作。通常这意味着,除非在出站消息中发送的数据极其庞大,否则客户端几乎立即继续运行(除非发送数据时出错)。此种类型的消息交换模式支持从客户端到服务应用程序的类似于事件的行为。

若要为返回void的操作指定单向消息交换,请将IsOneWay属性设置为true,默认为false

        [OperationContract(IsOneWay = true)]        void Hello(string greeting);

此方法与前面的请求/答复示例相同,但是将IsOneWay属性设置为true意味着尽管方法相同,服务操作也不会发送返回消息,而客户端将在出站消息抵达通道层时立即返回。

 

服务操作的消息模式3——双工

双工模式的特点是,无论使用单向消息发送还是请求/答复消息发送方式,服务和客户端均能够独立地向对方发送消息。对于必须直接与客户端通信或向消息交换的任意一方提供异步体验(包括类似于事件的行为)的服务来说,这种双向通信形式非常有用。

由于存在与客户端通信的附加机制,双向模式比请求/答复或单项模式率为复杂。

若要设计双工协定,还必须设定回调协定(让服务的方法能够调用客户端的方法),并将该回调协定的类型分配给标记服务协定的ServiceContractAttribute属性(Attribute)的CallbackContract属性(property)。回调协定就是为了让服务端调用客户端方法使用的。

若要实现双工模式,必须创建第二个接口,该接口包含在客户端调用的方法声明。

    //服务端使用CallbackContract声明客户端方法的接口的声明    [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode = SessionMode.Required, CallbackContract = typeof(ICalculatorDuplexCallback))]    public interface ICalculatorDuplex    {        [OperationContract(IsOneWay = true)]        void Clear();    }    //客户端有下面两个方法可以用于服务端回调使用    public interface ICalculatorDuplexCallback    {        [OperationContract(IsOneWay = true)]        void Equals(double result);        [OperationContract(IsOneWay = true)]        void Equation(string eqn);    }


DEMO演示——创建不同的WCF服务

请求/答复 单向 Demo

1. 新建WCF Service Library项目,先来进行请求/答复的模式的例子,默认的就会生成一个请求答复的服务,可以查看第一课来学习。

2. 在这里我们来实验一下是否是请求/答复的模式,我们在实现IService1的类Service1中添加一行让线程暂停10秒钟

        public string GetData(int value)        {            //让进程暂停10秒钟            System.Threading.Thread.Sleep(10000);            return string.Format("You entered: {0}", value);        }


3. 添加个Windows窗体应用程序,来测试一下这个WCF服务,在窗体里放一个按钮,在点击按钮的时候让他调用WCF服务,Debug一下就可以看出来,当点击按钮后不会马上返回字符串,而是在等待10秒后才会返回,并且可以看出来在点击按钮后的10秒内,不能进行其他操作,画面会卡在那里,等待WCF服务的返回值。

        private void button1_Click(object sender, EventArgs e)        {            ServiceReference1.Service1Client sc = new ServiceReference1.Service1Client();            MessageBox.Show(sc.GetData(5));        }


4. 下一步我们来做一个单项的测试,由于单项是没有返回值的,那么就需要做一个void类型返回值的方法

服务端IService1中添加:

        //单项模式        [OperationContract(IsOneWay = true)]        void TestMethod(string strInput);


5. 实现的时候在Service1类中实现,这里也是让线程等待10秒。那么我们就可以在这预先估算一下,既然这个服务是单向模式,所以在客户端点击按钮后,不会返回结果也不会等待线程的结束。

        public void TestMethod(string strInput)        {            System.Threading.Thread.Sleep(10000);        }


6. 注意修改了WCF服务后,需要编译一下WCF服务类库项目,然后还要在客户端程序中更新Service References,才可以使用新的WCF服务。右键点击“ServiceReference1”服务后,点击“Update Service Reference”即可。这样我们就可以在客户端的WCF代理类Reference.cs里查看到已经更新了TestMethod方法。

7. 我们在客户端调用一下新添加的WCF服务方法。

        private void button1_Click(object sender, EventArgs e)        {            ServiceReference1.Service1Client sc = new ServiceReference1.Service1Client();            //MessageBox.Show(sc.GetData(5));            sc.TestMethod("OneWayModel");            MessageBox.Show("After OneWayModel Message");        }


Debug后可以看到,无论在TestMethod里把线程暂停了多少秒,都不会影响到客户端的Show方法的执行,Show方法会马上执行下去。

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

双工 Demo

1. 新建一个WCF Service Library项目,然后把IService接口内容替换成如下,这是一个计算方法的WCF服务接口定义。

using System;using System.Collections.Generic;using System.Linq;using System.Runtime.Serialization;using System.ServiceModel;using System.Text;namespace Video2.WcfServiceLibrary2{    //这是一个双工的服务  所以涉及到服务端调用客户端回调的方法 由于这个例子的加减乘除是一系列的连续的调用 所以这个服务要求是一个会话的服务    //SessionMode是会话服务,会话服务是必须需要的 就是 SessionMode.Required 也就是该WCF服务执行的模式必须要开始会话才能执行    //CallbackContract是回调方法的协定类型接口 需要在服务端定义接口, 在客户端实现接口 typeof来进行类型化 是使用哪个类型的意思    [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode = SessionMode.Required, CallbackContract = typeof(ICalculatorDuplexCallback))]    public interface ICalculatorDuplex    {        //WCF服务的方法为单项模式        [OperationContract(IsOneWay = true)]        void Clear();        [OperationContract(IsOneWay = true)]        void AddTo(double n);        [OperationContract(IsOneWay = true)]        void SubtractFrom(double n);        [OperationContract(IsOneWay = true)]        void MultiplyBy(double n);        [OperationContract(IsOneWay = true)]        void DivideBy(double n);    }    //WCF服务端声明客户端需要实现的回调方法 他并不是服务 注意这里不需要使用[ServiceContract]属性标签 因为客户端是不能调用这个接口方法的    public interface ICalculatorDuplexCallback    {         //这也是单项模式 WCF服务端调用客户端的方法        [OperationContract(IsOneWay = true)]        void Equals(double result);        [OperationContract(IsOneWay = true)]        void Equation(string eqn);    }}


 

2. 在WCF服务接口好以后,我们在WCF服务端来实现这个接口,注意由于这是一个双工的服务,所以实现WCF服务接口的时候需要使用[ServiceBehavior]这个服务行为属性标签


 

using System;using System.Collections.Generic;using System.Linq;using System.Runtime.Serialization;using System.ServiceModel;using System.Text;namespace Video2.WcfServiceLibrary2{    // NOTE: You can use the "Rename" command on the "Refactor" menu to change the class name "Service1" in both code and config file together.    //ServiceBehavior是服务的行为 这是在实现WCF服务接口的时候使用的。 这里的PerSession指的是每个会话都会创建一个实例,这样才能保证前后会话之间的实例是唯一的,由于接口中执行的模式是必须要开启会话。    [ServiceBehavior(InstanceContextMode=InstanceContextMode.PerSession)]    public class CalculatorService : ICalculatorDuplex    {        double result;        string equation;        ICalculatorDuplexCallback callback = null;        public CalculatorService()        {            result = 0.0D;            equation = result.ToString();            //获取当前调用上下文的回调接口的类型,当前操作的实体(current)的回调通道(getcallbackchannel),是ICalculatorDuplexCallback类型的通道。            callback = OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();        }        public void Clear()        {            //回调方法的调用 当客户端调用服务端的Clear方法的时候,服务端又会紧接着调用客户端的Equation方法            //回调方法的实现是在客户端实现的*****            callback.Equation(equation + "=" + result.ToString());            result = 0.0D;            equation = result.ToString();        }        public void AddTo(double n)        {            result += n;            equation += "+" + n.ToString();            //回调客户端的Equals方法            callback.Equals(result);        }        public void SubtractFrom(double n)        {            result -= n;            equation += "-" + n.ToString();            //回调客户端方法            callback.Equals(result);        }        public void MultiplyBy(double n)        {            result *= n;            equation += "*" + n.ToString();            //回调客户端方法            callback.Equals(result);        }        public void DivideBy(double n)        {            result /= n;            equation += "/" + n.ToString();            //回调客户端方法            callback.Equals(result);        }    }}


3. 在做了服务的定义和实现后,我们还需要更改WCF的配置文件 App.Config文件

<?xml version="1.0" encoding="utf-8" ?><configuration>  <system.web>    <compilation debug="true" />  </system.web>  <!-- When deploying the service library project, the content of the config file must be added to the host's   app.config file. System.Configuration does not support config files for libraries. -->  <system.serviceModel>    <services>      <!--1.  Service Name需要修改为我们所修改的类服务Behavior设置成标记的类,并且还要添加behaviorConfiguration-->      <service name="Video2.WcfServiceLibrary2.CalculatorService" behaviorConfiguration="Video2.WcfServiceLibrary2.Service1Behavior">        <host>          <baseAddresses>            <!--2.  修改Address WCF服务地址-->            <add baseAddress = "http://localhost:8732/Design_Time_Addresses/Video2.WcfServiceLibrary2/CalculatorService/" />          </baseAddresses>        </host>        <!-- Service Endpoints -->        <!-- Unless fully qualified, address is relative to base address supplied above -->        <!--3.  修改终结点 由于我们使用的双工模式,但是默认的App.config里默认的wsHttpBinding是由于请求/答复和单项使用的bingding模式,        所以我们要修改成wsDualHttpBinding,只有这样才支持双工        并且还有修改contract,修改为WCF的服务接口类ICalculatorDuplex-->        <endpoint address ="" binding="wsDualHttpBinding" contract="Video2.WcfServiceLibrary2.ICalculatorDuplex">          <!--               Upon deployment, the following identity element should be removed or replaced to reflect the               identity under which the deployed service runs.  If removed, WCF will infer an appropriate identity               automatically.          -->          <identity>            <dns value="localhost"/>          </identity>        </endpoint>        <!-- Metadata Endpoints -->        <!-- The Metadata Exchange endpoint is used by the service to describe itself to clients. -->         <!-- This endpoint does not use a secure binding and should be secured or removed before deployment -->        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>      </service>    </services>    <behaviors>      <serviceBehaviors>        <!--4.  添加Behaviors的设置 name-->        <behavior name="Video2.WcfServiceLibrary2.Service1Behavior">          <!-- 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>  </system.serviceModel></configuration>


 

4. 我们Debug一下,可以发现启动的服务方法是带红色叹号的,这是因为我们还没有在客户端实现回调方法导致。

5. 添加一个控制台的客户端程序,来具体实现服务端回调的接口。在这个控制台项目中,来添加我们所创建的WCF服务;引用完成后,客户端就生成了这个WCF双工服务的代理类。

 

6. 在客户端项目中添加一个新类CallbackHandler来实现服务端定义的回调方法的接口

 

using System;using System.Collections.Generic;using System.Linq;using System.Text;using ConsoleApplication1.ServiceReference1;namespace ConsoleApplication1{    //在客户端实现服务端定义的回调方法接口,这是因为在引用WCF服务的时候会在客户端自动生成相应的客户端类    public class CallbackHandler:ICalculatorDuplexCallback    {        //实现接口        public void Equals(double result)        {            Console.WriteLine("Result({0})", result);        }        public void Equation(string eqn)        {            Console.WriteLine("Equation({0})", eqn);        }    }}


7. 在客户端的Program的Main方法中来进行相应的对WCF的使用:

 

using System;using System.Collections.Generic;using System.Linq;using System.Text;using System.ServiceModel;namespace ConsoleApplication1{    class Program    {        static void Main(string[] args)        {            //实例上下文的信息回调的类实例化传给双工服务,在实例化双工服务的类的时候,把我们回调的实例传给他,WCF服务端才能够知道我们客户端的方法真正如何实现的。            InstanceContext instanceContext = new InstanceContext(new CallbackHandler());            //创建代理类            ServiceReference1.CalculatorDuplexClient client = new ServiceReference1.CalculatorDuplexClient(instanceContext);            Console.WriteLine("Press <ENTER> to terminate client once the output is displayed.");            Console.WriteLine();            //Call the AddTo Service operation.            double value = 100.00D;            client.AddTo(value);            //Call the SubtractFrom service operation.            value = 50.00D;            client.SubtractFrom(value);            //Call the MultiplyBy service operation.            value = 17.65D;            client.DivideBy(value);            //Complete equation;            client.Clear();            Console.ReadLine();            //Closing the client gracefully closes the connection and cleans up resources            client.Close();        }    }}


8. 把启动项目设定为控制台应用程序后,运行即可看到结果。

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

 创建数据协定

面向服务的应用程序(例如Window Communication Foundation WCF应用程序)设计为与Microsoft平台和非Microsoft平台上的最大可能数量的客户端应用程序进行互操作。

为了获得自大可能的互操作性,建议使用DataContractAttribute和DataMemberAttribute属性对相应的类型进行标记,以创建数据协定。

数据协定是服务协定的一部分,用于描述服务操作交换的数据。(也就是调用服务时候所传递或返回的参数)

例子:

 

    [DataContract]    public class CompositeType    {        bool boolValue = true;        string stringValue = "Hello ";        [DataMember]        public bool BoolValue        {            get { return boolValue; }            set { boolValue = value; }        }        [DataMember]        public string StringValue        {            get { return stringValue; }            set { stringValue = value; }        }    }


注意:

1. 数据协定是可选的样式协定,除非显示应用数据协定属性,否则不会序列化任何类型或数据成员。也就是说如果不用[DataContract]标记的话,WCF是不会认为这是一个需要公开的类型,也就不会序列化,也就无法传递到其他位置使用。

2. 数据协定与托管代码的访问范围无关,可以对私有数据成员进行序列化,并将其发送到其他位置,以便可以公开访问他们。

3. WCF处理用于启用操作功能的基础SOAP消息的定义,并处理数据类型到消息正文的序列化和从消息正文进行的反序列化。数据类型一旦序列化,就无需在设计操作时考虑基础消息交换基础结构。也就是说开发者不用关心数据序列化和反序列化的过程,这些都有WCF自动完成。

4. 当DataContract无法满足自己的定义的时候,可以使用其他序列化机制。标准ISerializable,SerializableAttribute和IXmlSerializable机制都可用于数据类型到基础SOAP消息的序列化,这些消息可将数据类型从一个应用程序带到另一个应用程序。

 

使用前面的例子来修改一下:

        private void button1_Click(object sender, EventArgs e)        {            ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();            ServiceReference1.CompositeType c = new ServiceReference1.CompositeType();            c.BoolValue = true;            c.StringValue = "I am a CompositeType";            ServiceReference1.CompositeType c1 = new ServiceReference1.CompositeType();            c1 = client.GetDataUsingDataContract(c);            MessageBox.Show(c1.StringValue);        }


 

这样就调用了WCFService里的GetDatUsingDataContract方法,并且把在客户端实例化的WCF服务中定义的类CompositeType的c实例传递到WCF服务里去,得到了返回的类型c1。

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

Out和Ref参数

out关键字会导致参数通过引用来传递。这与ref关键字类似,不同之处在于ref要求变量在传递之前进行初始化。若要使用out参数,方法定义和调用方法都必须显示使用out关键字。

class OutExample{    static void Method(out int i)    {        i = 44;    }    static void Main()    {        int value;        Method(out value);        // value is now 44    }}


 

ref关键字会导致通过应用传递的参数,而不是值。通过的效果引用指向该参数的任何更改。方法反映在基础参数变量在被调用的方法上。引用参数的值始终是相同基础参数变量的值。

    class RefExample    {        static void Method(ref int i)        {            // Rest the mouse pointer over i to verify that it is an int.            // The following statement would cause a compiler error if i            // were boxed as an object.            i = i + 44;        }        static void Main()        {            int val = 1;            Method(ref val);            Console.WriteLine(val);            // Output: 45        }    }


 

大部分的情况下,可以使用in参数、out和ref参数。由于out和ref参数都指示数据是从操作返回的,类似如下的操作签名会指定需要请求/答复操作,即使操作签名返回void也是如此。

测试Demo:

1) 在WCF服务类库的IService1里添加一个OperationContract,注意所传递的参数里有ref和out类型的参数。

[OperationContract]        string GetOutRefData(int value, ref string strRef, out string strOut);


2) 在WCF服务的实现类Service1里实现接口中的服务定义

        public string GetOutRefData(int value, ref string strRef, out string strOut)        {            strRef += "---changed by wcf Method";            strOut = "from wcf method";            return string.Format("You entered:{0}", value);        }


 

3) 在WIndows窗体程序中来测试一下结果:

        private void button1_Click(object sender, EventArgs e)        {            //ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();            //ServiceReference1.CompositeType c = new ServiceReference1.CompositeType();            //c.BoolValue = true;            //c.StringValue = "I am a CompositeType";            //ServiceReference1.CompositeType c1 = new ServiceReference1.CompositeType();            //c1 = client.GetDataUsingDataContract(c);            //MessageBox.Show(c1.StringValue);            ServiceReference1.Service1Client client = new ServiceReference1.Service1Client();            string strRef = "ABC";            string strOut;            client.GetOutRefData(5, ref strRef, out strOut);            MessageBox.Show(strRef + "|||||||||||||" + strOut);        }


 

运行结果是:

 

 

这里要注意,如果服务协定是IsOneWay单项模式的话,是绝对不能用ref和out参数的,因为ref和out是会把改变的参数返回给调用该方法的类的相应对象,如果使用IsOneWay限制了无法返回,所以是绝对不能用的。

 

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