Workflow Services & MSMQ Revisited
April 20, 2012 4 Comments
I’ recently dusted off a WCF sample I’d written and blogged about a year or two ago. During the process of getting it to work again, I discovered the blog posting is incorrect so I’m reposting with corrections and additional explanation.
Tom Hollander published a great set of posts on this topic which I needed…
We needed a quick proof of concept to show that a workflow service could be activated via a message sent over MSMQ. First part was workflow design and coding, this was the easy part. All I wanted to do was accept a custom type, in this case a TimeEntry, from a SubmitTime service operation that belonged to an ITimeEntryContract. On receiving the time entry I would simple log the fact that it arrived into the event log. This is pretty much the “Hello, World!” of the services demos. The second part was getting the configuration correct…
One of the promises of WCF is that it gives us a unified communication model regardless of the protocol: net.tcp, http, msmq, net.pipe and it does. The best description I’ve heard for WCF is that it is a channel factory, and you configure the channels declaratively in the .config file (you can of course use code too if you prefer). The key benefit is that the service contract and implementation, for the main part, can be channel agnostic. Of course there are the exceptions to prove the rule such as a void return being required by an MSMQ channel but for the most part it holds. As it turns out, it was true that I needed to make no changes to code to move from a default http endpoint to a MSMQ endpoint. I need need to do a lot of configuration and setup though which is not that well documented. This post hopes to correct that is some small way.
First up the easy part, writing the code.
In Visual Studio 2010 I started a new ‘WCF Workflow Service Application’ project. First I define my TimeEntry model class:
using System;
namespace QueuedWorkflowService.Service {
public class TimeEntry {
public Guid TimekeeperId { get; set; }
public Guid MatterId { get; set; }
public TimeSpan Duration { get; set; }
public override string ToString() {
return string.Format(“Timekeeper: {0}, Matter: {1}, Duration: {2}”, TimekeeperId, MatterId, Duration);
}
}
}
Then I defined a code activity to write to the time entry provided into the event log:
using System;
using System.Diagnostics;
using System.Activities;
namespace QueuedWorkflowService.Service {
public sealed class DebugLog : CodeActivity {
public InArgument<string> Text { get; set; }
protected override void Execute(CodeActivityContext context) {
string message = string.Format(“Server [{0}] – Queued Workflow Service – debug :{1}”, DateTime.Now, context.GetValue(this.Text));
Debug.WriteLine(message);
EventLog.WriteEntry(“Queued Service Example”, message, EventLogEntryType.Information);
}
}
}
All the C# code is now written and I create my workflow:
I need a variable to hold my time entry so I define one at the scope of the service:
The project template creates the CorrelationHandle for me but we won’t be using it.
The receive activity is configured as follows:
With the Content specified as:
This is such a simple service that I don’t need any correlation between messages, it just receives and processes the message without communicating back to the sender of the message. Therefore I also cleared out the CorrelatesOn and the CorrelationInitializer properties.
Finally I set up the Debug activity to write the time entry to the event log:
That’s it! I’m done. This now runs using the default binding introduced in WCF4 (http and net.tcp). Starting up the project launches my service and the WCF test client is also opened pointing to my new service. The service is running in Cassini, the local web server built into the Visual Studio debugging environment.
15 minutes, or there about, to build a workflow service. What follows a summary of the steps discovered over the next 4 hours try to convert this sample from using an http endpoint to an msmq endpoint.
Default Behaviour
One of the key messages Microsoft heard from the WCF 3 community was that configuration was too hard. To even get started using WCF you had to understand a mountain of new terms and concepts including: channels, address, binding, contract, behaviours,… the response to this in .NET 4 is defaults. If you don’t specify an endpoint, binding etc then WCF creates a default one for you based upon your machine configuration settings. This makes getting a service up and running a very straightforward experience. BUT as soon as you want to step outside of the defaults, you need the same knowledge that you needed in the WCF 3 world.
Here’s the web.config I ended up with after a couple of hours, the MSMQ settings were a voyage of personal discovery… (http://msdn.microsoft.com/en-us/library/ms731380.aspx)
<?xml version=”1.0″ encoding=”utf-8″?>
<configuration>
<system.web>
<compilation debug=”true” targetFramework=”4.0″ />
</system.web>
<system.serviceModel>
<services>
<service name=”TimeEntryService”>
<endpoint
binding=”netMsmqBinding”
bindingConfiguration=”nonTxnMsmqBinding”
address=”net.msmq://localhost/private/QueuedWorkflowService/TimeEntryService.xamlx”
contract=”ITimeEntryContract” />
<endpoint
binding=”netMsmqBinding”
bindingConfiguration=”txnMsmqBinding”
address=”net.msmq://localhost/private/QueuedWorkflowServiceTxn/TimeEntryService.xamlx”
contract=”ITimeEntryContract” />
<endpoint
address=”mex”
binding=”mexHttpBinding”
contract=”IMetadataExchange” />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceMetadata httpGetEnabled=”true” />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<netMsmqBinding>
<binding
name=”nonTxnMsmqBinding”
durable=”false”
exactlyOnce=”false”
useActiveDirectory=”false”
queueTransferProtocol=”Native”>
<security mode=”None”>
<message clientCredentialType=”None” />
<transport
msmqAuthenticationMode=”None”
msmqProtectionLevel=”None” />
</security>
</binding>
<binding
name=”txnMsmqBinding”
durable=”true”
exactlyOnce=”true”
useActiveDirectory=”false”
queueTransferProtocol=”Native”>
<security mode=”None”>
<message clientCredentialType=”None” />
<transport
msmqAuthenticationMode=”None”
msmqProtectionLevel=”None” />
</security>
</binding>
</netMsmqBinding>
</bindings>
</system.serviceModel>
<microsoft.applicationServer>
<hosting>
<serviceAutoStart>
<add relativeVirtualPath=”TimeEntryService.xamlx” />
</serviceAutoStart>
</hosting>
</microsoft.applicationServer>
</configuration>
The two important sections are the endpoint and the netMsmqBinding sections. A single service is defined that exposes two MSMQ endpoints, a transactional endpoint and a non-transactional one. This was done to demonstrate the changes required in the netMsmqBinding to support a transactional queue over a non-transactional queue; namely the durable and exactlyOnce attributes. In both cases no security is enabled. I had to do this to get the simplest example to work. Note that the WCF address for the queue does not include a $ suffix on the private queue name and matches the Uri of the service.
We still have some way to go to get this to work, we need a number of services to be installed and running on the workstation:
Services
• Message Queuing (MSMQ)
• Net.Msmq Listener Adapter (NetMsmqActivator)
• Windows Process Activation Service (WAS)
I also ensured that AppFabric was running as this is the easiest way to start the debugging process:
• AppFabric Event Collection Service (AppFabricEventCollectionService)
If you don’t have these services registered on your workstation you will need to go into the ‘Programs and Features’ control panel, then ‘Turn Windows features on or off’ to enable them (Windows 7).
With the services installed and started you need to create a private message queue to map the endpoint to (see : http://msdn.microsoft.com/en-us/library/ms789025.aspx ). The queue name must match the Uri of the service.
The sample is configured to run without security on the queues, i.e. the queues are not authorized. You must allow the anonymous login ‘send’ rights on the queues. If you don’t, the messages will be delivered but the WAS listener will not be able to pick up the messages from the queue.
If you have problems and do not see the message delivered to the correct queue, have a look in the system Dead Letter queues.
You also need to change your VS2010 project to use IIS as the host rather than Cassini. On the project properties dialog, open the Web tab:
As I wanted events from this service to be added to my AppFabric monitoring store, I also added a connection string to the mapped web application and then configured AppFAbric monitoring to use that connection.
And in AppFabric configuration:
Finally you also need to enable the correct protocols on the web application (Manage Application… | Advanced Settings):
I’ve added in net.msmq for queuing support and also net.pipe for the workflow control endpoint.
Make sure that the user the application pool is running as has access to read and write to the queue.
With all the server configured I then wrote a simple WPF test application that used a service reference generated by VS2010, this creates the appropriate client side WCF configuration. The button click handler called the service proxy directly:
private void submitTimeEntryButton_Click(object sender, RoutedEventArgs e) {
using (TimeEntryContractClient proxy = new TimeEntryContractClient(“QueuedTimeEntryContract”)) {
TimeEntry timeEntry = new TimeEntry {
TimekeeperId = Guid.NewGuid(),
MatterId = Guid.NewGuid(),
Duration = new TimeSpan(0, 4, 0, 0)
};
string message = string.Format(“Client [{3}]- TimekeeperId: {0}, MatterId: {1}, Duration: {2}”,
timeEntry.TimekeeperId,
timeEntry.MatterId,
timeEntry.Duration,
DateTime.Now);
proxy.SubmitTimeEntry(timeEntry);
EventLog.WriteEntry(“Queued Service Example”, message, EventLogEntryType.Information);
}
}
And the awesome UI:
Click the button and you get entries in the event log, a client event and the server event:
I made no changes to the code to move from an http endpoint to a MSMQ endpoint, but it’s not as simple as tweaking the config and you’re good to go. I’d love to see some tooling in VS2010 or VS vNext to take some of the pain away from WCF config, similar to the tooling AppFabric adds into IIS. Until that happens, there are plenty of angle brackets to deal with.
Hi – great article. I seem to have a problem at the moment with my WF Control Endpoint. It’s not showing up automatically (under endpoints in AppFabric) despite the fact that net.pipe is enabled correctly, and no instances are appearing under tracked instances or anywhere . Did you explicitly declare the endpoint?
If you have AppFabric installed, have you enabled instance control in the Workflow Host Management settings [‘Manage WCF and WF services’ from the web application context menu]? Also ensure that there are no spaces in ‘Enabled Protocols’ property, it must be ‘http,net.pipe’. Lastly, by default a filter is applied to the AppFabric endpoints to remove system endpoints from the list. If you clear this filter do you see the endpoint?
Hi . I don’t know why but in my particular case I had to explicitly declare the endpoint to make it work. This doesn’t normally happen, I know. Not sure why. Now I have another problem that no matter what I try (i think I have tried everything, including the unrestricted token trick etc.,) I can’t get the listeneractivator to work. It just refuses, and I’ve no idea how to diagnose it.
Actually…scratch that last comment. It all suddenly started working. Being a complete spanner and also getting confused by the array of possible problems, I’d forgotten to enable net.pipe! I restarted IIS, restarted the listener adapter, and now everything works.