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