惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

罗磊的独立博客
SecWiki News
SecWiki News
酷 壳 – CoolShell
酷 壳 – CoolShell
爱范儿
爱范儿
量子位
M
MIT News - Artificial intelligence
GbyAI
GbyAI
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
TaoSecurity Blog
TaoSecurity Blog
博客园 - 【当耐特】
H
Heimdal Security Blog
腾讯CDC
The Last Watchdog
The Last Watchdog
Security Archives - TechRepublic
Security Archives - TechRepublic
Hacker News: Ask HN
Hacker News: Ask HN
S
Schneier on Security
Microsoft Security Blog
Microsoft Security Blog
WordPress大学
WordPress大学
博客园 - 司徒正美
Recent Commits to openclaw:main
Recent Commits to openclaw:main
C
Cybersecurity and Infrastructure Security Agency CISA
S
SegmentFault 最新的问题
大猫的无限游戏
大猫的无限游戏
Application and Cybersecurity Blog
Application and Cybersecurity Blog
F
Full Disclosure
有赞技术团队
有赞技术团队
T
Tailwind CSS Blog
Engineering at Meta
Engineering at Meta
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Threatpost
月光博客
月光博客
A
Arctic Wolf
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
雷峰网
雷峰网
T
Troy Hunt's Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
D
DataBreaches.Net
O
OpenAI News
L
LINUX DO - 最新话题
宝玉的分享
宝玉的分享
小众软件
小众软件
V
Vulnerabilities – Threatpost
A
About on SuperTechFans
人人都是产品经理
人人都是产品经理
T
The Exploit Database - CXSecurity.com
Martin Fowler
Martin Fowler
美团技术团队
P
Privacy International News Feed

博客园 - Ticky

关于WCF的“调用方未由服务进行身份验证”的另一解决方法 Process.Start触发Enviroment的改变 [转]ASP.NET页面事件:顺序与回传详解 [转]Dynamic ListView LayoutTemplate [转]多层C/S系统及其在PB中的应用 .NET强命名设置随笔 - Ticky - 博客园 [转载]SQL Server阻塞详解 Chrome发布了,感受新体验 绝对经典的十个故事 SQL SERVER的一些常用查询语句收集 链接服务器的服务器连接问题 存储过程参数的时间默认值解决方法 别把捐款与善心划等号 [转]ORM的介绍 工作随记 [转]Windows Communication Foundation介绍(四) [转]Windows Communication Foundation介绍(二) [转]Windows Communication Foundation介绍(一) Microsoft OneNote 2007的体验
[转]Windows Communication Foundation介绍(三)
Ticky · 2008-03-31 · via 博客园 - Ticky

示例下载(Orcas 下编写)

四、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对于对象封装的意义有矛盾。但是这样的规定是有其现实意义的,因为对于一个服务而言,服务端和客户端的需求往往会不一致。在服务端,该服务对象即使允许被远程调用,但本地调用却可能会因情况而异。如下面的服务定义:

  1. [ServiceContract]   
  2. public class BookTicket   
  3. {   
  4.    [OperationContract]   
  5.    public bool Check(Ticket ticket)   
  6.    {   
  7.       bool flag;   
  8.         
  9.       return flag;   
  10.    }   
  11.    [OperationContract]   
  12.    private bool Book(Ticket ticket)   
  13.    {   
  14.        
  15.    }   
  16. }  

在服务类BookTicket中,方法Check和Book都是服务方法,但后者被定义成为private方法。为什么呢?因为对于客户而言,首先会检查是否还有电影票,然而再预定该电影票。也就是说这两项功能都是面向客户的服务,会被远程调用。对于Check方法,除了远程客户会调用该方法之外,还有可能被查询电影票、预定电影票、出售电影票等业务逻辑所调用。而Book方法,则只针对远程客户,只可能被远程调用。为了保证该方法的安全,将其设置为private,使得本地对象不至于调用它。

