我正在将WCF实现到现有的应用程序中,作为实现的一部分,我创建了一个名为Manager的单件服务,它调用并实例化另一个服务(我们称之为LegacyApp)。 LegacyApp是每会话服务,它只不过是实例化为单片对象的现有应用程序(此设计是由于现有应用程序的体系结构)。
这个想法是任何客户端(例如.NET,Java)都可以连接到Manager服务,获取预先存在的LegacyApp实例列表,并连接到现有实例或获取新实例。
我已根据MSDN examples实现了InstanceContextSharing,但这似乎只适用于每个应用程序,即如果我在Manager服务上创建多个LegacyApp客户端,Manager服务可以在不同的LegacyApp上下文之间切换通过使用适当的唯一会话ID传递自定义标头。但是,如果我尝试使用具有相同唯一会话ID的新客户端连接到LegacyApp,则LegacyApp服务的行为就像没有其他客户端已连接一样。
我的问题是:如何在WCF中实现在不同应用程序之间工作的会话共享?
修改(1) Manager服务和LegacyApp服务由同一服务主机托管。 Manager服务使用代理客户端(在单独的代理库中定义)连接到LegacyApp服务。从某种意义上说,服务主机本身就是客户端,因为客户端和服务器都在同一个服务主机中。如果我创建另一个使用相同代理库来创建代理客户端的客户端(比如一个WinForms客户端),那么我会得到上面描述的行为。
以下是我实施的代码片段。扩展类和Shareable属性类与MSDN示例中的相同。为简洁起见,我遗漏了各种服务和客户的不相关部分。
LegacyApp服务:
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)>
<Shareable()>
Public Class LegacyAppService
Implements ILegacyApp
Private _foo As String = "Not Set"
Public Sub SetFoo(ByVal foo As String) Implements ILegacyApp.SetFoo
_foo = foo
End Sub
Public Function GetFoo() As String Implements ILegacyApp.GetFoo
Return _foo
End Function
End Class
LegacyApp客户端:
Public Class LegacyAppClient
Inherits ClientBase(Of ILegacyApp)
Implements ILegacyApp
Public Sub New()
Me.New(String.Empty)
End Sub
Public Sub New(ByVal id As String)
If IsNothing(id) OrElse id = String.Empty Then
_uniqueID = NewInstanceId()
Else
_uniqueID = id
End If
CreateContextHeader()
End Sub
Private Shared Function NewInstanceId() As String
Dim random As Byte() = New Byte(CInt(256 / 8 - 1)) {}
RandomNumberGenerator.GetBytes(random)
Return Convert.ToBase64String(random)
End Function
Private Sub CreateContextHeader()
_contextHeader = MessageHeader.CreateHeader(CustomHeader.HeaderName, CustomHeader.HeaderNamespace, _uniqueID)
End Sub
Public ReadOnly Property ID() As String
Get
Return _uniqueID
End Get
End Property
Public Sub SetFoo(ByVal foo As String) Implements ILegacyApp.SetFoo
Using New OperationContextScope(InnerChannel)
OperationContext.Current.OutgoingMessageHeaders.Add(_contextHeader)
Channel.SetFoo(foo)
End Using
End Sub
Public Function GetFoo() As String Implements ILegacyApp.GetFoo
Using New OperationContextScope(InnerChannel)
OperationContext.Current.OutgoingMessageHeaders.Add(_contextHeader)
Return Channel.GetFoo()
End Using
End Function
End Class
经理服务:
<ServiceBehavior(InstanceContextMode:=InstanceContextMode.Single)>
Public Class ManagerService
Implements IManager
Public Function CreateService() As String Implements IManager.CreateService
Dim openError As Integer
Dim legacyAppClient As New LegacyAppClient
legacyAppClient.SetFoo(legacyAppService.ID)
Return legacyAppClient.ID
End Function
End Class
测试客户:
<...>
Dim sessionID As String
Dim legacyApp As LegacyAppClient
sessionID = _managerClient.CreateService()
legacyAppClient = New LegacyAppClient(sessionID)
Dim foo As String = legacyAppClient.GetFoo()
<...>
我期望的行为是在查询sessionID
时返回foo
值,但我在测试客户端中调用的legacyAppClient.GetFoo()
方法返回“未设置”。
修改(2) 我错误地想到了这个问题吗?我是否应该从测试客户端获得所有呼叫转到Manager服务并让它充当LegacyApp服务的包装器?
答案 0 :(得分:1)
实例上下文共享是单个进程的特征。您可能正在寻找的是durable context,例如存储在数据库中。
答案 1 :(得分:0)
对于那些对我们的解决方案感兴趣的人,我们通过实施isolated service host architecture解决了这个问题。我们的解决方案是让Manager服务提供多个操作来实例化,查询和关闭每个在自己的AppDomain中隔离的LegacyApp服务(由于在LegacyApp中大量使用全局变量,这是必要的)。每个LegacyApp服务都作为单例实例托管,因此任何希望共享相同LegacyApp会话的客户端只需要查询Manager服务以获取该地址,然后连接到正确的实例。这个解决方案适用于我们,因为LegacyApp无论如何都表现为单身。
另外,非常感谢Matt Brindley为his code查询本地计算机以获取下一个可用的TCP端口。此代码用于Manager服务中的StartInstance操作的实现。
再次感谢Ladislav Mrnka让我们直接进行WCF实例上下文共享。
以下是一些代码段:
IManager界面:
Imports System.ServiceModel
<ServiceContract()>
Public Interface IManager
<OperationContract()>
Function GetOpenInstances() As String()
<OperationContract()>
Function StartInstance() As String
<OperationContract()>
Sub CloseInstance(ByVal address As String)
End Interface