WCF学习笔记(第二天,2.设计和实现服务协定)

  收听本次课程需具备的条件
  1.熟悉Web Service编程
  2.熟悉Visual Studio 2005/2008
  3.熟悉分布式应用程序开发
  4.跟我一起从零开始学WCF(1)—WCF概述

  本次课程内容包括
  1. 创建服务协定
  2. 数据协定
  3. Out和Ref参数

  创建服务协定—WCF术语
  1.消息
  – 消息是一个独立的数据单元,它可能由几个部分组成,包括消息正文和消息头。
  2 .服务
  – 服务是一个构造,它公开一个或多个终结点,其中每个终结点都公开一个或多个服务操作。
  3. 终结点
  – 终结点是用来发送或接收消息(或执行这两种操作)的构造。终结点包括一个定义消息可以发送到的目的地的位置(地址)、一个描述消息应如何发送的通信机制规范(绑定)以及对于可以在该位置发送或接收(或两者皆可)的一组消息的定义(服务协定)— 该定义还描述了可以发送何种消息。
  – WCF 服务作为一个终结点集合向外界公开。

  创建服务协定
  1. 类或接口都可以定义服务协定
  2.建议使用接口,因为接口可以直接对服务协定建模
  服务协定接口具有托管接口的所有优点:
  – 服务协定接口可以扩展任何数量的其他服务协定接口。
  – 一个类可以通过实现服务协定接口来实现任意数量的服务协定。
  – 可以通过更改接口实现来修改服务协定的实现,而让服务协定保持不变。
  – 可以通过实现旧接口和新接口来确定服务的版本。老客户端连接到原始版本,而新客户端则可以连接到较新的版本。


  创建服务协定
  1. 定义服务协定
  – 在类或接口上使用ServiceContractAttribute 属性标记
  2.定义服务操作
  – 在方法上使用OperationContractAttribute 属性对其进行标记
  3.参数和返回值
  – 每个操作都有一个返回值和一个参数,即使它们为void。可以使用局部方法将对对象的引用从一个对象传递到另一个对象,但与局部方法不同的是,服务操作不会传递对对象的引用, 它们传递的只是对象的副本。
  – 这一点很重要,这是因为参数或返回值中使用的每个类型都必须是可序列化的,换言之,该类型的对象必须能够转换为字节流,并能够从字节流转换为对象。
  – 默认情况下,基元类型是可序列化的,.NET Framework 中的很多类型都是可序列化的。

  创建服务协定-服务操作的消息模式1
  请求/答复
  – 通过请求/答复模式,请求发送方(客户端应用程序)将接收与请求相关的答复。这是默认的模式,因为它既支持传入操作(一个或多个参数传递到该操作中),也支持返回操作(该操作将一个或多个输出值传回给调用方)
  [OperationContract]
  – 请注意,除非指定其他基础消息模式,否则,即使服务操作返回
  string Hello(string greeting);
  void(在Visual Basic 中为Nothing),也属于请求/答复消息交换。
  – 操作的结果是:除非客户端异步调用操作,否则客户端将停止处理,直到收到返回消息,即使该消息正常情况下为空时也是如此。

  创建服务协定-服务操作的消息模式1
  缺点
  – 如果执行操作需要很长的时间,则会降低客户端性能和响应能力
  优点
  – 响应消息中可返回SOAP 错误,这表明可能在通信或处理中发生了一些与服务有关的错误状况


  创建服务协定-服务操作的消息模式2
  单向
  – 如果WCF 服务应用程序的客户端不必等待操作完成,并且不处理SOAP 错误,则该操作可以指定单向消息模式。
  – 单向操作是客户端调用操作并在WCF 将消息写入网络后继续进行处理的操作。通常这意味着,除非在出站消息中发送的数据极其庞大,否则客户端几乎立即继续运行(除非发送数据时出错)。此种类型的消息交换模式支持从客户端到服务应用程序的类似于事件的行为。
  – 若要为返回void 的操作指定单向消息交换,请将IsOneWay 属性
  设置为true,默认为false.
  [OperationContract(IsOneWay=true)]
  void Hello(string greeting);

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


  创建服务协定-服务操作的消息模式3
  双工
  – 双工模式的特点是,无论使用单向消息发送还是请求/答复消息发送方式,服务和客户端均能够独立地向对方发送消息。对于必须直接与客户端通信或向消息交换的任意一方提供异步体验(包括类似于事件的行为)的服务来说,这种双向通信形式非常有用
  – 由于存在与客户端通信的附加机制,双向模式比请求/答复或单向模式要略为复杂
  – 若要设计双工协定,还必须设计回调协定,并将该回调协定的类型分配给标记服务协定的ServiceContractAttribute 属性(attribute)的CallbackContract 属性(property)。
  – 若要实现双工模式,您必须创建第二个接口,该接口包含在客户端调用的方法声明


  创建服务协定-服务操作的消息模式3

