Capabilities and Limitations of “Contract First” Feature in Microsoft Workflow Services 4.5

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.

2012.10.12wf01

Then, I chose to add a new class directly to the Workflow Services project.

2012.10.12wf02

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.

2012.10.12wf03

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.

2012.10.12wf04

 

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.

2012.10.12wf05

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.

2012.10.12wf06

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.”

2012.10.12wf07

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”).

2012.10.12wf09

In addition, the field (“ExtraField”) that I excluded from the data contract is also nowhere to be found in the type definition.

2012.10.12wf10

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.

2012.10.12wf11

2012.10.12wf12

 

All that’s left to do is test the service, which I did in the WCF Test Client.

2012.10.12wf13

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.



Categories: .NET, SOA, WCF/WF

1 reply

Trackbacks

  1. Distributed Weekly 177 — Scott Banwart's Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: