none
WCF Remote Client Callbacks using wsDualHttpBinding

    Question

  • I am developing an application using WPF and WCF. This app on the server side will need for WCF services hosted in IIS6 to communicate with WCF services that are hosted as Windows services on the same server / or network. Also the WCF services hosted in IIS6 has to communicate with the WPF client apps that will be installed on computers that are not in the same network as the server/s. Right now I am creating small test apps so I can figure out how to get everything to play.

    I have sucessfully made calls from WPF to WCF, hit the data store from WCF and passed back complex types to WPF with security etc. That part works great, however trying to do a client callback is kicking my butt. I have walked through several tut's on the net with no sucess. and I think I know why. All of the tut's that I have found are client/server where the client and the server are on the same network. Another problem I have is that my development machine is behind a router (on a different network than the server, and this is the same type of environment all of the users of this app will have) so when I create the custom binding on the client and set the ClientBaseAddress (as follows);

     

    Dim mybinding As WSDualHttpBinding = New WSDualHttpBinding(WSDualHttpSecurityMode.Message)

    mybinding.Security.Message.ClientCredentialType = MessageCredentialType.Certificate

    mybinding.Security.Message.NegotiateServiceCredential = True

    Dim context As InstanceContext = New InstanceContext(New CallBack())

    Dim myHost As String = Dns.GetHostName()

    Dim ipEntry As IPHostEntry = Dns.GetHostByName(myHost)

    Dim add As IPAddress() = ipEntry.AddressList

    mybinding.ClientBaseAddress = New Uri("http://" + add(0).ToString() + ":" + "4000" + "/")

    _client = New TCallSvcClient(context)

    _client.Endpoint.Binding = mybinding

     

    the address will be a non public IP in the 192.168.x.x range and the server cant call the client back with that address. I figured that I may try to forward a port in the router config but I could not get that to work either. So I have (3) questions:

     

    1) how do I configure client callbacks using wsDualHttpBinding (This is the only binding I can use) when the client and server are on different networks and the clients will be behind a router?

     

    2) Is it possible for IIS 6.0 hosted WCF services to communicate with Windows hosted WCF services (on the same server / or network) via net.tcp binding? (duplex calls here)

     

    3) Can anyone give or point me to some example implementations of the above?

     

    Oh and right now I receive either a timeout exception (which I think is due to te real exception being dropped because of the one way op) or the 405 method not allowed exception (Fiddler says: The page you are looking for cannot be displayed because an invalid method (HTTP verb) was used to attempt access.)

     

    I thank you in advance.

    Friday, August 22, 2008 1:56 AM

