1

during the last days I spent a lot of time to find a solution on how to send simple messages from a workflow (WF4)-server to a client (in my case WPF).

There exist some examples how to reply on a request (via callback), but I can not find any example how to contact the client asynchronously.

I want to be able to have a communication like this:

  • [8:00 am] Client: "Hello Server, this is my request. Please start your workflow 'MakeCoffee' using this recipe.".
  • [8:01 am] WF-Server: "Hello Client, I'm on it."
  • [8:05 am] WF-Server: "I can't find any coffee. I will buy some."
  • [8:10 am] WF-Server: "I am boiling the water."
  • [8:15 am] WF-Server: "Do you want sugar?"
  • [8:17 am] Client: "Yes."
  • [8:18 am] WF-Server: "I am done."

Until now, I found these solutions:

1. WCF - Service with CallBack-Channel

This example seems to be pretty cool, but I do not see how to use this with my Workflow-Service - for there is no "implementing service" class. Everything is visual, so I can not apply the "SubscribeClient"-method (or at least I do not know where to place the service implementation).

2. Workflow with notifying events

This example is a nice solution - but (for my understanding) this does not work with a separate workflow-server - it seems to require that the workflow-server and the client are the same.

3. Accessing OperationContext

This example creates a WorkflowServiceHost on client-side, but it is not very easy to handle and I can't adjust it to my requirements.

Does anyone know a handy solution/pattern on how to send messages from a (separate) workflow-server to a client? The client does not know, when the server might send him messages and how many it will be.

I am kind of new to WF4 and would be very thankful for every hint/advise/idea.

Thanks in advance,

Timo

UPDATE

According to the comment from Maurice, I tried to use a simple service in my client, but unfortunately, my client does not receive and show any of the messages.

This is my client-code:

public partial class MainWindow : Window  
{  
    private ServiceHost _serviceHost;  
    private void Window_Loaded(object sender, RoutedEventArgs e)  
    { 
        ApplicationInterface._app = this;  
        //prepare the serviceHost  
        string clientAddress = "http://localhost:8000/ClientService";  
        System.ServiceModel.Channels.Binding bBinding = new BasicHttpBinding();  
        _serviceHost.AddServiceEndpoint(typeof(IClientService), bBinding, clientAddress);  
        _serviceHost.Open();  
    }

    public ListBox GetEventListBox()
    {
        return this.lstEvents;
    }
}  

This is my IClientService-Interface:

[ServiceContract]  
public interface IClientService  
{  
    [OperationContract(IsOneWay=true)]  
    void MeldeStatus(String statusText);  
}  

This is the implementation of the IClientService-Interface:

public class ClientService : IClientService  
{  
    public void MeldeStatus(string statusText)  
    {  
        ApplicationInterface.AddEvent(statusText);  
    }  
}  

This is the static ApplicationInterface:

public static class ApplicationInterface  
{  
    public static MainWindow _app { get; set; }  

    public static void AddEvent(String eventText)  
    {  
        if (_app != null)  
        {  
            new ListBoxTextWriter(_app.GetEventListBox()).WriteLine(eventText);  
        }  
    }  
}

And this the ListBoxTextWriter-class, which should add the messages to a given ListBox:

public class ListBoxTextWriter : TextWriter
{
    const string textClosed = "This TextWriter must be opened before use";

    private Encoding _encoding;
    private bool _isOpen = false;
    private ListBox _listBox;

    public ListBoxTextWriter()
    {
        // Get the static list box
        _listBox = ApplicationInterface._app.GetEventListBox();
        if (_listBox != null)
            _isOpen = true;
    }

    public ListBoxTextWriter(ListBox listBox)
    {
        this._listBox = listBox;
        this._isOpen = true;
    }

    public override Encoding Encoding
    {
        get
        {
            if (_encoding == null)
            {
                _encoding = new UnicodeEncoding(false, false);
            }
            return _encoding;
        }
    }

    public override void Close()
    {
        this.Dispose(true);
    }

