WCF服务端运行时架构体系详解[下篇]

时间:2022-04-27
本文章向大家介绍WCF服务端运行时架构体系详解[下篇],主要内容包括二、分发运行时(DispatchRuntime)、2、服务实例上下文、3、会话关闭通知、4、同步上下文、5、消息检验、6、操作与操作选择、7、授权、8、审核、9、事务与会话、10、未处理操作、11、SOAP ValidateMustUnderstand处理、12、并发控制、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

作为WCF中一个核心概念,终结点在不同的语境中实际上指代不同的对象。站在服务描述的角度,我们所说的终结点实际上是指ServiceEndpoint对象。如果站在WCF服务端运行时框架来说,终结点实际上指代的是终结点分发器(EndpointDispatcher)。而ServiceEndpoint与EndpointDispatcher是一一匹配的,并且前者是创建后者的基础。而终结点分发器具有自己的运行,即分发运行时(DispatchRuntime)。

目录 一、终结点分发器(EndpointDispatcher) 二、分发运行时(DispatchRuntime)       可扩展组件                         认证与授权             服务实例上下文             会话关闭通知             同步上下文             消息检验             操作与操作选择    可扩展属性            授权            审核            事务与会话            未处理操作            SOAP ValidateMustUnderstand处理            并发控制

一、终结点分发器(EndpointDispatcher)

除了之前介绍的三个辅助信道分发器向匹配的终结点分发器实施消息路由的三个属性(AddressFilter、ContractFilter和FilterPriority)之外,你还可以通过属性ContractName和ContractNamespace得到服务契约的名称和命名空间,以通过EndpointAddress属性得到相应的终结点地址。将消息路由到该终结点分发器的信道分发器可以通过属性ChannelDispatcher获得。但是对于终结点分发器来说,其重要的还是通过属性DispatchRuntime表示的分发运行时。

   1: public class EndpointDispatcher
   2: {
   3:     //其他成员
   4:     public string ContractName { get; }
   5:     public string ContractNamespace { get; }
   6:  
   7:     public MessageFilter AddressFilter { get; set; }
   8:     public MessageFilter ContractFilter { get; set; }
   9:     public int FilterPriority { get; set; }
  10:  
  11:     public ChannelDispatcher ChannelDispatcher { get; }
  12:     public DispatchRuntime DispatchRuntime { get; }
  13:     public EndpointAddress EndpointAddress { get; }
  14: }

二、分发运行时(DispatchRuntime)

毫不夸张地说,终结点分发器的分发运行时是WCF整个服务端运行时架构体系的核心,同时也是对WCF服务端服务模型进行扩展重点考虑的对象。分发运行时之所以具有如此重要的地位,原因在于:终结点分发器接收到从信道分发器路由的消息的整个处理是在分发运行时中进行的。

上面分析信道分发器一样,我们首先来看看分发运行时具有哪些可扩展的组件。终结点的分发运行时对应的类型为DispatchRuntime。下面的代码片断列出了这些扩展组件在DispatchRuntime中的对应的属性定义。
   1: public sealed class DispatchRuntime
   2: {
   3:     //其他成员
   4:     public ServiceAuthorizationManager ServiceAuthorizationManager { get; set; }
   5:     public ServiceAuthenticationManager ServiceAuthenticationManager { get; set; }
   6:     public RoleProvider RoleProvider { get; set; }
   7:     public ReadOnlyCollection<IAuthorizationPolicy> ExternalAuthorizationPolicies { get; set; }
   8:  
   9:     public IInstanceContextProvider InstanceContextProvider { get; set; }
  10:     public SynchronizedCollection<IInstanceContextInitializer> InstanceContextInitializers { get; }
  11:     public InstanceContext SingletonInstanceContext { get; set; }
  12:     public IInstanceProvider InstanceProvider { get; set; }
  13:  
  14:     public SynchronizedCollection<IInputSessionShutdown> InputSessionShutdownHandlers { get; }
  15:     public SynchronizationContext SynchronizationContext { get; set; }
  16:  
  17:     public SynchronizedCollection<IDispatchMessageInspector> MessageInspectors { get; }
  18:  
  19:     public SynchronizedKeyedCollection<string, DispatchOperation> Operations { get; }
  20:     public IDispatchOperationSelector OperationSelector { get; set; }
  21: }