[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);
}

  演示1
  创建不同消息模式的WCF服务
  1请求/答复,2单向,3双工

  请求/答复,单向的代码如下:

  IService1.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WcfServiceLibrary1
{
    // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的接口名“IService1”。
    [ServiceContract]
    public interface IService1
    {
        [OperationContract]
        string GetData(int value, ref string strRef, out string strOut);

        [OperationContract]
        CompositeType GetDataUsingDataContract(CompositeType composite);

        // TODO: 在此添加您的服务操作
        [OperationContract(IsOneWay=true)]
        void TextMethod(string strdata);
    }

    // 使用下面示例中说明的数据协定将复合类型添加到服务操作
    [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; }
        }
    }
}

  Service1.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WcfServiceLibrary1
{
    // 注意: 使用“重构”菜单上的“重命名”命令,可以同时更改代码和配置文件中的类名“Service1”。
    public class Service1 : IService1
    {
        public string GetData(int value, ref string strRef, out string strOut)
        {
            //System.Threading.Thread.Sleep(1000);
            strRef += "-----changed by wcf method";

            strOut = "from wcf method";
            return string.Format("You entered: {0}", value);
        }
        public CompositeType GetDataUsingDataContract(CompositeType composite)
        {
            if (composite == null)
            {
                throw new ArgumentNullException("composite");
            }
            if (composite.BoolValue)
            {
                composite.StringValue += "Suffix";
            }
            return composite;
        }

        public void TextMethod(string strdata)
        {
            System.Threading.Thread.Sleep(1000);
        }
    }
}

  Form1.cs:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            ServiceReference1.Service1Client sc = new ServiceReference1.Service1Client();
            ////string strOutput = sc.GetData(5);

            ////MessageBox.Show(strOutput);

            //sc.TextMethod("Hello,WCF");

            //MessageBox.Show("单向,没有等待服务器端的执行");




            //ServiceReference1.CompositeType c = new ServiceReference1.CompositeType();   //演示自定义参数
            //c.BoolValue = true;
            //c.StringValue = "Hello,WCF";

            //ServiceReference1.CompositeType c1;


            //c1 = sc.GetDataUsingDataContract(c);

            //MessageBox.Show(c1.StringValue);





            //strRef += "-----changed by wcf method";     //这是服务器端的方法

            //strOut = "from wcf method";

            string strRef = "ABC";
            string strOut = "DEF";                       //传出参数,“EDF”将不在结果中显示
            sc.GetData(5, ref strRef, out strOut);

            MessageBox.Show(strRef += "------" + strOut);

            //运行结果:ABC-----change by wcf method------from wcf method
            

        }
    }
}

  如果是在单向下直接运行,则出现下面的错误:

 

  双工的代码如下:

  IService1.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WcfServiceLibrary1
{
    [ServiceContract(Namespace = "http://Microsoft.ServiceModel.Samples", SessionMode = SessionMode.Required,
            CallbackContract = typeof(ICalculatorDuplexCallback))]   //告诉服务器端,客户端方法的接口形式
    public interface ICalculatorDuplex
    {
        [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);
    }

    public interface ICalculatorDuplexCallback   //客户端方法的接口,不是服务,所以不用添加ServiceContract。只是申明客户端回调方法
    {
        [OperationContract(IsOneWay = true)]     //需要添加OperationContract属性,让服务端去调用这个方法
        void Equals(double result);
        [OperationContract(IsOneWay = true)]
        void Equation(string eqn);
    }
}

  Service1.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;

namespace WcfServiceLibrary1
{
        [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]  //保证是同一个实例的模式,为每一个回话创建一个实例
    public class CalculatorService : ICalculatorDuplex
    {
        double result;
        string equation;
        ICalculatorDuplexCallback callback = null;

        public CalculatorService()    //构造方法获得上下文中回调的接口
        {
            result = 0.0D;
            equation = result.ToString();
            callback = OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();
        }

        public void Clear()
        {
            callback.Equation(equation + " = " + result.ToString());
            result = 0.0D;
            equation = result.ToString();
        }

