Developing Connected Systems
This article examines portions of the DinnerNow.net sample application to illustrate the connected systems concepts that are discussed in the companion topics, Understanding Windows Communication Foundation and Understanding Windows Workflow Foundation.
Overview
DinnerNow.net is a sample application that revolves around a fictitious Web site where customers can search for and place a food order, split between multiple restaurants, for delivery to their home or office. In addition to the customer Web site, a kiosk is provided for restaurants to manage orders, and a Microsoft Management Console (MMC) snap-in allows viewing of the Windows Communication Foundation (WCF) services and Windows Foundation (WF) workflows used in the application.
DinnerNow.net demonstrates several new Microsoft technologies, including WCF, WF, Windows Process Activation Service (WAS), Internet Information Services (IIS) 7.0, ASP.NET Ajax Extensions, Linq, Windows Presentation Foundation (WPF), and Windows PowerShell.
This article's examination concentrates on DinnerNow.net's use of WCF services and WF workflows in the ordering process, from the initial receipt of the order to its destination at the restaurant.
The source code for the application can be downloaded from CodePlex/DinnerNow/Releases. The code examples shown in this article are from the DinnerNow\solution\DinnerNow - ServicePortfolio2 folder.
DinnerNow
The ordering process in DinnerNow.net is modeled using two workflows, ProcessOrder and RestaurantOrderWorkflow. ProcessOrder is a sequential workflow that starts when an order is received from the DinnerNow.net Web site and ends when the workflow is notified that the order has been delivered or canceled.
An order can consist of menu items from multiple restaurants. The ProcessOrder workflow splits an incoming order into restaurant specific orders and passes each one to a new instance of the RestaurantOrderWorkflow workflow. RestaurantOrderWorkflow is a state machine workflow that follows an order through the restaurant until it receives a delivery or cancellation notification.
The two workflows use the SendActivity and ReceiveActivity classes, new for .NET Framework 3.5, to communicate through WCF services.
The Visual Studio Workflow Designer was used to graphically create the workflows based on Visual Studio workflow templates. The designer generates an eXtensible Application Markup Language (XAML, .xoml) file and an associated code-behind file containing the implementation logic. For more information, see Using Workflow Markup.
The following sections describe the ordering process.
Hosting the services and workflows
Placing the order
ProcessOrder workflow
Receiving the order
IProcessOrder service contract
receiveNewOrder activity (implements Processorder operation contract)
saveOrderActivity1 activity
Splitting the order
CreateRestaurantOrdersCode activity
replicatorActivity1 activity
restaurantOrderContainer1 activity
RestaurantOrderWorkflow workflow
Communicating the order between workflows
IUpdateOrder service contract
sendActivity1 activity (ProcessOrder workflow)
receiveOrder activity (RestaurantOrderWorkflow workflow)
Order complete notification
Note: The included code examples, taken from DinnerNow.net's source code, are usually not complete and are modified for readability and succinctness.
Hosting the Services and Workflows
DinnerNow.net hosts the WCF services and WF workflows in Windows Process Activation Service. The workflow services are hosted by instances of the WorkflowServiceHost class that are created by the WorkflowServiceHostFactory class, as specified by the following service files (.svc). These files are from the DinnerNow.ServiceHost project and are deployed to the Service directory of the DinnerNow virtual Web directory.
OrderProcess.svc
<%@ ServiceHost Service="DinnerNow.OrderProcess.ProcessOrder" Factory="System.ServiceModel.Activation.WorkflowServiceHostFactory" %>
UpdateOrderService.svc
<%@ ServiceHost Service="DinnerNow.OrderProcess.RestaurantOrderWorkflow" Factory="System.ServiceModel.Activation.WorkflowServiceHostFactory" %>
The endpoints for the workflow services are defined in the <system.serviceModel> section of the Web.config file for the host, as shown in the following XML example. Note the blank endpoint addresses: the address of the service is determined by the address of the virtual Web directory and the location of the corresponding service file in that directory. Endpoints with a "mex" address allow a client to retrieve metadata information about the corresponding service.
<client>
<endpoint name="WSHttpContextBinding_IProcessOrder"
address="https://localhost/DinnerNow/service/orderprocess.svc"
binding="wsHttpContextBinding"
bindingConfiguration="WSHttpContextBinding_IProcessOrder"
contract="DinnerNow.OrderProcess.IProcessOrder" />
<endpoint name="WSHttpContextBinding_IUpdateOrder"
address="https://localhost/DinnerNow/service/OrderUpdateService.svc"
binding="wsHttpContextBinding"
bindingConfiguration="WSHttpContextBinding_IUpdateOrder"
contract="DinnerNow.OrderProcess.IUpdateOrder" />
</client>
<services>
<service name="DinnerNow.OrderProcess.ProcessOrder">
<endpoint
address=""
binding="wsHttpContextBinding"
contract="DinnerNow.OrderProcess.IProcessOrder" />
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
<service name="DinnerNow.OrderProcess.RestaurantOrderWorkflow">
<endpoint
address=""
binding="wsHttpContextBinding"
contract="DinnerNow.OrderProcess.IUpdateOrder" />
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange" />
</service>
</services>
Placing the Order
On the DinnerNow.net Web site, when a customer adds an order to the cart or checks out, an instance of the DinnerNow.Business.Data.Order class is created.
The Order class is decorated with the DataContractAttribute attribute, and its OrderItems property is decorated with the DataMemberAttribute attribute, as shown in the following code example, which allows the data to be exchanged (serialized/deserialized) through WCF services. The OrderItems member is a collection of OrderItem instances. Each OrderItem instance represents one menu item from a specific restaurant. The code is from Order.cs in the Data folder of the DinnerNow.Business project.
namespace DinnerNow.Business.Data
{
[DataContract]
[Serializable]
public class Order
{
[DataMember]
public OrderItem[] OrderItems { get; set; }
//...
}
}
Once an order is submitted, the order is communicated to the ProcessOrder workflow using the ProcessOrder service.
ProcessOrder Workflow
ProcessOrder is a SequentialWorkflowActivity type that communicates using WCF services. ProcessOrder is authored using the code-separation style, which creates a .cs and a .xoml file. The workflow was created in the Visual Studio Workflow Designer as shown in the following screenshot. The screenshot is the Designer view of the ProcessOrder.xoml file in the DinnerNow.OrderProcess project under the Workflow folder.
Each rectangle represents an activity in the workflow. Note the green arrows on the left side of the Processorder and RestaurantOrderComplete rectangles. These arrows signify a ReceiveActivity activity; the corresponding name refers to the operation contract the activity implements (specified by the TypedOperationInfo element in the following XML). The dual left/right arrows in Processorder signify that a reply message is sent to the client. The single right arrow in RestaurantOrderComplete indicates a one way contract; no reply message is sent to the client. The arrows on the right side of the StartRestaurantOrder rectangle signify a SendActivity activity.
The following XML (condensed from ProcessOrder.xoml) shows the corresponding code view of the activities that make up the ProcessOrder workflow.
<SequentialWorkflowActivity Name="ProcessOrder"
Class="DinnerNow.OrderProcess.ProcessOrder">
<ReceiveActivity Name="receiveNewOrder" />
<ReceiveActivity.ServiceOperationInfo>
<TypedOperationInfo Name="Processorder"
ContractType="{Type DinnerNow.OrderProcess.IProcessOrder}" ...
<SaveOrderActivity Name="saveOrderActivity1" />
<CodeActivity Name="CreateRestaurantOrdersCode" />
<ReplicatorActivity Name="replicatorActivity1">
<RestaurantOrderContainer Name="restaurantOrderContainer1">
<SendActivity Name="sendActivity1" />
<SendActivity.ServiceOperationInfo>
<TypedOperationInfo Name="StartRestaurantOrder" ...
<ReceiveActivity Name="receiveActivity1" />
<ReceiveActivity.ServiceOperationInfo>
<TypedOperationInfo Name="RestaurantOrderComplete" ...
</RestaurantOrderContainer>
</ReplicatorActivity>
</SequentialWorkflowActivity>
All the activities are from the default WF activity library except SaveOrderActivity and RestaurantOrderContainer. Note the ReceiveActivity and SendActivity activities, which are the activities that communicate using WCF services. The order is received by receiveNewOrder, sendActivity1 sends each split order to its proper restaurant, and receiveActivity1 receives a notification from the restaurant when the order is completed.
As shown in the preceding examples, an order flows through the ProcessOrder workflow in the following steps:
receiveNewOrder - receives the order.
saveOrderActivity1 - saves the order to the DinnerNow database.
CreateRestaurantOrdersCode - creates an array of restaurant based orders.
replicatorActivity1 - repeats the following steps for each restaurant order.
restaurantOrderContainer1 - a container for the order.
sendActivity1 (StartRestaurantOrder) - sends the order to RestaurantOrderWorkflow.
receiveActivity1 (RestaurantOrderComplete) - receives notification from RestaurantOrderWorkflow that the order is complete.
The following code listing shows the properties declared by the ProcessOrder class. The flow of an order through these properties and the preceding activities is discussed in the following sections. The code is from ProcessOrder.xoml.cs.
using DinnerNow.Business.Data;
public partial class ProcessOrder : SequentialWorkflowActivity
{
public static DependencyProperty incomingOrderProperty =
DependencyProperty.Register("incomingOrder",
typeof(DinnerNow.Business.Data.Order),
typeof(DinnerNow.OrderProcess.ProcessOrder));
public static DependencyProperty orderIDProperty =
DependencyProperty.Register("orderID",
typeof(System.Guid),
typeof(DinnerNow.OrderProcess.ProcessOrder));
public DinnerNow.Business.Data.Order IncomingOrder { ... }
public Guid orderID { ... }
public RestaurantOrder[] Order { get; set; }
}
Receiving the Order
The order placed by the customer is sent to the ProcessOrder workflow using the IProcessOrder.Processorder operation contract. Once received, the order is saved to the DinnerNow database. The following sections describe this process.
IProcessOrder contract
receiveNewOrder activity (implements Processorder operation contract)
saveOrderActivity1 activity
IProcessOrder Contract
The ServiceContractAttribute and OperationContractAttribute attributes, as shown in the following code example, define the IProcessOrder interface as a service contract. The IsOneWay property of the RestaurantOrderComplete operation indicates that the operation does not return a reply message. The code is from IProcessOrder.cs in the Interfaces folder of the DinnerNow.Services project.
[ServiceContract]
interface IProcessOrder
{
[OperationContract(IsInitiating = true)]
void Processorder(DinnerNow.Business.Data.Order newOrder);
[OperationContract(IsOneWay = true)]
void RestaurantOrderComplete();
}
receiveNewOrder Activity
The receiveNewOrder activity of the ProcessOrder workflow, shown in the following XML, implements the preceding IProcessOrder.Processorder operation contract and receives the customer's order.
<ReceiveActivity Name="receiveNewOrder">
<ReceiveActivity.ServiceOperationInfo>
<TypedOperationInfo
ContractType="{Type DinnerNow.OrderProcess.IProcessOrder}"
Name="Processorder" />
</ReceiveActivity.ServiceOperationInfo>
<ReceiveActivity.ParameterBindings>
<WorkflowParameterBinding ParameterName="newOrder">
<WorkflowParameterBinding.Value>
<ActivityBind Name="ProcessOrder" Path="IncomingOrder" />
</WorkflowParameterBinding.Value>
</WorkflowParameterBinding>
</ReceiveActivity.ParameterBindings>
</ReceiveActivity>
The ReceiveActivity.ServiceOperationInfo property, through the TypedOperationInfo instance, specifies the service and operation contract that receiveNewOrder implements, which in this case is DinnerNow.OrderProcess.IProcessOrder.Processorder.
The ReceiveActivity.ParameterBindings property is the collection of bindings between the service operation parameters and properties of the workflow, which in this case contains a single bound member. WorkflowParameterBinding creates a binding between the newOrder parameter of the Processorder method and the IncomingOrder property of the ProcessOrder class.
When an order is placed on the DinnerNow.net Web site, WCF sends a message containing the order details (the Order instance as the argument to the newOrder parameter) to the ProcessOrder service. When the message is received, receiveNewOrder executes and the value of the newOrder parameter is passed to the IncomingOrder property of the ProcessOrder class.
saveOrderActivity1 Activity
When receiveNewOrder finishes, the saveOrderActivity1.Execute method is run, which saves the order to the DinnerNow SQL database. SaveOrderActivity obtains the value of its IncomingOrder and orderID properties through activity binding.
SaveOrderActivity declares a DependencyProperty, IncomingOrderProperty, as backing for its IncomingOrder property, as shown in the following code, which is from SaveOrderActivity.cs in the DinnerNow.WorkflowActivities project under the Workflow folder. The orderIDProperty dependency property is similarly declared but not shown.
public partial class SaveOrderActivity : Activity
{
public static DependencyProperty IncomingOrderProperty =
DependencyProperty.Register(
"IncomingOrder",
typeof(DinnerNow.Business.Data.Order),
typeof(SaveOrderActivity));
public DinnerNow.Business.Data.Order IncomingOrder
{
get
{
return (DinnerNow.Business.Data.Order)base.GetValue(
SaveOrderActivity.IncomingOrderProperty);
}
set
{
base.SetValue(SaveOrderActivity.IncomingOrderProperty, value);
}
}
}
The saveOrderActivity1.IncomingOrder property is bound to the ProcessOrder.IncomingOrder property and the saveOrderActivity1.orderID property is bound to the ProcessOrder.orderID property, as shown in the following XML from ProcessOrder.xoml.
<SaveOrderActivity Name="saveOrderActivity1"
IncomingOrder="{ActivityBind ProcessOrder, Path=IncomingOrder}"
orderID="{ActivityBind ProcessOrder,Path=orderID}" />
When saveOrderActivity1 executes, its IncomingOrder and orderID properties receive their value from the IncomingOrder and orderID properties of the ProcessOrder class.
Splitting the Order
After the order is saved to the DinnerNow database, the order is split into restaurant specific orders and processed by a ReplicatorActivity activity. The following sections describe this process.
CreateRestaurantOrdersCode activity
replicatorActivity1 activity
restaurantOrderContainer1 activity
CreateRestaurantOrdersCode Activity
After saveOrderActivity1 executes, control passes to CreateRestaurantOrdersCode, a CodeActivity type, which causes the ProcessOrder.CreateRestaurantOrders method to execute. The code is from ProcessOrder.xoml.cs.
<CodeActivity Name="CreateRestaurantOrdersCode"
ExecuteCode="CreateRestaurantOrders" />
CreateRestaurantOrders queries the DinnerNow database on the ProcessOrder.orderID property, creates an array of RestaurantOrder instances, and sets the array to the ProcessOrder.Order property. Next, each restaurant specific order is sent to its restaurant for preparation.
replicatorActivity1 Activity
The replicator activity processes multiple instances of its child activity, restaurantOrderContainer1, in parallel as specified by the ExecutionType attribute. The InitialChildData property, an IList type, of replicatorActivity1 is bound to the ProcessOrder.Order property. The replicator activity enumerates through the collection of RestaurantOrder instances, passing each one to the OrderToSend property of a new instance of restaurantOrderContainer1. The following XML is from ProcessOrder.xoml.
<ReplicatorActivity
Name="replicatorActivity1"
ExecutionType="Parallel"
InitialChildData="{ActivityBind ProcessOrder, Path=Order}">
<RestaurantOrderContainer
Name="restaurantOrderContainer1"
OrderToSend="{Null}">
<SendActivity Name="sendActivity1" />
<ReceiveActivity Name="receiveActivity1" />
</RestaurantOrderContainer>
</ReplicatorActivity>
restaurantOrderContainer1 Activity
Each instance of restaurantOrderContainer1 contains a SendActivity that communicates the RestaurantOrder to a new instance of RestaurantOrderWorkflow, and a ReceiveActivity that receives notification from this instance when the order is completed.
RestaurantOrderWorkflow
RestaurantOrderWorkflow is a StateMachineWorkflowActivity that models the food-preparation process after the order reaches the restaurant. This article's examination of RestaurantOrderWorkflow is limited to its communications with the ProcessOrder workflow; the following XML (condensed from RestaurantOrderWorkflow.xoml) shows only the activities relevant to those communications. The file is located in the DinnerNow.OrderProcess project under the Workflow folder.
<StateMachineWorkflowActivity Name="RestaurantOrderWorkflow">
<StateActivity Name="RestaurantOrderWorkflowInitialState">
<EventDrivenActivity Name="ReceiveRestaurantOrder">
<ReceiveActivity Name="receiveOrder">
<ReceiveActivity.ServiceOperationInfo>
<TypedOperationInfo Name="StartRestaurantOrder"
ContractType="{Type DinnerNow.OrderProcess.IUpdateOrder}" ...
</EventDrivenActivity>
</StateActivity>
<StateActivity Name="OrderDelivered">
<StateInitializationActivity Name="OrderDeliveredInitialization">
<SendActivity Name="restaurantOrderComplete" />
<SendActivity.ServiceOperationInfo>
<TypedOperationInfo Name="RestaurantOrderComplete"
ContractType="{Type DinnerNow.OrderProcess.IProcessOrder}" ...
</StateInitializationActivity>
</StateActivity>
</StateMachineWorkflowActivity>
All shown activities are from the default WF activity library except UpdateOrderStatusActivity. Note the ReceiveActivity and SendActivity activities. The restaurant order is received from the ProcessOrder workflow by receiveOrder, and restaurantOrderComplete notifies the ProcessOrder workflow when the order is delivered.
The Visual Studio Workflow Designer view of the RestaurantOrderWorkflow.xoml file is shown in the following screenshot.
Communicating the Order Between Workflows
The two workflows communicate using the IUpdateOrder service contract as described in the following sections.
IUpdateOrder contract
sendActivity1 activity (ProcessOrder workflow)
receiveOrder activity (RestaurantOrderWorkflow workflow)
IUpdateOrder Contract
As shown in the following example, the ServiceContractAttribute and OperationContractAttribute attributes define the IUpdateOrder interface as a service contract. The code is from IUpdateOrder.cs in the DinnerNow.OrderProcess project under the Workflow folder.
using System.Collections.Generic;
using DinnerNow.Business.Data;
[ServiceContract]
interface IUpdateOrder
{
[OperationContract(IsInitiating = true)]
void StartRestaurantOrder(RestaurantOrder order,
Dictionary<string, string> context);
[OperationContract(IsOneWay=true)]
void OrderReadyForPickup(RestaurantOrder order);
[OperationContract(IsOneWay = true)]
void OrderPickedUp(RestaurantOrder order, Guid deliveryId);
[OperationContract(IsOneWay = true)]
void OrderDelivered(RestaurantOrder order);
}
sendActivity1 Activity (ProcessOrder Workflow)
The ProcessOrder workflow sends an order to RestaurantOrderWorkflow using sendActivity1, a SendActivity type, and the preceding StartRestaurantOrder operation contract, as shown in the following XML.
<RestaurantOrderContainer Name="OrderToProcess"
SendContext="{Null}"
RestaurantOrder="{Null}" />
<SendActivity Name="sendActivity1" AfterResponse="ResetContext">
<SendActivity.ServiceOperationInfo>
<TypedOperationInfo Name="StartRestaurantOrder"
ContractType="{Type DinnerNow.OrderProcess.IUpdateOrder}" />
</SendActivity.ServiceOperationInfo>
<SendActivity.ChannelToken>
<ChannelToken Name="sendToken"
OwnerActivityName="restaurantOrderContainer1"
EndpointName="WSHttpContextBinding_IUpdateOrder" />
</SendActivity.ChannelToken>
</SendActivity>
The SendActivity.ServiceOperationInfo property defines the service and operation contract that sendActivity1 fulfills, which is DinnerNow.OrderProcess.IUpdateOrder.StartRestaurantOrder.
The SendActivity.ChannelToken property specifies the name of the endpoint for the service that sendActivity1 communicates over, which corresponds to the RestaurantOrderWorkflow service, as shown by the following XML from the host Web.config and OrderUpdateService.svc files.
Web.config:
<client>
<endpoint name="WSHttpContextBinding_IUpdateOrder"
address=https://localhost/DinnerNow/service/OrderUpdateService.svc
binding="wsHttpContextBinding"
bindingConfiguration="WSHttpContextBinding_IUpdateOrder"
contract="DinnerNow.OrderProcess.IUpdateOrder">
</endpoint>
<client>
OrderUpdateService.svc:
<%@ServiceHost Service="DinnerNow.OrderProcess.RestaurantOrderWorkflow" %>
receiveOrder Activity (RestaurantOrderWorkflow Workflow)
RestaurantOrderWorkflow receives an order using receiveOrder, a ReceiveActivity type, and the StartRestaurantOrder operation contract, as shown in the following XML.
<ReceiveActivity Name="receiveOrder">
<ReceiveActivity.ServiceOperationInfo>
<TypedOperationInfo Name="StartRestaurantOrder"
ContractType="{Type DinnerNow.OrderProcess.IUpdateOrder}" />
</ReceiveActivity.ServiceOperationInfo>
<ReceiveActivity.ParameterBindings>
<WorkflowParameterBinding ParameterName="order">
<WorkflowParameterBinding.Value>
<ActivityBind Name="RestaurantOrderWorkflow"
Path="orderToProcess" />
</WorkflowParameterBinding.Value>
</WorkflowParameterBinding>
<WorkflowParameterBinding ParameterName="context">
<WorkflowParameterBinding.Value>
<ActivityBind Name="RestaurantOrderWorkflow"
Path="updateOrderStatusActivity4_conversation1" />
</WorkflowParameterBinding.Value>
</WorkflowParameterBinding>
</ReceiveActivity.ParameterBindings>
<CodeActivity Name="codeActivity1"
ExecuteCode="AcceptOrderCode" />
</ReceiveActivity>
RestaurantOrderWorkflow's receiveOrder activity implements the same contract that is specified in ProcessOrder's sendActivity1. In this case, the order and context parameters of the StartRestaurantOrder method are bound to the orderToProcess and updateOrderStatusActivity4_conversation1 properties, respectively, of RestaurantOrderWorkflow.
The receiveOrder activity is a child of an EventDrivenActivity activity, ReceiveRestaurantOrder, which starts to execute when a message is received from the ProcessOrder workflow. The message sent by WCF contains the values for the order and context parameters, which are passed to the orderToProcess and updateOrderStatusActivity4_conversation1 properties of RestaurantOrderWorkflow.
Order Complete Notification
RestaurantOrderWorkflow is notified through a WCF service when its order is delivered. In the reverse of the preceding communication pattern, RestaurantOrderWorkflow in turn uses its restaurantOrderComplete SendActivity and the IProcessOrder.RestaurantOrderComplete operation to notify receiveActivity1 of the ProcessOrder workflow that the order is delivered.
See Also
Concepts
Understanding Windows Communication Foundation
Understanding Windows Workflow Foundation
Other Resources
Getting Started with Microsoft Windows Workflow Foundation: A Developer Walkthrough
Integrating Windows Workflow Foundation and Windows Communication Foundation