Answers

  •  

    Hello Imod,

     

    Here is how you can marshal your intention of changing the UI from an unfriendly thread to the UI thread:

     

    For starters I'm going to try to produce the effect you desire in Visual Basic using Windows Forms (I think that's easier to

    understand). A bit later I'll write the WPF version.

     

    Considering I got the right ideea, you probably have a class that represents the "Listener" and which has at least

    one method that is going to be called remotely, by the WCF service:

     

    Public Class Listener

    Public Sub OnCertainMessage(ByVal message As String)

    Form1.Label1.Text = "Notification: " + message

    End Sub

    End Class

     

    Here is your problem. Amongst other things you want to do when that method is called (which is imminent and asychronous) you probably wish to let the user know about the event that happened, for personal enjoyment, and you do that by changing the GUI. The problem is it doesn't let you.

     

    For Desktop applications I normally use C# with Windows Forms, but I just figured out how you can

    invoke those annoying anonymous delegates (which in C# are actually quite pleasant) in Visual Basic.

     

    The following example will give you an ideea of how you can marshal a call to the GUI thread, in Visual Basic, supposing that you need to pass just One string parameter (for simplicity) along with the call.

     

    Public Delegate Sub MyDelegate(ByVal x As String)

     

    Public Class Listener

    Public Sub OnCertainMessage(ByVal message As String)

    Dim method As MyDelegate

    method = AddressOf OnCertainMessage_TheRightThread

    Form1.Label1.Invoke(method, message)

    End Sub

    Public Sub OnCertainMessage_TheRightThread(ByVal message As String)

    Form1.Label1.Text = message

    End Sub

    End Class

     

    Ok, now what did we do here ?

    Well, at first we defined the delegate that would support our calls that need to be marshalled towards the GUI thread.

    Then we wrote a method (in the same class) that is compatible with the delegate.

     

    Now you probably may have noticed that the second method resembles the first one both in the method prototype and

    in the method body.

     

    All the initial method has to do now is to ask a (windows forms in my scenario) control to marshal a call

    towards the designate method (OnCertainMessage_TheRightThread) and execute the component operations of that

    method on the GUI thread.

     

    To be more precise, the first method, which will be executed on a secondary thread because of the asynchronous nature of the callback, will call the Invoke method of a control (doesn't have to be the exact control

    you're about to access), and the implementation of the Invoke method states that the message pump (hopefully there is one) that is running on the thread that created that particular control, on which you're calling the Invoke method, be notified that in its next cycle, it should call the provided delegate, with the provided parameters.

     

    That kind of wraps it up. Although it may sound ackward it's actually easy to understand. On the other hand,

    understanding anonymous methods and lamdas in Visual Basic may tougher than in C#, syntactically speaking.

     

    Ok, now what does this all look like for WPF.

    Almost the same:

     

    What I said earlier about you not having to use the exact control you're about to change in order to call the

    Control.Invoke() method is true.

     

    So we had to totally separate lines that stated:

    Form1.Label1.Invoke(method, message)

    Form1.Label1.Text = message

     

    Now the second one does the actual work and cannot be changed, but since both Form1 and Label1 were on the same

    thread (obviously, because Form1 is Label1's parent, and Form1.Controls.Add(Label1) would throw the exception you're getting if either of them would've been created on a separate thread, each of the controls believing to be owned by the true

    thread-king, father of a murdered son, husband of a murdered wife, and I will have vengence in this life or the next... Gladiator Smile),

    you might get away with changin the first one into something like:

     

    Form1.Invoke(...

     

    or even

     

    Form1.Button1.Invoke(... etc.

     

    Ok, in WPF all you have to do is obtain a reference to the Dispatcher wich is associated with GUI thread (there should always be one, otherwise the GUI would freez and a NotResponding text would be added to the windows caption by the OS) and call its Invoke method.

     

    You can do that like so:

     

    Dim dispatcher As System.Windows.Threading.Dispatcher

    dispatcher = Label1.Dispatcher

     

    Now you have a reference to the dispatcher that could execute something for you on the right thread.

    (I'm writing everything down, in more lines than required, for readability).

     

    Dim priority As System.Windows.Threading.DispatcherPriority

    priority = Windows.Threading.DispatcherPriority.ApplicationIdle

    dispatcher.Invoke(priority, method, 1, 2, 3)

     

    where method is this guy:

    Dim method As MyDelegate

    method = AddressOf OnCertainMessage_TheRightThread

     

    Anywhoo if you merge the Windows Forms part with the last few lines you might be able to solve the problem.

    So, please excuse my non mother tonguish english.

    I hope this was helpful.

     

    See ya.

    Wednesday, September 24, 2008 12:41 PM

All replies

  • Hi,

     I can try to answer question 1 & 2:

     1) You either need to open up/NAT your firewall/router to let the callbacks through - or you have to implement a SOAP router of some sort, that sits on the router and knows how to mediate between the clients and the service. (Michéle Bustamante has a couple of articles about WCF routing in some back issues of MSDN Magazine).
     
    2) You can not host a service with a netTcpBinding in IIS6 - but a service hosted in IIS6 can make a call to another service with netTcpBinding (hosted with IIS7/WAS, NT Service etc.)

     --larsw
    Friday, August 22, 2008 7:19 AM
    Moderator
  • Hi,

    could you please clearify what you mean with different networks (possibly two different domains or one domain and a workgroup) and whether you really want to authenticate your clients with (individual) certificates.

     

    Friday, August 22, 2008 8:31 AM
  • Sorry about the delay, (Life what can I saySmile. Yse I am talking about different domains. However, I think I have the call back situation solved. This is what I did:

     

    1. WCF has the ability to grab the callers Public Ip address and Port #: So I created a service that the client will call when first loaded. The Svc will grab the necessary info and create a string for return to the client.
      1. EX).

        Code Snippet

        Public Function Start() As String Implements IStartSvc.Start

        Dim oc As OperationContext = OperationContext.Current

        Dim mp As MessageProperties = oc.IncomingMessageProperties

        Dim epProperty As RemoteEndpointMessageProperty = TryCast(mp(RemoteEndpointMessageProperty.Name), RemoteEndpointMessageProperty)

        Dim ret As New StringBuilder()

        ret.Append(oc.SessionId + "&")

        ret.Append(epProperty.Address.ToString() + "*")

        ret.Append(epProperty.Port.ToString())

        Return ret.ToString()

        End Function

         

         

    2. When the client receives the reply, I use the delimeters (&*) to accuratly create sub strings for the IP address etc and set the values in variables. (or better yet app settings).
      1. EX).

        Code Snippet

        Dim pxy As New TempSvc.StartSvcClient()

        Dim ret As String = Nothing

        Try

        pxy.Open()

        ret = pxy.Start()

         

        Catch ex As Exception

        txtbInfo.Text = ex.Message.ToString()

        End Try

        Dim indSid As Short = ret.IndexOf("&")

        Dim indPort As Short = ret.IndexOf("*")

        Dim len As Short = ret.Length

        sid = ret.Substring(0, (len - (len - indSid)))

        ip = ret.Substring(indSid + 1, indPort - indSid - 1)

        port = ret.Substring(indPort + 1)

         

         

    3. Then I create the binding and set the client base address and make the call to register the client with the call back svc.

      1. EX).

        Code Snippet

        Dim bnd As WSDualHttpBinding = New WSDualHttpBinding(WSDualHttpSecurityMode.None)

        Dim ctx As InstanceContext = New InstanceContext(New CB(ip, port))

        bnd.ClientBaseAddress = New Uri("http://" + ip + ":" + "55000" + "/")

        bnd.ReliableSession.Ordered = True

        bnd.ReceiveTimeout = New TimeSpan(0, 10, 0)

        bnd.SendTimeout = New TimeSpan(0, 10, 0)

        Dim cbPxy As New TempSvcCb.CBTimerClient(ctx)

        cbPxy.Endpoint.Binding = bnd

        Try

        cbPxy.StartTimer()

        Catch ex As Exception

        txtbInfo2.Text = ex.Message.ToString()

        End Try

         

         

    Now this along with forwarding the static port "55000" on the router to the private Ip Address of the client and unblocking the port in the firewall, I am seeing the callback data from the svc.

     

    NOW for the new problem, I can not get the data from the callback on to the UI thread. In this simple example all I am trying to do is update a Label control (WPF). All of the examples I have found are using C# and I cannot figure out how to translate those anonymous methods they are using. This is what I have.

     

    'Create a new thread to establish the callback

     

    Code Snippet

    Dim mc As MakeCall = New MakeCall(AddressOf IniCallBack)

    workerThread = New Thread(AddressOf IniCallBack)

    workerThread.Name = "CallBack Thread"

    workerThread.SetApartmentState(ApartmentState.STA)

    workerThread.Start()

     

     

     

    Code Snippet

    Public Sub IniCallBack()

    If Not IsNothing(ip) Then

    Dim bnd As WSDualHttpBinding = New WSDualHttpBinding(WSDualHttpSecurityMode.None)

    Dim ctx As InstanceContext = New InstanceContext(New CB(ip, port))

    bnd.ClientBaseAddress = New Uri("http://" + ip + ":" + "55000" + "/")

    bnd.ReliableSession.Ordered = True

    bnd.ReceiveTimeout = New TimeSpan(0, 10, 0)

    bnd.SendTimeout = New TimeSpan(0, 10, 0)

    Dim cbPxy As New TempSvcCb.CBTimerClient(ctx)

    cbPxy.Endpoint.Binding = bnd

    Try

    cbPxy.StartTimer()

    Catch ex As Exception

    txtbInfo2.Text = ex.Message.ToString()

    End Try

    End If

    End Sub

     

     

    This is the class that handles the callback

     

    Code Snippet

    <DataContract()> _

    <CallbackBehavior(ConcurrencyMode:=ConcurrencyMode.Single, UseSynchronizationContext:=False)> _

    Public Class CB

    Inherits testWpf.Window1

    Implements ICBTimerCallback

    Private _ip As String

    Private _port As String

    Private m As String = Nothing

    <DataMember()> _

    Public Property IP() As String

    Get

    Return _ip

    End Get

    Set(ByVal value As String)

    _ip = value

    End Set

    End Property

    <DataMember()> _

    Public Property Port() As String

    Get

    Return _port

    End Get

    Set(ByVal value As String)

    _port = value

    End Set

    End Property

    Public Sub New()

    End Sub

    Public Sub New(ByVal ip As String, ByVal port As String)

    Me.IP = ip

    Me.Port = port

    End Sub

    '<OperationBehavior(TransactionScopeRequired:=True)> _

    Public Sub SendMsg(ByVal msg As String) Implements TempSvcCb.ICBTimerCallback.SendMsg

    Dim c As TempCallBack = New TempCallBack(AddressOf Update)

    c(msg)

    End Sub

    Public Sub Update(ByVal str As String)

    lblInfo.Content = str

    End Sub

     

     

    WHen I step through this code I see that "msg" in sub SendMsg has the value that I set in the callback message, I just cant figure out how to marshal this to the UI thread to update the WPF control In VB.

     

    Any assistance will be greatly appreciated.

     

     

    Saturday, September 06, 2008 5:46 PM
  •  

    Hello Imod,

     

    Here is how you can marshal your intention of changing the UI from an unfriendly thread to the UI thread:

     

    For starters I'm going to try to produce the effect you desire in Visual Basic using Windows Forms (I think that's easier to

    understand). A bit later I'll write the WPF version.

     

    Considering I got the right ideea, you probably have a class that represents the "Listener" and which has at least

    one method that is going to be called remotely, by the WCF service:

     

    Public Class Listener

    Public Sub OnCertainMessage(ByVal message As String)

    Form1.Label1.Text = "Notification: " + message

    End Sub

    End Class

     

    Here is your problem. Amongst other things you want to do when that method is called (which is imminent and asychronous) you probably wish to let the user know about the event that happened, for personal enjoyment, and you do that by changing the GUI. The problem is it doesn't let you.

     

    For Desktop applications I normally use C# with Windows Forms, but I just figured out how you can

    invoke those annoying anonymous delegates (which in C# are actually quite pleasant) in Visual Basic.

     

    The following example will give you an ideea of how you can marshal a call to the GUI thread, in Visual Basic, supposing that you need to pass just One string parameter (for simplicity) along with the call.

     

    Public Delegate Sub MyDelegate(ByVal x As String)

     

    Public Class Listener

    Public Sub OnCertainMessage(ByVal message As String)

    Dim method As MyDelegate

    method = AddressOf OnCertainMessage_TheRightThread

    Form1.Label1.Invoke(method, message)

    End Sub

    Public Sub OnCertainMessage_TheRightThread(ByVal message As String)

    Form1.Label1.Text = message

    End Sub

    End Class

     

    Ok, now what did we do here ?

    Well, at first we defined the delegate that would support our calls that need to be marshalled towards the GUI thread.

    Then we wrote a method (in the same class) that is compatible with the delegate.

     

    Now you probably may have noticed that the second method resembles the first one both in the method prototype and

    in the method body.

     

    All the initial method has to do now is to ask a (windows forms in my scenario) control to marshal a call

    towards the designate method (OnCertainMessage_TheRightThread) and execute the component operations of that

    method on the GUI thread.

     

    To be more precise, the first method, which will be executed on a secondary thread because of the asynchronous nature of the callback, will call the Invoke method of a control (doesn't have to be the exact control

    you're about to access), and the implementation of the Invoke method states that the message pump (hopefully there is one) that is running on the thread that created that particular control, on which you're calling the Invoke method, be notified that in its next cycle, it should call the provided delegate, with the provided parameters.

     

    That kind of wraps it up. Although it may sound ackward it's actually easy to understand. On the other hand,

    understanding anonymous methods and lamdas in Visual Basic may tougher than in C#, syntactically speaking.

     

    Ok, now what does this all look like for WPF.

    Almost the same:

     

    What I said earlier about you not having to use the exact control you're about to change in order to call the

    Control.Invoke() method is true.

     

    So we had to totally separate lines that stated:

    Form1.Label1.Invoke(method, message)

    Form1.Label1.Text = message

     

    Now the second one does the actual work and cannot be changed, but since both Form1 and Label1 were on the same

    thread (obviously, because Form1 is Label1's parent, and Form1.Controls.Add(Label1) would throw the exception you're getting if either of them would've been created on a separate thread, each of the controls believing to be owned by the true

    thread-king, father of a murdered son, husband of a murdered wife, and I will have vengence in this life or the next... Gladiator Smile),

    you might get away with changin the first one into something like:

     

    Form1.Invoke(...

     

    or even

     

    Form1.Button1.Invoke(... etc.

     

    Ok, in WPF all you have to do is obtain a reference to the Dispatcher wich is associated with GUI thread (there should always be one, otherwise the GUI would freez and a NotResponding text would be added to the windows caption by the OS) and call its Invoke method.

     

    You can do that like so:

     

    Dim dispatcher As System.Windows.Threading.Dispatcher

    dispatcher = Label1.Dispatcher

     

    Now you have a reference to the dispatcher that could execute something for you on the right thread.

    (I'm writing everything down, in more lines than required, for readability).

     

    Dim priority As System.Windows.Threading.DispatcherPriority

    priority = Windows.Threading.DispatcherPriority.ApplicationIdle

    dispatcher.Invoke(priority, method, 1, 2, 3)

     

    where method is this guy:

    Dim method As MyDelegate

    method = AddressOf OnCertainMessage_TheRightThread

     

    Anywhoo if you merge the Windows Forms part with the last few lines you might be able to solve the problem.

    So, please excuse my non mother tonguish english.

    I hope this was helpful.

     

    See ya.

    Wednesday, September 24, 2008 12:41 PM
  • I know this anwer is a little late. But it doesnt hurt for people searching on this issue. In windows forms you have to execute code that changes UI elements under the context of the form thread. You can do this using the SendOrPostCallback method. SEe below for an example:

    _uiContext As SynchronizationContext = Nothing

     

    Sub Main_Load(ByVal sender As Object, ByVal e As EventArgs) Handles MyBase.Load
         _uiContext = SynchronizationContext.Current
         ....
    End Sub

    Private Sub _TaskAdded(ByVal task As Domain.Task)
         TaskListBox.Items.Add(task)
    End Sub

    Public Sub TaskAdded(ByVal task As Domain.Task) Implements ServiceReference.iServiceCallCallback.TaskAdded
          Dim callback As New SendOrPostCallback(AddressOf _TaskAdded)
          _uiContext.Post(callback, task)
    End Sub

    There is obviously more to this code to make it work than what I have posted but this is the guts of the UI context code the important bits are bolded.


    • Edited by w0o7 Thursday, May 28, 2009 9:34 PM
    Thursday, May 28, 2009 9:32 PM