1、认证与授权

分发运行时具有一组与安全相关的组件,具体来说是这组组件是与身份认证和授权相关。它们包括用于进行认证的ServiceAuthorizationManager,用于进行授权的ServiceAuthorizationManager,以及在在ASP.NET Roles安全主体权限模式下实现授权采用的RoleProvider和在自定义安全主体权限模式下自定义的授权策略(通过ExternalAuthorizationPolicies属性表示)。如果你阅读了《深入剖析授权在WCF中的实现[共14篇]》,相对对这四个对象不会感到陌生。

2、服务实例上下文

服务端框架对服务调用请求的处理最终必然体现在服务实例的创建和操作方法的调用。而服务实例并不是单独存储,而是存在于一个上下文中,该上下文被称为实例上下文(InstanceContext)。WCF服务端框架通过一个被称为实例上下文提供者(InstanceContextProvider)来提供基于当前服务请求对应的实例上下文。这里所说的实例上下文的提供机制包括两种情况下:创建新的服务上下文,或者提供一个现有之前创建好的实例上下文。

实例上下文通过类型InstanceContext表示,而所有的实例上下文提供者实现了一个具有如下定义的接口IInstanceContextProvider。WCF为我们提供了相应的实例上下文提供者以实现不同的实例上下文模式:单调(PerCall)、会话(PerSession)和单例(Single)。关于实例上下文、实例上下文模式以及它们最终采用怎样的实例上下文提供者,在《WCF技术剖析(卷1)》第9章《实例管理与会话》有详细的介绍。在这里你需要了解的是:WCF服务端框建最终使用的实例上下文提供者反映在InstanceContextProvider属性上。如果采用单例实例上下文模式,最终提供的实例上下文赋值给了属性SingletonInstanceContext。

   1: public interface IInstanceContextProvider
   2:  {
   3:      InstanceContext GetExistingInstanceContext(Message message, IContextChannel channel);
   4:      void InitializeInstanceContext(InstanceContext instanceContext, Message message, IContextChannel channel);
   5:      bool IsIdle(InstanceContext instanceContext);
   6:      void NotifyIdle(InstanceContextIdleCallback callback, InstanceContext instanceContext);
   7:  }

当实例上下文被创建出来后,如果你需要对其进行一些初始化操作。比如通过检查被创建出来的实例上下文并执行相应的一些操作,或者直接对创建的实例上下文作相应的修改。实例上下文的初始化工作通过一个被称作实例上下文初始化器(InstanceContextInitializer)来实现,它们的类型实现了一个具有如下定义的接口IInstanceContextInitializer。相应的初始化操作定义在Initialize方法中。

   1: public interface IInstanceContextInitializer
   2: {
   3:     void Initialize(InstanceContext instanceContext, Message message);
   4: }

你可以将任意数量的实例上下文初始化器应用到WCF服务端分发系统中,而DispatchRuntime的只读属性InstanceContextInitializers代表了当前的实例上下文初始化器列表。当实例上下文被成功创建后,这些实例上下文初始化器将会以此被执行。

在默认的情况下,WCF会采用反射的方式调用服务类型的无参构造函数来创建服务实例。但是你可以通过自定义一个被称为实例提供者的组件来让WCF服务端分发系统按照你希望的方式创建你想要的实例作为最终的服务实例。所有的InstanceProvider都实现具有如下定义的接口System.ServiceModel.Dispatcher.IInstanceProvider,而DispatchRuntime的InstanceProvider属性表示这个真正被用于提供具体服务实例的实例提供者。《WCF技术剖析(卷1)》第9章《实例管理与会话》中也有关于实例提供者的介绍。

   1: public interface IInstanceProvider
   2:  {
   3:      object GetInstance(InstanceContext instanceContext);
   4:      object GetInstance(InstanceContext instanceContext, Message message);
   5:      void ReleaseInstance(InstanceContext instanceContext, object instance);
   6:  }

3、会话关闭通知

