I think we’ve moved well past the point of believing that “every service should be a workflow” and other things that I heard when Microsoft was first plugging their Workflow Foundation. However, there still seem to be many cases where executing a visually modeled workflow is useful. Specifically, they are very helpful when you have long running interactions that must retain state. When Microsoft revamped Workflow Services with the .NET 4.0 release, it became really simple to build workflows that were exposed as WCF services. But, despite all the “contract first” hoopla with WCF, Workflow Services were inexplicably left out of that. You couldn’t start the construction of a Workflow Service by designing a contract that described the operations and data payloads. That has all been rectified in .NET 4.5 as now developers can do true contract-first development with Workflow Services. In this blog post, I’ll show you how to build a contract-first Workflow Service, and, include a list of all the WCF contract properties that get respected by the workflow engine.
First off, there is an MSDN article (How to: Create a workflow service that consumes an existing service contract) that touches on this, but there are no pictures and limited details, and my readers demand both, dammit.
To begin with, I created a new Workflow Services project in Visual Studio 2012.
Then, I chose to add a new class directly to the Workflow Services project.
Within this new class filed, named IOrderService, I defined a new WCF service contract that included an operation that processes new orders. You can see below that I have one contract and two data payloads (“order” and “order confirmation”).
namespace Seroter.ContractFirstWorkflow { [ServiceContract( Name="OrderService", Namespace="http://Seroter.Demos")] public interface IOrderService { [OperationContract(Name="SubmitOrder")] OrderConfirmation Submit(Order customerOrder); } [DataContract(Name="CustomerOrder")] public class Order { [DataMember] public int ProductId { get; set; } [DataMember] public int CustomerId { get; set; } [DataMember] public int Quantity { get; set; } [DataMember] public string OrderDate { get; set; } public string ExtraField { get; set; } } [DataContract] public class OrderConfirmation { [DataMember] public int OrderId { get; set; } [DataMember] public string TrackingId { get; set; } [DataMember] public string Status { get; set; } } }
Now which WCF service/operation/data/message/fault contract attributes are supported by the workflow engine? You can’t find that information from Microsoft at the moment, so I reached out to the product team, and they generously shared the content below. You can see that a good portion of the contract attributes are supported, but there are a number of key ones (e.g. callback and session) that won’t make it over. Also, from my own experimentation, you also can’t use the RESTful attributes like WebGet/WebInvoke.
Attribute | Property Name | Supported | Description |
Service Contract | CallbackContract | No | Gets or sets the type of callback contract when the contract is a duplex contract. |
ConfigurationName | No | Gets or sets the name used to locate the service in an application configuration file. | |
HasProtectionLevel | Yes | Gets a value that indicates whether the member has a protection level assigned. | |
Name | Yes | Gets or sets the name for the <portType> element in Web Services Description Language (WSDL). | |
Namespace | Yes | Gets or sets the namespace of the <portType> element in Web Services Description Language (WSDL). | |
ProtectionLevel | Yes | Specifies whether the binding for the contract must support the value of the ProtectionLevel property. | |
SessionMode | No | Gets or sets whether sessions are allowed, not allowed or required. | |
TypeId | No | When implemented in a derived class, gets a unique identifier for this Attribute. (Inherited from Attribute.) | |
Operation Contract | Action | Yes | Gets or sets the WS-Addressing action of the request message. |
AsyncPattern | No | Indicates that an operation is implemented asynchronously using a Begin<methodName> and End<methodName> method pair in a service contract. | |
HasProtectionLevel | Yes | Gets a value that indicates whether the messages for this operation must be encrypted, signed, or both. | |
IsInitiating | No | Gets or sets a value that indicates whether the method implements an operation that can initiate a session on the server(if such a session exists). | |
IsOneWay | Yes | Gets or sets a value that indicates whether an operation returns a reply message. | |
IsTerminating | No | Gets or sets a value that indicates whether the service operation causes the server to close the session after the reply message, if any, is sent. | |
Name | Yes | Gets or sets the name of the operation. | |
ProtectionLevel | Yes | Gets or sets a value that specifies whether the messages of an operation must be encrypted, signed, or both. | |
ReplyAction | Yes | Gets or sets the value of the SOAP action for the reply message of the operation. | |
TypeId | No | When implemented in a derived class, gets a unique identifier for this Attribute. (Inherited from Attribute.) | |
Message Contract | HasProtectionLevel | Yes | Gets a value that indicates whether the message has a protection level. |
IsWrapped | Yes | Gets or sets a value that specifies whether the message body has a wrapper element. | |
ProtectionLevel | No | Gets or sets a value that specified whether the message must be encrypted, signed, or both. | |
TypeId | Yes | When implemented in a derived class, gets a unique identifier for this Attribute. (Inherited from Attribute.) | |
WrapperName | Yes | Gets or sets the name of the wrapper element of the message body. | |
WrapperNamespace | No | Gets or sets the namespace of the message body wrapper element. | |
Data Contract | IsReference | No | Gets or sets a value that indicates whether to preserve object reference data. |
Name | Yes | Gets or sets the name of the data contract for the type. | |
Namespace | Yes | Gets or sets the namespace for the data contract for the type. | |
TypeId | No | When implemented in a derived class, gets a unique identifier for this Attribute. (Inherited from Attribute.) | |
Fault Contract | Action | Yes | Gets or sets the action of the SOAP fault message that is specified as part of the operation contract. |
DetailType | Yes | Gets the type of a serializable object that contains error information. | |
HasProtectionLevel | No | Gets a value that indicates whether the SOAP fault message has a protection level assigned. | |
Name | No | Gets or sets the name of the fault message in Web Services Description Language (WSDL). | |
Namespace | No | Gets or sets the namespace of the SOAP fault. | |
ProtectionLevel | No | Specifies the level of protection the SOAP fault requires from the binding. | |
TypeId | No | When implemented in a derived class, gets a unique identifier for this Attribute. (Inherited from Attribute.) |
With the contract in place, I could then right-click the workflow project and choose to Import Service Contract.
From here, I chose which interface to import. Notice that I can look inside my current project, or, browse any of the assemblies referenced in the project.
After the WCF contract was imported, I got a notice that I “will see the generated activities in the toolbox after you rebuild the project.” Since I don’t mind following instructions, I rebuilt my project and looked at the Visual Studio toolbox.
Nice! So now I could drag this shape onto my Workflow and check out how my WCF contract attributes got mapped over. First off, the “name” attribute of my contract operation (“SubmitOrder”) differed from the name of the operation itself (“Submit”). You can see here that the operation name of the Workflow Service correctly uses the attribute value, not the operation name.
What was interesting to me is that none of my DataContract attributes got recognized in the Workflow itself. If you recall from above, I set the “name” attribute of the DataContract for “Order” to “CustomerOrder” and excluded one of the fields, “ExtraField”, from the contract. However, the data type in my workflow is called “Order”, and I can still access the “ExtraField.”
So maybe these attribute values only get reflected in the external contract, not the internal data types. Let’s find out! After starting the Workflow Service and inspecting the WSDL, sure enough, the “type” of the inbound request corresponds to the data contract attribute (“CustomerOrder”).
In addition, the field (“ExtraField”) that I excluded from the data contract is also nowhere to be found in the type definition.
Finally, the name and namespace of the service should reflect the values I defined in the service contract. And indeed they do. The target namespace of the service is the value I set in the contract, and the port type reflects the overall name of the service.
All that’s left to do is test the service, which I did in the WCF Test Client.
The service worked fine. That was easy. So if you have existing service contracts and want to use Workflow Services to model out the business logic, you can now do so.
One thought