InstanceContextSharing
此示例演示如何使用 IInstanceContextProvider 接口在多个调用和客户端之间共享 InstanceContext 对象。此示例演示客户端应用程序如何创建唯一标识符以及如何将其发送给服务。服务随后将该标识符与特定的 InstanceContext 对象相关联。客户端随后将该标识符传递到其他客户端。然后,此客户端将上下文标识符放置到发送到同一服务的标头中。该服务使用上下文标识符将第二个调用与第一个实例上下文对象相关联,因此也与服务对象相关联。
提示
本主题的末尾介绍了此示例的设置过程和生成说明。
实现 IInstanceContextProvider 接口,以向系统提供相应的 InstanceContext 对象。通常,实现此接口是为了支持共享会话、启用服务实例池、控制服务实例的生存期或将客户端之间的上下文分组。
若要插入自定义 IInstanceContextProvider,请创建一个行为(例如 IEndpointBehavior 或 IServiceBehavior),并使用该行为将 IInstanceContextProvider 对象分配给 System.ServiceModel.Dispatcher.DispatchRuntime.InstanceContextProvider 属性。
此示例在自定义 ShareableAttribute
属性上使用 IServiceBehavior 以插入 IInstanceContextProvider。
ShareableAttribute
实例化一个 CalculatorExtension
对象(该对象实现 IInstanceContextProvider),循环访问每个 EndpointDispatcher,并将每个 InstanceContextProvider 属性设置为刚创建的 CalculatorExtension
对象。下面的示例代码演示了此过程。
//Apply the custom IInstanceContextProvider to the EndpointDispatcher.DispatchRuntime
public void ApplyDispatchBehavior(ServiceDescription description, ServiceHostBase serviceHostBase)
{
CalculatorExtension extension = new CalculatorExtension();
foreach (ChannelDispatcherBase dispatcherBase in serviceHostBase.ChannelDispatchers)
{
ChannelDispatcher dispatcher = dispatcherBase as ChannelDispatcher;
foreach (EndpointDispatcher endpointDispatcher in dispatcher.Endpoints)
{
endpointDispatcher.DispatchRuntime.InstanceContextProvider = extension;
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(extension);
}
}
}
来自客户端的每个调用将遍历 CalculatorExtension
对象,以确定使用哪个 InstanceContext 来为该特定消息提供服务。
该示例使用两个客户端(Client1
和 Client2
)来演示共享。下面的代码中显示了这两个客户端进行交互的顺序。请注意,客户端和服务器都使用 CustomHeader
为 InstanceContext 传递唯一标识符,并使用随机数生成器实用程序生成唯一的 32 字节标识符。
public static class CustomHeader
{
public static readonly String HeaderName = "InstanceId";
public static readonly String HeaderNamespace = "http://Microsoft.ServiceModel.Samples/Sharing";
}
static string NewInstanceId()
{
byte[] random = new byte[256 / 8];
randomNumberGenerator.GetBytes(random);
return Convert.ToBase64String(random);
}
Client1
创建一个 OperationContextScope,以便它添加标头。它随后生成一个唯一 ID,然后将该 ID 作为一个值添加到其传出标头列表中。//Create a new 1028 bit strong InstanceContextId that we want the server to associate //the InstanceContext that processes all messages from this client. String uniqueId = NewInstanceId(); MessageHeader Client1InstanceContextHeader = MessageHeader.CreateHeader( CustomHeader.HeaderName, CustomHeader.HeaderNamespace, uniqueId); try { using (new OperationContextScope(client1.InnerChannel)) { //Add the header as a header to the scope so it gets sent for each message. OperationContext.Current.OutgoingMessageHeaders.Add(Client1InstanceContextHeader); ... } }
之后,它将调用可在远程服务器上调用多个操作的
DoCalculations
。对于调用的每个操作,将发送自定义标头和生成的 ID。Client1
调用Add
操作,这是此通道上的第一个调用。服务器接收消息,并使用通道和消息调用
CalculatorExtension.GetExistingInstanceContext
。扩展将检查通道是否已附加到 InstanceContext。如果没有,扩展将尝试查找自定义标头,并检查缓存以查看它是否有用于该 ID 的 InstanceContext。在本示例中,缓存为空,因此返回 空。请注意,扩展实际上存储的是AddressableInstanceContextInfo
(在后面的源中定义),以便保留有关InstanceContext
的额外信息并在创建InstanceContext
之前在多个线程之间进行协调。// If the channel has a session, we bind the session to a particular InstanceContext // based on the first message, and then route all subsequent messages on that session to // the same InstanceContext. bool hasSession = (channel.SessionId != null); if (hasSession) { info = channel.Extensions.Find<AddressableInstanceContextInfo>(); if (info != null) { ... } } // If this is the first message of a session, or is using a datagram channel, look in // the message headers to see if there is a header with an instance id. int headerIndex = message.Headers.FindHeader(CustomHeader.HeaderName, CustomHeader.HeaderNamespace); // If there was a header, extract the instanceId. string instanceId = null; if (headerIndex != -1) { instanceId = message.Headers.GetHeader<string>(headerIndex); } ... // Check our table to see if we recognize the instance id. lock (this.ThisLock) { if ((instanceId == null) || !this.contextMap.TryGetValue(instanceId, out info)) { isNew = true; ... } ... } ... if (isNew) { // This tells WCF to create a new InstanceContext and call InitializeInstanceContext. return null; }
服务器随后创建新的 InstanceContext 并调用
CalculatorExtension.InitializeInstanceContext
。InitializeInstanceContext 方法向扩展通知新建的 InstanceContext。这样将会从通道(针对会话通道)或缓存(针对数据报通道)中检索AddressableInstanceContextInfo
并将其添加到 InstanceContext 扩展集合中。只有完成此操作后,服务器才能快速检索任何 InstanceContext 的 ID。如果通道中有会话,扩展则会将通道添加到 InstanceContext 的 IncomingChannels 集合,以便在通道关闭之前 Windows Communication Foundation (WCF) 不会关闭 InstanceContext。该示例还与 InstanceContext 的 Closed 事件关联,因此在该示例被显式关闭时可以从缓存中移除它。现在,此 InstanceContext 即可用于处理消息和回复消息。if (hasSession) { // Since this is a new InstanceContext, we could not add the channel in // GetExistingInstanceContext, so add it here. instanceContext.IncomingChannels.Add(channel); // If we have a session, we stored the info in the channel, so just look it up // there. info = channel.Extensions.Find<AddressableInstanceContextInfo>(); } else { // Otherwise, if we don't have a session, look the info up again in the table. ... } // Now that we have the InstanceContext, we can link it to the // AddressableInstanceContextInfo and vice versa. if (info != null) { instanceContext.Extensions.Add(info); info.SetInstanceContext(instanceContext); } // When the InstanceContext starts closing, remove it from the table. // // Generally we will already have the lock because Close will happen inside // CallIdleCallback. However, if someone just closes the InstanceContext explicitly // before it goes idle, we will not have the lock. Since modifying Dictionary is not // thread-safe, we lock here. instanceContext.Closing += delegate(object sender, EventArgs e) { lock (this.ThisLock) { this.contextMap.Remove(info.InstanceId); } };
Client1
随后调用其第二个操作Subtract
。再次使用新消息调用CalculatorExtension.GetExistingInstanceContext
。检索标头,此时查找成功。将从缓存中返回 InstanceContext。WaitForInstance
确保第一个调用已完成其InitializeInstanceContext
调用。WCF 使用此 InstanceContext 处理消息的其余部分。if (hasSession) { info = channel.Extensions.Find<AddressableInstanceContextInfo>(); if (info != null) { // We may be processing a second message before the first message has finished // initializing the InstanceContext. Wait here until the first message is // done. If the first message has already finished initializing, this returns // immediately. info.IncrementBusyCount(); return info.WaitForInstanceContext(); } }
Client1
调用Subtract
和Delete
操作,它们通过重复步骤 5 的操作来使用相同的 InstanceContext。客户端随后创建另一个通道(
Client2
)并再次创建一个 OperationContextScope,然后将相同 ID 添加到其 OutgoingMessageHeaders 集合中。因此,在对Client2
进行了所有调用后,现在将发送由Client1
使用的此同一 ID。Client2
调用Add()
、Subtract()
、Multiply()
和Divide()
操作,并使用与步骤 7 中相同的逻辑,第一个客户端创建的 InstanceContext 为所有这些操作提供服务。服务器调用CalculatorExtension.GetExistingInstanceContext
。扩展查找标头并搜索与该标头相关联的 InstanceContext。此 InstanceContext 用于调度消息。// If this is the first message of a session, or is using a datagram channel, look in // the message headers to see if there is a header with an instance id. int headerIndex = message.Headers.FindHeader(CustomHeader.HeaderName, CustomHeader.HeaderNamespace); // If there was a header, extract the instanceId. string instanceId = null; if (headerIndex != -1) { instanceId = message.Headers.GetHeader<string>(headerIndex); } // Remember if we created a new AddressableInstanceContextInfo. bool isNew = false; // Check our table to see if we recognize the instance id. lock (this.ThisLock) { if ((instanceId == null) || !this.contextMap.TryGetValue(instanceId, out info)) { ... } ... } ... if (isNew) { // ... } else { InstanceContext instanceContext = info.WaitForInstanceContext(); ... return instanceContext; }
设置、生成和运行示例
若要生成解决方案,请按照生成 Windows Communication Foundation 示例中的说明进行操作。
若要用单机配置或跨计算机配置来运行示例,请按照运行 Windows Communication Foundation 示例中的说明进行操作。
Send comments about this topic to Microsoft.
© 2007 Microsoft Corporation. All rights reserved.