在一个基于双工(Duplex)消息交换模式的会话中,如果客户端在完成了基于当前会话所有消息介绍工作时系统通知服务端以从事一些相关的处理工作,可以通过实现一个被称为输入会话关闭处理器(InputSessionShutdownHandler)的组件。该组件类型实现如下一个名为IInputSessionShutdown的接口。DoneReceiving方法会在接受到上述通知时被调用,而输入参数调用当前的双工信道。如果该信道出现错误(状态变成Faulted),方法ChannelFaulted会被调用。

   1: public interface IInputSessionShutdown
   2: {
   3:     void ChannelFaulted(IDuplexContextChannel channel);
   4:     void DoneReceiving(IDuplexContextChannel channel);
   5: }

和实例上下文初始化器一样,输入会话关闭处理器的数量也没有限制,你可以将多个这样的组件应用到当前分发运行时。而DispatchRumtime的只读属性InputSessionShutdownHandlers代表当前的输入会话关闭处理器列表。

4、同步上下文

在默认的情况下,如果服务寄宿过程中的当前线程具有同步上下文(比如将Windows Forms应用作为服务的宿主,主线程具有一个类型为WindowsFormsSynchronizationContext的同步上下文),那么后续的处理将在该同步上下文中进行。而你可以通过DispatchRuntime的SynchronizationContext属性得到该同步上下文对象。

这就意味着,所有的处理均是按照同步的方式进行处理的,在高并发的情况下着往往是致命的。我们可以使用在服务类型上应用ServiceBehaviorAttribute特性并通过指定UseSynchronizationContext属性决定是否使用宿主线程当前的同步上下文。

   1: [AttributeUsage(AttributeTargets.Class)]
   2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
   3: {
   4:     //其他成员
   5:     public bool UseSynchronizationContext { get; set; }
   6: }

此外,由于DispatchRuntime的SynchronizationContext属性是可读可写的,如果你为其指定一个特定的同步上下文对象,相应的处理工作也是在该同步上下文中执行。

5、消息检验

WCF允许你对服务端框架进行扩展以实现对路由道终结点分发器的消息进行后续的处理,我们把这个机制成为消息检验。比如说,如果说客户端希望向服务端传输一些与功能无关的上下文信息,可以将其封装成消息报头并添加到请求消息中。你就可以通过这个消息检验机制将上下文信息从相应的消息报头中获取出来。此外,通过消息的检验机制运行你对传入的消息进行相应的更改。如果后续处理中需要相应的控制信息,你可以将其通过该机制将这些信息以消息报头的方式至于传输的消息之中。

消息的检验机制通过自定义消息检验器(MessageInspector)。实际上WCF的客户端和服务端运行时具有自己的消息检验器;客户端的被称为客户端消息检验器(ClientMessageInspector),用于针对发出的请求消息和接收的回复消息进行检验;而服务端的被称为分发消息检验器(DispatchMessageInspector),用于针对于接收的请求消息和发送的回复消息进行检验。在这里我们主要讨论分发消息检验器,在介绍客户端运行时架构的时候我们会对客户端消息检验器进行相应的介绍。

分发消息检验器实现了接口IDispatchMessageInspector。从如下给出的IDispatchMessageInspector接口的定义中我们可以看出,该接口具有两个方法BeforeSendReply和AfterReceiveRequest。从方法名称就可以看出来,它们分别在请求消息被接收后以及回复消息被发送前对消息进行检验。

   1: public interface IDispatchMessageInspector
   2: {
   3:     object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext);
   4:     void BeforeSendReply(ref Message reply, object correlationState);
   5: }

分发运行时具有一个分发消息检验器的列表,该列表通过属性MessageInspectors表示。根据具体应用中针对消息检查的需要,你可以在该列表中添加任意的分发消息检验器,它们按照添加的顺序依次执行。

6、操作与操作选择

我们说了,服务端分发体系对消息请求处理最终体现在多相应操作方法的执行。在服务表示中,操作通过类型OperationDescription表示。当服务端运行时框架通过服务描述被创建的时候,每一个OperationDescription会转变成DiaptchOperation对象。而DispatchRuntime的Operations属性就代表当前终结点的所有DispatchOperation集合。这是一个类似于字典的集合类型,而代表键值的字符串为操作的名称。