因此在WCF中,一个方法是否应该被设置为服务方法,以及应该设置为public还是private,都需要根据具体的业务逻辑来判断。如果涉及到私有的服务方法较多,一种好的方法是利用设计模式的Façade模式,将这些方法组合起来。而这些方法的真实逻辑,可能会散放到各自的本地对象中,对于这些本地对象,也可以给与一定的访问限制,如下面的代码所示:

  1. internal class BusinessObjA   
  2. {   
  3.    internal void FooA(){}   
  4. }   
  5. internal class BusinessObjB   
  6. {   
  7.    internal void FooB(){}   
  8. }   
  9. internal class BusinessObjC   
  10. {   
  11.    internal void FooC(){}   
  12. }   
  13. [ServiceContract]   
  14. internal class Façade   
  15. {   
  16.    private BusinessObjA objA = new BusinessObjA();   
  17.    private BusinessObjB objB = new BusinessObjB();   
  18.    private BusinessObjC objC = new BusinessObjC();   
  19.    [OperationContract]   
  20.    private void SvcA()   
  21.    {   
  22.       objA.FooA();   
  23.    }   
  24.  [OperationContract]   
  25.    private void SvcB()   
  26.    {   
  27.       objB.FooB();   
  28.    }   
  29.    [OperationContract]   
  30.    private void SvcC()   
  31.    {   
  32.       objC.FooC();   
  33.    }   
  34. }  

方法FooA,FooB,FooC作为internal方法,拒绝被程序集外的本地对象调用,但SvcA,SvcB和SvcC方法,却可以被远程对象所调用。我们甚至可以将BusinessObjA,BusinessObjB等类定义为Façade类的嵌套类。采用这样的方法,有利于这些特殊的服务方法,被远程客户更方便的调用。

定义一个Service,最常见的还是显式地将接口定义为Service。这样的方式使得服务的定义更加灵活,这一点,我已在(二)中有过描述。当然,采用这种方式,就不存在前面所述的私有方法成为服务方法的形式了,因为在一个接口定义中,所有方法都是public的。

另外一个话题是有关“服务接口的继承”。一个被标记了[ServiceContract]的接口,在其继承链上,允许具有多个同样标记了[ServiceContract]的接口。对接口内定义的OperationContract方法,则是根据“聚合”的原则,如下的代码所示:

  1. [ServiceContract]   
  2. public interface IOne   
  3. {   
  4.    [OperationContract(IsOneWay=true)]   
  5.    void A();   
  6. }   
  7. [ServiceContract]   
  8. public interface ITwo   
  9. {   
  10.    [OperationContract]   
  11.    void B();   
  12. }   
  13. [ServiceContract]   
  14. public interface IOneTwo : IOne, ITwo   
  15. {   
  16.    [OperationContract]   
  17.    void C();   
  18. }  

在这个例子中,接口IOneTwo继承了接口IOne和ITwo。此时服务IOneTwo暴露的服务方法应该为方法A、B和C。

然而当我们采用Duplex消息交换模式(文章后面会详细介绍Duplex)时,对于服务接口的回调接口在接口继承上有一定的限制。WCF要求服务接口IB在继承另一个服务接口IA时,IB的回调接口IBCallBack必须同时继承IACallBack,否则会抛出InvalidContractException异常。正确的定义如下所示:

  1. [ServiceContract(CallbackContract = IACallback)]   
  2. interface IA {}   
  3. interface IACallback {}   
  4.   
  5. [ServiceContract(CallbackContract = IBCallback)]   
  6. interface IB : IA {}   
  7. interface IBCallback : IACallback {}  

五、消息交换模式(Message Exchange Patterns,MEPS)

在WCF中,服务端与客户端之间消息的交换共有三种模式:Request/Reply,One-Way,Duplex。

1、Request/Reply
这是默认的一种消息交换模式,客户端调用服务方法发出请求(Request),服务端收到请求后,进行相应的操作,然后返回一个结果值(Reply)。

如果没有其它特别的设置,一个方法如果标记了OperationContract,则该方法的消息交换模式就是采用的Request/Reply方式,即使它的返回值是void。当然,我们也可以将IsOneWay设置为false,这也是默认的设置。如下的代码所示:

  1. [ServiceContract]   
  2. public interface ICalculator   
  3. {   
  4.    [OperationContract]   
  5.    int Add(int a, int b);   
  6.   
  7.    [OperationContract]   
  8.    int Subtract(int a, int b);   
  9. }  