        public void AddTo(double n)
        {
            result += n;
            equation += " + " + n.ToString();
            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);
        }
    }
}

  还需要做的是改变App.coning的配置。因为wsDualHttpBinding不支持回调,所以将 <endpoint address="" binding="wsDualHttpBinding" contract="WcfServiceLibrary2.ICalculatorDuplex">里面的wsDualHttpBinding改成wsDualHttpBinding,这种方式支持回调。协定的名字已经改变,所以App.coning的配置里面协定的名字也要改变。将<service name="WcfServiceLibrary1.Service1">中的Service1改成CalculatorService,将<endpoint address ="" binding="wsDualHttpBinding" contract="WcfServiceLibrary1.IService1">中的IService1改成ICalculatorDuplex直接运行程序,因为是双工方法,所以显示红色感叹号。

  添加控制台程序,并且引用WCF服务。

  Program.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;                     //添加的命名空间==》ICalculatorDuplexCallback
using ConsoleApplication1.ServiceReference1;   //添加的命名空间==》InstanceContext

namespace ConsoleApplication1
{
    class Program
    {
        public class CallbackHandler : ICalculatorDuplexCallback
        {
            public void Equals(double result)
            {
                Console.WriteLine("Result({0})", result);
            }

            public void Equation(string eqn)
            {
                Console.WriteLine("Equation({0})", eqn);
            }
        }

        static void Main(string[] args)
        {
            // Construct InstanceContext to handle messages on callback interface
            InstanceContext instanceContext = new InstanceContext(new CallbackHandler());   //实例化回调的类,传给双工的服务

            // Create a client
            ServiceReference1.CalculatorDuplexClient client = new ServiceReference1.CalculatorDuplexClient(instanceContext);


            WSDualHttpBinding ws = (WSDualHttpBinding)client.Endpoint.Binding;
            ws.ClientBaseAddress = new Uri("http://localhost:30002/");    //设定绑定的地址


            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.MultiplyBy(value);

            // Call the DivideBy service operation.
            value = 2.00D;
            client.DivideBy(value);

            // Complete equation
            client.Clear();

            Console.ReadLine();

            //Closing the client gracefully closes the connection and cleans up resources
            client.Close();
        }
    }
}

  运行代码出错:HTTP 无法注册,另一应用程序正在使用 TCP 端口 80。原因,主要是因为默认80端口已经被其他程序占用。所以WCF服务在设置默认的绑定结点时会抛异常。在客户端中代码中加入WSDualHttpBinding ws = (WSDualHttpBinding)client.Endpoint.Binding;ws.ClientBaseAddress = new Uri("http://localhost:30001/");    //设定绑定的地址
或者在客户端app.confing的binding节点中加入clientBaseAddress="http://localhost:30001/"

  创建数据协定
  1.面向服务的应用程序(例如Windows Communication Foundation(WCF) 应用程序)设计为与Microsoft 平台和非 Microsoft 平台上的最大可能数量的客户端应用程序进行互操作。
  2.为了获得最大可能的互操作性,建议您使用DataContractAttribute 和DataMemberAttribute 属性对您的类型进行标记,以创建数据协定。
  3.数据协定是服务协定的一部分,用于描述您的服务操作交换的数据。

  入门级代码:

[DataContract]   //修饰数据类型
public class CompositeType
{
bool boolValue = true;
string stringValue = "Hello ";
[DataMember]   //修饰属性
public bool BoolValue
{
get { return boolValue; }
set { boolValue = value; }
}
}

  创建数据协定
  1.数据协定是可选的样式协定:除非您显式应用数据协定属性,否则不会序列化任何类型或数据成员
  2.数据协定与托管代码的访问范围无关:可以对私有数据成员进行序列化,并将其发送到其他位置,以便可以公开访问它们
  3.WCF 处理用于启用操作功能的基础 SOAP 消息的定义,并处理数据类型到消息正文的序列化和从消息正文进行的反序列化。数据类型一旦序列化,您就无需在设计操作时考虑基础消息交换基础结构
  4.可以使用其他序列化机制。标准ISerializable, SerializableAttribute和IXmlSerializable 机制都可用于处理数据类型到基础SOAP 消息的序列化,这些消息可将数据类型从一个应用程序带到另一个应用程序

  Out和Ref参数
  1.大部分情况下,您可以使用in 参数(Visual Basic 中为ByVal)、out 和 ref 参数(Visual Basic 中为 ByRef)。由于out 和 ref 参数都指示数据是从操作返回的,类似如下的操作签名会指定需要请求/答复操作,即使操作签名返回void 也是如此

[ServiceContract]
public interface IMyContract
{
[OperationContract]
void PopulateData(ref CustomDataType data);
}

  2.使用out或者ref参数要求操作具有基础的响应消息,才可以将已修改的对象传回。如果操作是单向操作,则将在运行时引发InvalidOperationException 异常

原文地址:https://www.cnblogs.com/yangwujun/p/2837963.html