由于当前的分发运行时中大都具有多个DispatchOperation对象,而它接收的是一个消息,那么必须具有某种机制以实现根据接收的消息解析出对应的目标操作。这样一种操作的选择机制在WCF分发运行时中是通过一个被称为操作选择器(OperationSelector)的组件来实现的。操作选择器对应的接口为IDispatchOperationSelector,针对消息对操作的选择通过SelectOperation实现,方法的返回值代表操作的名称。当从该方法得到正确的操作名称,WCF就可以从Operations熟悉代表的操作列表中选择正确的DispatchOperation了。

   1: public interface IDispatchOperationSelector
   2: {
   3:     string SelectOperation(ref Message message);
   4: }

至此,我们对分发运行时中基本的可供扩展的组件进行了接收。这些组件是你针对服务端运行时进行扩展的核心。整个分发运行时连同这些可扩展的组件可以通过下图表示。

介绍了分发运行时可供扩展(添加或者替换)的组件之后,我们来看它具有哪些可以修改的属性,通过修改这些属性会对整个消息分发、实例上下文的激活以及服务操作的执行等行为具有怎样的影响。下面的代码片断列出了这些属性。

   1: public sealed class DispatchRuntime
   2: {
   3:     //其他成员
   4:     public PrincipalPermissionMode PrincipalPermissionMode { get; set; }
   5:     public bool ImpersonateCallerForAllOperations { get; set; }
   6:  
   7:     public AuditLogLocation SecurityAuditLogLocation { get; set; }
   8:     public bool SuppressAuditFailure { get; set; }
   9:     public AuditLevel ServiceAuthorizationAuditLevel { get; set; }
  10:     public AuditLevel MessageAuthenticationAuditLevel { get; set; }
  11:  
  12:  
  13:     public bool AutomaticInputSessionShutdown { get; set; }
  14:     public bool ReleaseServiceInstanceOnTransactionComplete { get; set; }
  15:     public bool TransactionAutoCompleteOnSessionClose { get; set; }
  16:     public bool IgnoreTransactionMessageProperty { get; set; }
  17:  
  18:     public DispatchOperation UnhandledDispatchOperation { get; set; }
  19:     public bool ValidateMustUnderstand { get; set; }
  20:     public ConcurrencyMode ConcurrencyMode { get; set; }
  21: }

7、授权

DispatchRuntime的属性PrincipalPermissionMode和ImpersonateCallerForAllOperations是与授权相关的两个属性。前者表示安全主体权限模式,后者表示是否以模拟的客户端Windows帐号执行所有的操作。它们对应于服务行为ServiceAuthorizationBehavior的同名属性。

   1: public sealed class ServiceAuthorizationBehavior : IServiceBehavior
   2: {
   3:     //其他成员
   4:     public bool ImpersonateCallerForAllOperations { get; set; }
   5:     public PrincipalPermissionMode PrincipalPermissionMode { get; set; }
   6: }

8、审核

DispatchRuntime具有四个与审核相关的属性。其中SecurityAuditLogLocation决定了审核日志被入何处;SuppressAuditFailure决定了是否要抑制审核日志记录过程中出现的非关键异常的抛出;而ServiceAuthorizationAuditLevel和MessageAuthenticationAuditLevel则表示具体那些认证和授权相关的事件应该进行审核日志的记录。这四个属性对应于服务行为ServiceSecurityAuditBehavior的同名属性。

   1: public sealed class ServiceSecurityAuditBehavior : IServiceBehavior
   2: {
   3:     //其他成员
   4:     public AuditLogLocation AuditLogLocation { get; set; }
   5:     public bool SuppressAuditFailure { get; set; }
   6:     public AuditLevel MessageAuthenticationAuditLevel { get; set; }
   7:     public AuditLevel ServiceAuthorizationAuditLevel { get; set; }
   8: }

9、事务与会话

接下来介绍四个与会话和事务相关的属性。AutomaticInputSessionShutdown表示服务端是否在客户端关闭输出会话(Output Session)的时候是否关闭输入会话(Input Session)。关于会话的相关内容,在《WCF技术剖析(卷1)》第9章《实例管理与会话》中具有详细的介绍。至于这里指的输出和输入则是消息交换模式(MEP:Message Exchange Pattern),你可以从《WCF技术剖析(卷1)》的第4章《服务契约》中找到关于消息交换模式的详细介绍。

