An orchestrated task service encapsulates a body of
business process (workflow) logic and makes it accessible via a
published service contract. This section explores the many ways of
applying Decoupled Contract with WF as a means of establishing orchestrations as standalone and
independently invokable services. We further explore the various
industry standards supported by WF, each of which provides different
implementation options for the creation of orchestrated task services.
WF relies on the industry
standards support provided by WCF and ASMX to position orchestrations as
services. The WCF programming model abstracts transports and policies
into Send and Receive message exchanges and WF translates this model
into corresponding Send and Receive messaging activities. With ASMX,
activities can be published and interacted with via WS-I BasicProfile
compliant workflows.
A Brief History of WF Service Contract Support
Support for creating
technical service contracts improved as WF evolved from version 3.0 to
4.0. With WF 3.0, there was no actual notion of formal contracts.
Workflow containers essentially passed opaque Dictionary types to workflow instances in order to move data from the consumer-side to the workflows being positioned as services.
The options for defining formal service contracts for workflows published as a Web service with WF 3.0 were:
Define the
contract explicitly as a .NET interface and publish the workflow as an
ASMX Web service with the “Publish as Web service” feature in Visual
Studio. This approach worked for contracts with simple message exchange
patterns and limited scalability requirements.
Define the contract as an interface following the requirements for WF’s ExternalDataExchange
services, and publish the workflow manually. This provided broader
support for message exchange patterns and communication protocols, but
could require custom development of the service container in order to
meet scalability and fault tolerance requirements.
Define
the contract explicitly as a .NET interface with WCF contract
attributes and implement all communication with the workflow via the Receive and Send
activities introduced with .NET 3.5. Like the workflow-first option,
this approach took advantage of the benefits of WCF, but provided
greater flexibility for custom service contract design.
Despite these options, under
the covers, WF 3.0 workflows were hosted as in-process services that
did not support strongly typed interfaces. It was with version 3.5 of WF
Workflow Services that support for formal contracts was introduced. The
contract could be defined implicitly with the interface definition
dialog introduced in Visual Studio, thereby creating a “workflow-first”
approach. Visual Studio 2008 added an Interface designer to define a WCF
interface inside the workflow designer and bind the interface to the
new Send and Receive activities.
WF 4.0 introduced
support for actual contract-first style development with regular WCF
service contracts through the use of WCF messaging activities. As a
result, workflows themselves can be strongly-typed. Parameters are
declared as workflow input or output, either in the Workflow Designer (Figure 1) or in code using the InParameter<T>, OutParameter<T>, and InOutParameter<T> types.
These parameters can be
defined as message types declared in the form of WCF contracts in order
to allow for the reuse of contract definitions for in-process services.
Publishing WF Workflows as Web Services and Activities
Because of the diverse options available to apply Decoupled Contract
with WF in support of creating orchestrated task services, and because
of the importance of choosing the correct option based on your
requirements (and the version of WF you are using), we will take the
time to explore the details of the following approaches:
Workflows Published as ASMX Services
Workflows Published via WCF 3.5 Activities
Workflows Published via WCF 4.0 Activities
Workflows Published via ExternalDataExchange Services
Note that the option you choose can have a significant impact on the application of the Standardized Service Contract principle. In an environment where multiple implementations of WF co-exist, there may be the need to consider Dual Protocols as a means of support for disparate WF service contracts within the same service inventory.
Workflows Published as ASMX Services
Visual Studio offers the “Publish
as Web Service” option to expose a body of workflow logic as an ASMX Web
service. It requires the definition of a .NET interface that serves as
the formal contract for the service. Each interface method maps to
either a one-way operation (for methods of type void) or a
request-response operation (for methods with a non-void return type).
Note
WF does not require decorating the interface with any special attributes (such as WebMethod) to publish a workflow as a Web service.
The following interface translates to a request-response operation because the method has a non-void return type:
Example 1.
public interface ICustomerService { CustomerInfoResponse GetCustomerInfo( CustomerInfoRequest request); } [Serializable] [XmlRoot(Namespace= "http://example.org/Service/Customer")] public class CustomerInfoRequest { ... } [Serializable] [XmlRoot(Namespace = "http://example.org/Service/Customer")] public class CustomerInfoResponse { ... }
|
In the preceding example, WebServiceInputActivity, WebServiceOutputActivity and WebServiceFaultActivity bind a workflow to this interface. Each WebServiceInputActivity corresponds to a method in the interface contract as illustrated in Figure 2.
The activity’s InterfaceType and MethodName properties shown in Figure 2
link the activity to the method on the interface. Any method parameters
defined by the interface are added to the activity as properties (Figure 3). You can bind them to dependency properties on the workflow to make them available to all other activities.
The WebServiceOutputActivity and the WebServiceFaultActivity handle responses for request-response message exchanges. Figure 3 shows the WF implementation of the interface in Figure 4.
The WebServiceInput activity is configured to handle the GetCustomerInfo method and stores the received message in a workflow property. In the example from Figure 4, all logic to collect the customer information is factored into a custom activity. If this custom activity succeeds, then the WebServiceOutput activity sends the response message. Otherwise the WebServiceFault sends an appropriate SOAP fault to the caller.
WF also supports publishing workflows with asynchronous interfaces, such as this one where the SubmitOrder operation does not return anything:
Example 2.
public interface IOrderService { void SubmitOrder(OrderDataMessage msg); }
[Serializable] [XmlRoot(Namespace = "http://example.org/Service/Order")] public class OrderDataMessage { ... }
|
Once again, the WebServiceInput activity provides a strongly-typed Web service interface. Figure 5 shows how you can provide a typed service wrapper for an asynchronous service operation.
After you define the .NET
interface and the workflows are developed, you can publish the entire
workflow project to an ASMX Web service by selecting “Publish as Web
Service” from the context menu in the Solution Explorer or from the
Workflow menu (Figure 6).
There are some limitations to
keep in mind with regard to reusability when deciding between the
different options to expose a workflow as a service.
First, the initial release of WF
does not provide tools or activities to support formal contracts that
express policies beyond the WS-I BasicProfile. With the “Publish as Web
Service” feature, workflows are completely decoupled from their external
interface and independent of any particular service host. The contract
definition does not include any details about message encoding or
service policies.
Second, the formal contract
definition for the “Publish as Web service” feature lacks control over
the XML-related aspects of the service contract, such
as the XML namespace of the service, its XML encoding, etc. The
generated Web service honors any XML serialization attributes from System.Xml.Serialization
on message classes and data classes, but it ignores any attributes you
may have put on the interface definition that drives the generation of
the WebService-derived class and methods exposed via the WebMethod attribute.
Take the service definition in the following code fragment:
Example 3.
[System.Web.Services.WebService( Namespace = "http://example.org/QuoteService" )] public interface SimpleOrderService { [System.Web.Services.Protocols.SoapDocumentMethod (RequestNamespace="http://example.org/Quotes" )] ns0.QuoteResponse CreateQuote(ns0.QuoteRequest param1); ns0.ApproveResponse ApproveOrder (ns0.ApproveRequest quoteData); ns0.RejectResponse RejectOrder(ns0.RejectRequest quoteData); } [System.Xml.Serialization.XmlRoot( Namespace="http://example.org/Quotes" )] public class QuoteRequest { public Product quoteData; public Customer customerData; }
|
The service interface and the QuoteRequest
class are both decorated with attributes to control the XML namespaces
of the service and the request data type in the serialized SOAP
messages. The generated service, however, does not include the namespace
definition for the service. Only the namespace on the CreateQuote element is present:
Example 4.
<soap:Envelope xmlns:xsi= "http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"> <soap:Body> <CreateQuote xmlns="http://tempuri.org/"> <param1 xmlns="http://example.org/Quotes2"> <quoteData /> <customerData /> </param1> </CreateQuote> </soap:Body> </soap:Envelope>
|
Because the ability to define namespaces and service types is important to the application of Canonical Schema , let’s cover a work-around whereby we side-step the “Publish as Web Service” option by building the Web service manually.
Start with a Web service class that follows the requirements of WorkflowWebHostingModule to expose a workflow as an ASMX Web service such as the one shown here:
Example 5.
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1, EmitConformanceClaims = true)] public class OrderService_WebService : System.Workflow.Activities.WorkflowWebService { public OrderService_WebService() : base(typeof(OrderService)) { } [WebMethodAttribute(Description = "SubmitOrder", EnableSession = false)] [SoapDocumentMethod( RequestNamespace="http://example.org/service", ResponseNamespace="http://example.org/service" )] public virtual void SubmitOrder( WorkflowWebService.OrderDataMessage msg) { this.Invoke(typeof(WorkflowWebService.IOrderService), "SubmitOrder", true, new object[] {msg}); } }
|
The service class derives from System.Workflow.Activities.WorkflowWebService and each WebMethod marshals the request to the workflow via the Invoke method provided by that base class (similar to what the auto-generated service class would do).
By implementing your own service call, you can add attributes from the System.Web.Services.Protocols namespace to shape the XML of the request and response messages. For example, in the preceding code fragment, we added a SoapDocumentMethod attribute to the XML namespace http://example.org/service instead of the auto-generated namespace for the service’s request and response messages.
Next, you need an .asmx file for ASP.NET to expose the Web service. If you compile the WorkflowWebService class into a separate assembly, the ASMX file would look like this:
<%@WebService Class=
"WorkflowOrchestration.OrderService_WebService"%>
The “Publish as Web Service” option configures the WorkflowWebHostingModule in the Web service’s App.Config file for you. If you choose to bypass this feature and publish the workflow yourself, you have to complete the following steps:
1. | Compile the workflow and the WorkflowWebService derived class into a .NET assembly.
|
2. | Create an .asmx file to define an endpoint for the WorkflowWebService derived class
|
3. | Add the WorkflowRuntime section definition for the ASP.NET runtime to parse the WF-specific settings.
|
4. | Configure the ManualWorkflowSchedulerService and the DefaultWorkflowCommitWorkBatchService services for proper execution of the workflow in the ASP.NET environment.
|
5. | Add references to the WF assemblies to allow dynamic compilation of the .asmx file.
|
6. | Configure the WorkflowWebHostingModule, which controls the life cycle of the WorkflowHost in the ASP.NET application.
|
The following example shows the content you need to add to the configuration file:
Example 6.
<configSections> <section name="WorkflowRuntime" type= "System.Workflow.Runtime.Configuration. WorkflowRuntimeSection,System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" /> </configSections> <WorkflowRuntime Name="WorkflowServiceContainer"> <Services> <add type="System.Workflow.Runtime. Hosting.ManualWorkflowSchedulerService, System.Workflow.Runtime,Version=3.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35"/> <add type="System.Workflow.Runtime. Hosting.DefaultWorkflowCommitWorkBatchService, System.Workflow.Runtime,Version=3.0.0.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35"/> </Services> </WorkflowRuntime> <system.web> <compilation debug="false"> <assemblies> <add assembly="System.Design,Version=2.0.0.0, Culture=neutral,PublicKeyToken=B03F5F7F11D50A3A"/> <add assembly="System.Drawing.Design,Version=2.0.0.0, Culture=neutral,PublicKeyToken=B03F5F7F11D50A3A"/> <add assembly="System.Transactions,Version=2.0.0.0, Culture=neutral,PublicKeyToken=B77A5C561934E089"/> <add assembly="System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add assembly="System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add assembly="System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/> <add assembly="Microsoft.Build.Tasks,Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/> <add assembly="System.Messaging,Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/> <add assembly="System.Runtime.Remoting, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> <add assembly="System.DirectoryServices, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/> <add assembly="System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B77A5C561934E089"/> <add assembly="Microsoft.Build.Utilities, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/> <add assembly="Microsoft.Build.Framework, Version=2.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/> </assemblies> </compilation> ... <httpModules> <add type="System.Workflow.Runtime. Hosting.WorkflowWebHostingModule, System.Workflow.Runtime,Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="WorkflowHost"/> </httpModules> </system.web> </configuration>
|
WF
does not provide a solution for identifying the target workflow instance
for a received message based on the message content. The WorkflowWebHostingModule
implements a message correlation solution, but it relies on HTTP
Cookies rather than message content, which requires cookie-enabled
service consumers (and is an approach discouraged in the WS-I Basic
Profile).
A further limitation to keep in
mind when selecting ASMX as the hosting environment for WF
orchestrations is associated with threading. The ASMX workflow hosting
module does not handle overlapping response-request operations because
of the way the WorkflowWebHostingModule handles threading. That means you can’t receive messages for an instance while a request-response operation is pending.
The WorkflowWebHostingModule
executes the workflow on the ASP.NET thread that handles the Web
request—that thread is blocked until the workflow sends the response to
the initial request. If the Web server receives another request for that
same workflow instance before the outstanding request is sent, the
service responds with a SOAP fault.
Unfortunately, you cannot change this behavior without writing your own replacement of the WorkflowWebHostingModule because this module controls the WorkflowHost and manages the WorkflowHost’s life cycle. You can still choose different persistence in the Web service’s Web.Config file, but the WorkflowWebHostingModule
hardwires the threading and the scheduling services when you rely on
Visual Studio to publish a workflow as an ASMX service. With that
limitation in mind, it can be more desirable to design each interaction
with the workflow using asynchronous operations.