WCF 介绍(三)
来源:互联网 发布:linux重新启动命令 编辑:程序博客网 时间:2024/05/17 07:11
示例下载(Orcas 下编写)参阅: WCF 介绍(一) | WCF 介绍(二) | WCF 介绍(三) | WCF 介绍(四)
四、Service Contract编程模型
在(二)中,我以“SayHello”为例讲解了如何定义一个Service。其核心就是为接口或类施加ServiceContractAttribute,为方法施加OperationContractAttribute。在Service的方法中,可以接受多个参数,也可以有返回类型,只要这些数据类型能够被序列化。这样一种方式通常被称为本地对象,远程过程调用(local-object, Remoting-Procedure-Call)方式,它非常利于开发人员快速地进行Service的开发。在Service Contract编程模型中,还有一种方式是基于Message Contract的。服务的方法最多只能有一个参数,以及一个返回值,且它们的数据类型是通过Message Contract自定义的消息类型。在自定义消息中,可以为消息定义详细的Header和Body,使得对消息的交换更加灵活,也更利于对消息的控制。一个有趣的话题是当我们定义一个Service时,如果一个private方法被施加了OperationContractAttribute,那么对于客户端而言,这个方法是可以被调用的。这似乎与private对于对象封装的意义有矛盾。但是这样的规定是有其现实意义的,因为对于一个服务而言,服务端和客户端的需求往往会不一致。在服务端,该服务对象即使允许被远程调用,但本地调用却可能会因情况而异。如下面的服务定义:- [ServiceContract]
- public class BookTicket
- {
- [OperationContract]
- public bool Check(Ticket ticket)
- {
- bool flag;
- //logic to check whether the ticket is none;
- return flag;
- }
- [OperationContract]
- private bool Book(Ticket ticket)
- {
- //logic to book the ticket
- }
- }
[ServiceContract]public class BookTicket{ [OperationContract] public bool Check(Ticket ticket) { bool flag; //logic to check whether the ticket is none; return flag; } [OperationContract] private bool Book(Ticket ticket) { //logic to book the ticket }}在服务类BookTicket中,方法Check和Book都是服务方法,但后者被定义成为private方法。为什么呢?因为对于客户而言,首先会检查是否还有电影票,然而再预定该电影票。也就是说这两项功能都是面向客户的服务,会被远程调用。对于Check方法,除了远程客户会调用该方法之外,还有可能被查询电影票、预定电影票、出售电影票等业务逻辑所调用。而Book方法,则只针对远程客户,只可能被远程调用。为了保证该方法的安全,将其设置为private,使得本地对象不至于调用它。因此在WCF中,一个方法是否应该被设置为服务方法,以及应该设置为public还是private,都需要根据具体的业务逻辑来判断。如果涉及到私有的服务方法较多,一种好的方法是利用设计模式的Façade模式,将这些方法组合起来。而这些方法的真实逻辑,可能会散放到各自的本地对象中,对于这些本地对象,也可以给与一定的访问限制,如下面的代码所示:
- internal class BusinessObjA
- {
- internal void FooA(){}
- }
- internal class BusinessObjB
- {
- internal void FooB(){}
- }
- internal class BusinessObjC
- {
- internal void FooC(){}
- }
- [ServiceContract]
- internal class Façade
- {
- private BusinessObjA objA = new BusinessObjA();
- private BusinessObjB objB = new BusinessObjB();
- private BusinessObjC objC = new BusinessObjC();
- [OperationContract]
- private void SvcA()
- {
- objA.FooA();
- }
- [OperationContract]
- private void SvcB()
- {
- objB.FooB();
- }
- [OperationContract]
- private void SvcC()
- {
- objC.FooC();
- }
- }
internal class BusinessObjA{ internal void FooA(){}}internal class BusinessObjB{ internal void FooB(){}}internal class BusinessObjC{ internal void FooC(){}}[ServiceContract]internal class Façade{ private BusinessObjA objA = new BusinessObjA(); private BusinessObjB objB = new BusinessObjB(); private BusinessObjC objC = new BusinessObjC(); [OperationContract] private void SvcA() { objA.FooA(); } [OperationContract] private void SvcB() { objB.FooB(); } [OperationContract] private void SvcC() { objC.FooC(); }}方法FooA,FooB,FooC作为internal方法,拒绝被程序集外的本地对象调用,但SvcA,SvcB和SvcC方法,却可以被远程对象所调用。我们甚至可以将BusinessObjA,BusinessObjB等类定义为Façade类的嵌套类。采用这样的方法,有利于这些特殊的服务方法,被远程客户更方便的调用。定义一个Service,最常见的还是显式地将接口定义为Service。这样的方式使得服务的定义更加灵活,这一点,我已在(二)中有过描述。当然,采用这种方式,就不存在前面所述的私有方法成为服务方法的形式了,因为在一个接口定义中,所有方法都是public的。另外一个话题是有关“服务接口的继承”。一个被标记了[ServiceContract]的接口,在其继承链上,允许具有多个同样标记了[ServiceContract]的接口。对接口内定义的OperationContract方法,则是根据“聚合”的原则,如下的代码所示:
- [ServiceContract]
- public interface IOne
- {
- [OperationContract(IsOneWay=true)]
- void A();
- }
- [ServiceContract]
- public interface ITwo
- {
- [OperationContract]
- void B();
- }
- [ServiceContract]
- public interface IOneTwo : IOne, ITwo
- {
- [OperationContract]
- void C();
- }
[ServiceContract]public interface IOne{ [OperationContract(IsOneWay=true)] void A();}[ServiceContract]public interface ITwo{ [OperationContract] void B();}[ServiceContract]public interface IOneTwo : IOne, ITwo{ [OperationContract] void C();}在这个例子中,接口IOneTwo继承了接口IOne和ITwo。此时服务IOneTwo暴露的服务方法应该为方法A、B和C。然而当我们采用Duplex消息交换模式(文章后面会详细介绍Duplex)时,对于服务接口的回调接口在接口继承上有一定的限制。WCF要求服务接口IB在继承另一个服务接口IA时,IB的回调接口IBCallBack必须同时继承IACallBack,否则会抛出InvalidContractException异常。正确的定义如下所示:
- [ServiceContract(CallbackContract = IACallback)]
- interface IA {}
- interface IACallback {}
- [ServiceContract(CallbackContract = IBCallback)]
- interface IB : IA {}
- interface IBCallback : IACallback {}
[ServiceContract(CallbackContract = IACallback)]interface IA {}interface IACallback {}[ServiceContract(CallbackContract = IBCallback)]interface IB : IA {}interface IBCallback : IACallback {}
五、消息交换模式(Message Exchange Patterns,MEPS)
在WCF中,服务端与客户端之间消息的交换共有三种模式:Request/Reply,One-Way,Duplex。1、Request/Reply这是默认的一种消息交换模式,客户端调用服务方法发出请求(Request),服务端收到请求后,进行相应的操作,然后返回一个结果值(Reply)。如果没有其它特别的设置,一个方法如果标记了OperationContract,则该方法的消息交换模式就是采用的Request/Reply方式,即使它的返回值是void。当然,我们也可以将IsOneWay设置为false,这也是默认的设置。如下的代码所示:- [ServiceContract]
- public interface ICalculator
- {
- [OperationContract]
- int Add(int a, int b);
- [OperationContract]
- int Subtract(int a, int b);
- }
[ServiceContract]public interface ICalculator{ [OperationContract] int Add(int a, int b); [OperationContract] int Subtract(int a, int b);}2、One-Way如果消息交换模式为One-Way,则表明客户端与服务端之间只有请求,没有响应。即使响应信息被发出,该响应信息也会被忽略。这种方式类似于消息的通知或者广播。当一个服务方法被设置为One-Way时,如果该方法有返回值,会抛出InvalidOperationException异常。要将服务方法设置为One-Way非常简单,只需要将OperationContractAttribute的属性IsOneWay设置为true就可以了,如下的代码所示:
- public class Radio
- {
- [OperationContract(IsOneWay=true)]
- private void BroadCast();
- }
public class Radio{ [OperationContract(IsOneWay=true)] private void BroadCast();}3、DuplexDuplex消息交换模式具有客户端与服务端双向通信的功能,同时它的实现还可以使消息交换具有异步回调的作用。要实现消息交换的Duplex,相对比较复杂。它需要定义两个接口,其中服务接口用于客户端向服务端发送消息,而回调接口则是从服务端返回消息给客户端,它是通过回调的方式来完成的。接口定义如下:服务接口:
- [ServiceContract(SessionMode=SessionMode.Allowed, 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);
- }
[ServiceContract(SessionMode=SessionMode.Allowed, 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); }回调接口:
- public interface ICalculatorDuplexCallback
- {
- [OperationContract(IsOneWay=true)]
- void Equals(double result);
- [OperationContract(IsOneWay=true)]
- void Equation(string equation);
- }
public interface ICalculatorDuplexCallback { [OperationContract(IsOneWay=true)] void Equals(double result); [OperationContract(IsOneWay=true)] void Equation(string equation); }注意在接口定义中,每个服务方法的消息转换模式均设置为One-Way。此外,回调接口是被本地调用,因此不需要定义[ServiceContract]。在服务接口中,需要设置ServiceContractAttribute的CallbackContract属性,使其指向回调接口的类型type。对于实现服务的类,实例化模式(InstanceContextMode)究竟是采用PerSession方式,还是PerCall方式,应根据该服务对象是否需要保存状态来决定。如果是PerSession,则服务对象的生命周期是存活于一个会话期间。而PerCall方式下,服务对象是在方法被调用时创建,结束后即被销毁。然而在Duplex模式下,不能使用Single方式,否则会导致异常抛出。本例的实现如下:
- [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
- public class CalculatorService : ICalculatorDuplex
- {
- double result;
- string equation;
- ICalculatorDuplexCallback callback;
- public CalculatorService()
- {
- result = 0.0;
- equation = result.ToString();
- callback = OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();
- }
- #region ICalculatorDuplex Members
- public void Clear()
- {
- equation += "=" + result.ToString();
- callback.Equation(equation);
- }
- 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);
- }
- #endregion
- }
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)] public class CalculatorService : ICalculatorDuplex { double result; string equation; ICalculatorDuplexCallback callback; public CalculatorService() { result = 0.0; equation = result.ToString(); callback = OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>(); } #region ICalculatorDuplex Members public void Clear() { equation += "=" + result.ToString(); callback.Equation(equation); } 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); } #endregion }在类CalculatorService中,回调接口对象callback通过OperationContext.Current.GetCallbackChannel<>()获取。然后在服务方法例如AddTo()中,通过调用该回调对象的方法,完成服务端向客户端返回消息的功能。在使用Duplex时,Contract使用的Binding应该是系统提供的WSDualHttpBinding,如果使用BasicHttpBinding,会出现错误。因此WCF Service Host程序配置文件应该如下所示:
- <?xml version="1.0" encoding="utf-8" ?>
- <configuration>
- <!-- 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>
- <service name="WcfServiceLibrary1.CalculatorService" behaviorConfiguration="WcfServiceLibrary1.Service1Behavior">
- <host>
- <baseAddresses>
- <add baseAddress = "http:localhost:8080/Service1" />
- </baseAddresses>
- </host>
- <!-- Service Endpoints -->
- <!-- Unless fully qualified, address is relative to base address supplied above -->
- <endpoint address ="" binding="wsDualHttpBinding" contract="WcfServiceLibrary1.ICalculatorDuplex" />
- </service>
- </services>
- <behaviors>
- <serviceBehaviors>
- <behavior name="WcfServiceLibrary1.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>
<?xml version="1.0" encoding="utf-8" ?><configuration> <!-- 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> <service name="WcfServiceLibrary1.CalculatorService" behaviorConfiguration="WcfServiceLibrary1.Service1Behavior"> <host> <baseAddresses> <add baseAddress = "http:localhost:8080/Service1" /> </baseAddresses> </host> <!-- Service Endpoints --> <!-- Unless fully qualified, address is relative to base address supplied above --> <endpoint address ="" binding="wsDualHttpBinding" contract="WcfServiceLibrary1.ICalculatorDuplex" /> </service> </services> <behaviors> <serviceBehaviors> <behavior name="WcfServiceLibrary1.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>使用命令svcutil.exe http://localhost:8080/Service1?wsdl生成客户端代理类和配置文件,配置文件内容类似:
- <?xml version="1.0" encoding="utf-8"?>
- <configuration>
- <system.serviceModel>
- <client>
- <endpoint address="http:localhost:8080/Service1"
- binding="wsDualHttpBinding"
- contract="ICalculatorDuplex">
- <identity>
- <userPrincipalName value="allisok-PC/allisok" />
- </identity>
- </endpoint>
- </client>
- </system.serviceModel>
- </configuration>
<?xml version="1.0" encoding="utf-8"?><configuration> <system.serviceModel> <client> <endpoint address="http:localhost:8080/Service1" binding="wsDualHttpBinding" contract="ICalculatorDuplex"> <identity> <userPrincipalName value="allisok-PC/allisok" /> </identity> </endpoint> </client> </system.serviceModel></configuration>当服务端将信息回送到客户端后,对消息的处理是由回调对象来处理的,所以回调对象的实现应该是在客户端完成,如下所示的代码应该是在客户端中:
- public class CallbackHandler : ICalculatorDuplexCallback
- {
- #region ICalculatorDuplexCallback Members
- void ICalculatorDuplexCallback.Equals(double result)
- {
- Console.WriteLine("Result({0})", result);
- }
- void ICalculatorDuplexCallback.Equation(string equation)
- {
- Console.WriteLine("Equation({0})", equation);
- }
- #endregion
- }
public class CallbackHandler : ICalculatorDuplexCallback { #region ICalculatorDuplexCallback Members void ICalculatorDuplexCallback.Equals(double result) { Console.WriteLine("Result({0})", result); } void ICalculatorDuplexCallback.Equation(string equation) { Console.WriteLine("Equation({0})", equation); } #endregion }客户端调用服务对象相应的为:
- class ClientApp
- {
- static void Main(string[] args)
- {
- InstanceContext instanceContext = new InstanceContext(new CallbackHandler());
- CalculatorDuplexClient client = new CalculatorDuplexClient(instanceContext);
- // 使用 "client" 变量在服务上调用操作。
- double value;
- value = 100;
- client.AddTo(value);
- value = 50;
- client.SubtractFrom(value);
- client.Clear();
- // 始终关闭客户端。
- //client.Close();
- Console.ReadLine();
- }
- }
class ClientApp { static void Main(string[] args) { InstanceContext instanceContext = new InstanceContext(new CallbackHandler()); CalculatorDuplexClient client = new CalculatorDuplexClient(instanceContext); // 使用 "client" 变量在服务上调用操作。 double value; value = 100; client.AddTo(value); value = 50; client.SubtractFrom(value); client.Clear(); // 始终关闭客户端。 //client.Close(); Console.ReadLine(); } }注意在Duplex中,会话创建的时机并不是客户端创建Proxy实例的时候,而是当服务对象的方法被第一次调用时,会话方才建立,此时服务对象会在方法调用之前被实例化,直至会话结束,服务对象都是存在的。
- WCF 介绍(三)
- WCF介绍
- WCF 介绍(一)
- WCF 介绍(二)
- WCF 介绍(四)
- wcf的简单介绍
- wcf契约具体介绍
- WCF简要介绍
- WCF简要介绍
- WCF基本介绍
- WCF详细介绍
- .net wcf 概念介绍
- (三)、WCF绑定
- 学习WCF(三)
- WCF学习(三)--实例
- WCF系列(三) -- WCF配置文件注释
- WCF入门教程三[WCF的宿主]
- WCF入门教程三[WCF的宿主]
- sap组织结构的学习
- Subversion1.5发布说明
- 在用户态进行虚拟空间地址向物理空间地址的转换
- WCF 介绍(二)
- 非托管资源的回收问题
- WCF 介绍(三)
- WCF 介绍(四)
- C#实现后台大量运算并刷新控件的方法
- Ajax各类Http请求状态(status,state)及其含义
- 利用Token防止重复提交(Struts框架)
- 一个程序员的爱情自白
- Essential SQLAlchemy
- javascript + asp 实现javascript跨域读取xml文件
- 使用 Web 标准生成 ASP.NET 2.0 Web 站点