另外两个属性ReleaseServiceInstanceOnTransactionComplete和TransactionAutoCompleteOnSessionClose表示在事务提交之后是否自定释放服务实例,以及在会话关闭之后是否自动提交事务。上述的两个属性分别对应ServiceBehaviorAttribute的同名属性。而AutomaticInputSessionShutdown属性则定于ServiceBehaviorAttribute的AutomaticSessionShutdown属性。

   1: [AttributeUsage(AttributeTargets.Class)]
   2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
   3: {
   4:     //其他成员
   5:     public bool AutomaticSessionShutdown { get; set; }
   6:     public bool ReleaseServiceInstanceOnTransactionComplete { get; set; }
   7:     public bool TransactionAutoCompleteOnSessionClose { get; set; }
   8: }

为了实现基于消息交换的事务传播,事务本身是被封装成一个TransactionMessageProperty对象并被消息属性的形式置于消息之中。而DispatchRuntime的IgnoreTransactionMessageProperty属性表示在接收到这么一消息的时候,是否要忽略其中的TransactionMessageProperty消息属性。

是否忽略消息中的IgnoreTransactionMessageProperty属性决定于终结点的两个要素,即绑定和契约。具体来说,如果绑定不支持事务流转(Transaction Flow),则该属性返回True。反之,还有需要分析服务契约中应用在操作上的TransactionFlowAttribute设置。相关的逻辑,请参阅本书第3章《事务》。

10、未处理操作

但我们在定义服务契约的时候,通过将OperationContractAttribute特性应用在相应的方法上使其成员一个服务操作。每个操作都具有一个Action属性,并最终决定了针对该操作的请求消息的Action报头的URI。我们可以通过OperationContractAttribute特性的同名属性设定操作的Action。如果该属性没有在OperationContractAttribute特性进行显式设置,对应的操作也具有一个默认值。当ServiceHost被开启之后,每一个终结点对应的操作都转换成DispatchOperation对象,并添加到DispatchRuntime的Operations属性表示的操作列表中。

而实际上我们可以通过OperationContractAttribute将操作的Action定义成“*”。这样的操作被称为为处理操作(Unhandled Operation)这样的操作最终同样会被转换成DispatchOperation对象,并作为DispatchRuntime的UnhandledDispatchOperation属性而存在。对于请求的消息,不能从DispatchRuntime的Operations属性表示的操作列表中找到一个相匹配的操作时,这个未处理操作会被用于选用。

11、SOAP ValidateMustUnderstand处理

DispatchRuntime的ValidateMustUnderstand属性用于指定是由系统还是由应用程序强制执行 SOAP MustUnderstand标头处理。使用该属性来关闭对到达的消息头强制执行验证。在正常执行过程中,将消息头与UnderstoodHeaders 属性进行比较,来确认是否由服务显式处理到达的消息。将此属性设置为false可以禁用此检查。当设置为false时,应用程序必须检查具有 MustUnderstand="true" 标记的标头,如果其中一个或多个标头没有被理解,则返回错误。当应用程序应接受任何传入的SOAP 消息(例如,使用类型化消息或非类型化消息)以及执行自定义标头处理时,这将很有用。该属性对应于ServiceBehaviorAttribute特性的同名属性。

   1: [AttributeUsage(AttributeTargets.Class)]
   2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
   3: {
   4:     //其他成员
   5:     public bool ValidateMustUnderstand { get; set; }
   6: }

12、并发控制

最后一个属性ConcurrencyMode与并发有关,用于指定三种并发模式(Single、Reentrant和Multiple)中的某一种。它同样对应于ServiceBehaviorAttribute特性的同名属性。

   1: [AttributeUsage(AttributeTargets.Class)]
   2: public sealed class ServiceBehaviorAttribute : Attribute, IServiceBehavior
   3: {
   4:     //其他成员
   5:     public ConcurrencyMode ConcurrencyMode { get; set; }
   6: }

WCF服务端运行时架构体系详解[上篇] WCF服务端运行时架构体系详解[中篇] WCF服务端运行时架构体系详解[下篇] WCF服务端运行时架构体系详解[续篇]