    protected override void Dispose(bool disposing)
    {
        this._isOpen = false;
        base.Dispose(disposing);
    }

    public override string ToString()
    {
        return "";
    }

    public override void Write(char value)
    {
        if (!this._isOpen)
            throw new ApplicationException(textClosed); ;

        this._listBox.Dispatcher.BeginInvoke
            (new Action(() => this._listBox.Items.Add(value.ToString())));
    }

    public override void Write(string value)
    {
        if (!this._isOpen)
            throw new ApplicationException(textClosed); ;

        if (value != null)
            this._listBox.Dispatcher.BeginInvoke
                (new Action(() => this._listBox.Items.Add(value)));
    }

    public override void Write(char[] buffer, int index, int count)
    {
        String toAdd = "";

        if (!this._isOpen)
            throw new ApplicationException(textClosed); ;

        if (buffer == null || index < 0 || count < 0)
            throw new ArgumentOutOfRangeException("buffer");


        if ((buffer.Length - index) < count)
            throw new ArgumentException("The buffer is too small");

        for (int i = 0; i < count; i++)
            toAdd += buffer[i];

        this._listBox.Dispatcher.BeginInvoke
            (new Action(() => this._listBox.Items.Add(toAdd)));
    }
}

My WF-Server uses this Send-Activity:

<Send Action="MeldeStatus" EndpointConfigurationName="BasicHttpBinding_Client" sap:VirtualizedContainerService.HintSize="255,90" OperationName="MeldeStatus" ProtectionLevel="None" ServiceContractName="p:IClientService">
  <SendParametersContent>
    <p1:InArgument x:TypeArguments="x:String" x:Key="statusText">Yes, it works.</p1:InArgument>
  </SendParametersContent>
</Send>

This is the configuration of my endpoint at the WF-server:

<system.serviceModel>
  <bindings>
    <basicHttpBinding>        
      <binding name="DefaultHTTPBinding" allowCookies="true" />
    </basicHttpBinding>
  </bindings>    
  <behaviors>
    <serviceBehaviors>
      <behavior>
        <serviceMetadata httpGetEnabled="true"/>          
        <serviceDebug includeExceptionDetailInFaults="true"/>
      </behavior>
    </serviceBehaviors>
  </behaviors>    
  <services>
    <service name="WFServer.ReklamationErfassen">
      <endpoint address="http://localhost:7812/ReklamationErfassen.xamlx"
                binding="basicHttpBinding"
                bindingConfiguration="DefaultHTTPBinding"
                contract="IReklamationService" />
    </service>
  </services>
  <client>      
      <endpoint address="http://localhost:8000/ClientService" binding="basicHttpBinding"
          bindingConfiguration="DefaultHTTPBinding" contract="IClientService"
          name="BasicHttpBinding_Client" />
  </client>
</system.serviceModel>
<system.webServer>
  <modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>

I do not get any messages, error or warnings - can somebody please have a quick check on the given code?

Thanks again,
Timo

Community
  • 1
  • 1
Timo Paschke
  • 11
  • 1
  • 6
  • Kind of hard to see what is going on. I would suggest adding tracing and Fiddler to see if the messages are actually send and not processed on the client or not even send from the workflow. And then investigate further depending on what you find. – Maurice Aug 09 '12 at 14:25

1 Answers1

0

Lots of ways to do this but two of the easiest are:

  • Use the Send activity to send SOAP messages to a WCF endpoint to the client app is hosting. If the client needs to respond just add a Receive activity to wait for the answer.
  • Use a database table to write the messages to. The client app polls the messages and displays these to the user. If a reply is needed you can resume a bookmark on a custom activity with the response.

The first depends on WCF and for responses on hosting in a WorkflowServiceHost. The second approach is more general in not being dependent on WCF but requires more code and custom activities.

Maurice
  • 27,582
  • 5
  • 49
  • 62
  • Thank you, Maurice. I tried your solution (before), but it did not work. I updated my question and posted the code I have used in a minimal example. Could you (or so. else) please have a quick look at the code? – Timo Paschke Aug 09 '12 at 12:48