2、One-Way
如果消息交换模式为One-Way,则表明客户端与服务端之间只有请求,没有响应。即使响应信息被发出,该响应信息也会被忽略。这种方式类似于消息的通知或者广播。当一个服务方法被设置为One-Way时,如果该方法有返回值,会抛出InvalidOperationException异常。

要将服务方法设置为One-Way非常简单,只需要将OperationContractAttribute的属性IsOneWay设置为true就可以了,如下的代码所示:

  1. public class Radio   
  2. {   
  3.    [OperationContract(IsOneWay=true)]   
  4.    private void BroadCast();   
  5. }  

3、Duplex
Duplex消息交换模式具有客户端与服务端双向通信的功能,同时它的实现还可以使消息交换具有异步回调的作用。

要实现消息交换的Duplex,相对比较复杂。它需要定义两个接口,其中服务接口用于客户端向服务端发送消息,而回调接口则是从服务端返回消息给客户端,它是通过回调的方式来完成的。接口定义如下:
服务接口:

  1. [ServiceContract(SessionMode=SessionMode.Allowed, CallbackContract=typeof(ICalculatorDuplexCallback))]   
  2. public interface ICalculatorDuplex   
  3. {   
  4.   
  5.     [OperationContract(IsOneWay=true)]   
  6.     void Clear();   
  7.   
  8.     [OperationContract(IsOneWay=true)]   
  9.     void AddTo(double n);   
  10.   
  11.     [OperationContract(IsOneWay = true)]   
  12.     void SubtractFrom(double n);   
  13. }  

回调接口:

  1. public interface ICalculatorDuplexCallback   
  2. {   
  3.     [OperationContract(IsOneWay=true)]   
  4.     void Equals(double result);   
  5.   
  6.     [OperationContract(IsOneWay=true)]   
  7.     void Equation(string equation);   
  8. }  

注意在接口定义中,每个服务方法的消息转换模式均设置为One-Way。此外,回调接口是被本地调用,因此不需要定义[ServiceContract]。在服务接口中,需要设置ServiceContractAttribute的CallbackContract属性,使其指向回调接口的类型type。

对于实现服务的类,实例化模式(InstanceContextMode)究竟是采用PerSession方式,还是PerCall方式,应根据该服务对象是否需要保存状态来决定。如果是PerSession,则服务对象的生命周期是存活于一个会话期间。而PerCall方式下,服务对象是在方法被调用时创建,结束后即被销毁。然而在Duplex模式下,不能使用Single方式,否则会导致异常抛出。本例的实现如下:

  1. [ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]   
  2. public class CalculatorService : ICalculatorDuplex   
  3. {   
  4.     double result;   
  5.     string equation;   
  6.     ICalculatorDuplexCallback callback;   
  7.   
  8.     public CalculatorService()   
  9.     {   
  10.         result = 0.0;   
  11.         equation = result.ToString();   
  12.         callback = OperationContext.Current.GetCallbackChannel<ICalculatorDuplexCallback>();   
  13.   
  14.     }  
  15.  
  16.     #region ICalculatorDuplex Members   
  17.   
  18.     public void Clear()   
  19.     {   
  20.         equation += "=" + result.ToString();   
  21.         callback.Equation(equation);   
  22.   
  23.     }   
  24.   
  25.     public void AddTo(double n)   
  26.     {   
  27.         result += n;   
  28.         equation += "+" + n.ToString();   
  29.         callback.Equals(result);   
  30.     }   
  31.   
  32.     public void SubtractFrom(double n)   
  33.     {   
  34.         result -= n;   
  35.         equation += "-" + n.ToString();   
  36.         callback.Equals(result);   
  37.     }  
  38.  
  39.     #endregion   
  40. }  

在类CalculatorService中,回调接口对象callback通过OperationContext.Current.GetCallbackChannel<>()获取。然后在服务方法例如AddTo()中,通过调用该回调对象的方法,完成服务端向客户端返回消息的功能。

