如何在应用程序之间的WCF中实现InstanceContextSharing?

时间:2012-03-14 16:23:39

标签: .net wcf

我正在将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服务的包装器?

2 个答案:

答案 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