在使用Duplex时,Contract使用的Binding应该是系统提供的WSDualHttpBinding,如果使用BasicHttpBinding,会出现错误。因此WCF Service Host程序配置文件应该如下所示:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <configuration>  
  3.   <!-- When deploying the service library project, the content of the config file must be added to the host's    
  4.   app.config file. System.Configuration does not support config files for libraries. -->  
  5.   <system.serviceModel>  
  6.     <services>  
  7.       <service name="WcfServiceLibrary1.CalculatorService" behaviorConfiguration="WcfServiceLibrary1.Service1Behavior">  
  8.         <host>  
  9.           <baseAddresses>  
  10.             <add baseAddress = "http:localhost:8080/Service1" />  
  11.           </baseAddresses>  
  12.         </host>  
  13.           
  14.           
  15.         <endpoint address ="" binding="wsDualHttpBinding" contract="WcfServiceLibrary1.ICalculatorDuplex" />  
  16.       </service>  
  17.     </services>  
  18.     <behaviors>  
  19.       <serviceBehaviors>  
  20.         <behavior name="WcfServiceLibrary1.Service1Behavior">  
  21.           <!-- To avoid disclosing metadata information,    
  22.           set the value below to false and remove the metadata endpoint above before deployment -->  
  23.           <serviceMetadata httpGetEnabled="True"/>  
  24.           <!-- To receive exception details in faults for debugging purposes,    
  25.           set the value below to true.  Set to false before deployment    
  26.           to avoid disclosing exception information -->  
  27.           <serviceDebug includeExceptionDetailInFaults="False" />  
  28.         </behavior>  
  29.       </serviceBehaviors>  
  30.     </behaviors>  
  31.   </system.serviceModel>  
  32. </configuration>  

使用命令svcutil.exe http://localhost:8080/Service1?wsdl生成客户端代理类和配置文件,配置文件内容类似:

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <configuration>  
  3.     <system.serviceModel>  
  4.         <client>  
  5.             <endpoint address="http:localhost:8080/Service1"    
  6.                       binding="wsDualHttpBinding"  
  7.                       contract="ICalculatorDuplex">  
  8.                 <identity>  
  9.                     <userPrincipalName value="allisok-PC\allisok" />  
  10.                 </identity>  
  11.             </endpoint>  
  12.         </client>  
  13.     </system.serviceModel>  
  14. </configuration>  

当服务端将信息回送到客户端后,对消息的处理是由回调对象来处理的,所以回调对象的实现应该是在客户端完成,如下所示的代码应该是在客户端中:

  1. public class CallbackHandler : ICalculatorDuplexCallback   
  2. {  
  3.     #region ICalculatorDuplexCallback Members   
  4.   
  5.     void ICalculatorDuplexCallback.Equals(double result)   
  6.     {   
  7.         Console.WriteLine("Result({0})", result);   
  8.     }   
  9.   
  10.     void ICalculatorDuplexCallback.Equation(string equation)   
  11.     {   
  12.         Console.WriteLine("Equation({0})", equation);   
  13.     }  
  14.  
  15.     #endregion   
  16. }  

客户端调用服务对象相应的为:

  1. class ClientApp   
  2. {   
  3.     static void Main(string[] args)   
  4.     {   
  5.         InstanceContext instanceContext = new InstanceContext(new CallbackHandler());   
  6.   
  7.         CalculatorDuplexClient client = new CalculatorDuplexClient(instanceContext);   
  8.   
  9.           
  10.         double value;   
  11.         value = 100;   
  12.         client.AddTo(value);   
  13.   
  14.         value = 50;   
  15.         client.SubtractFrom(value);   
  16.   
  17.         client.Clear();   
  18.            
  19.           
  20.           
  21.   
  22.         Console.ReadLine();   
  23.   
  24.     }   
  25. }  

注意在Duplex中,会话创建的时机并不是客户端创建Proxy实例的时候,而是当服务对象的方法被第一次调用时,会话方才建立,此时服务对象会在方法调用之前被实例化,直至会话结束,服务对象都是存在的。