做为微软最新技术应用的DEMO。dinnernow使用了: IIS7, ASP.NET Ajax Extensions, LINQ, WCF,
WF,WPF,Windows PowerShell, Card Space以及 .NET Compact Framework. 本文将会继续订餐流程,来讨论关于WF(Windows Work Flow Foundation)状态机, 在"订单"这一应用场景中的设计思路:)
继续上一篇中的关于SendActivity的讨论,目前已经完成了订单的创建工作,下面就是要激活该定单流程的时候了.首先请先双击打开ProcessOrder.xoml文件,找到里面的sendActivity1,它是一个SendActivity, 在这个Activity上面击右键属性,如下图:
图中的一个非常重要的属性就是ServiceOperationInfo, 它定义了要调用的服务,这里它的属性值为:
DinnerNow.OrderProcess.IUpdateOrder.StartRestaurantOrder.而这个StartRestaurantOrder操作又是什么东西呢.看来我们还有必要再去检查一下DinnerNow.ServiceHost项目下的 web.config文件,发现如下服务配置节点:
看来要去RestaurantOrderWorkflow中去找StartRestaurantOrder方法,而RestaurantOrderWorkflow.xoml本身是一个状态机工作流,如下图:
注:如果大家对状态机工作流不清楚,可以参考这篇文章《WF编程》系列之34 - 基本活动:状态活动
其中黑线箭头就是定单的流转方向.我们在当前状态机上击右键属性,查看该状态机的设置属性如下图所示:
其中的:
InitialStateName代表初始状态,因为状态机必有一个初始状态,这里它的属性值为:
RestaurantOrderWorkflowInitialState,
CompletedStateName代表工作流的结束状态,这里的值为OrderComplete
当然光看这些还是无法知道StartRestaurantOrder方法的定义,这时我们要在RestaurantOrderWorkflow.xoml上击右键,选择"打开方式",在弹出窗口中选择"XML 编辑器", 在XML中找到下面的节点信息:
< EventDrivenActivity x:Name = " ReceiveRestaurantOrder " >
< ns0:ReceiveActivity x:Name = " receiveOrder " CanCreateInstance = " True " >
< ns0:ReceiveActivity.ServiceOperationInfo >
< ns0:TypedOperationInfo Name = " StartRestaurantOrder " ContractType = " {x:Type DinnerNow.OrderProcess.IUpdateOrder} " />
ns0:ReceiveActivity.ServiceOperationInfo >
< ns0: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 >
ns0:ReceiveActivity.ParameterBindings >
ns0:ReceiveActivity >
< ns1:UpdateOrderStatusActivity orderStatus = " {ActivityBind RestaurantOrderWorkflow,Path=orderStatus} " IncomingOrder = " {ActivityBind RestaurantOrderWorkflow,Path=orderToProcess} " conversation = " {ActivityBind RestaurantOrderWorkflow,Path=updateOrderStatusActivity4_conversation1} " x:Name = " updateOrderStatusActivity4 " />
< SetStateActivity x:Name = " setStateActivity3 " TargetStateName = " OrderCooking " />
EventDrivenActivity >
StateActivity >
这里需要解释一下,上面代码的第一行就是我们看到的状态机的初始化活动的名称,即这个StateActivity就是初始化活动,而EventDrivenActivity x:Name="ReceiveRestaurantOrder"代表当发生ReceiveRestaurantOrder事件时即启动当前的状态活动并将当前的状态转换到下一个新的状态(即上面代码中的TargetStateName="OrderCooking"属性值).当然状态机本身是需要有实例来运行的,所以CanCreateInstance="True".
接下来我们看到了下面这一行代码:
到这里,我们找到了StartRestaurantOrder方法的声明位置,那StartRestaurantOrder运行代码又在何处呢,其实我们可以从上面代码中找到如下一行代码:
它指示当前状态初始化后要执行UpdateOrderStatusActivity.
注:该类位于Workflow\UpdateOrderStatusActivity.cs文件,见下面代码:
{
public static DependencyProperty IncomingOrderProperty = DependencyProperty.Register( " IncomingOrder " , typeof (DinnerNow.Business.Data.RestaurantOrder), typeof (UpdateOrderStatusActivity));
public static DependencyProperty orderStatusProperty = DependencyProperty.Register( " orderStatus " , typeof (System.String), typeof (UpdateOrderStatusActivity));
public static DependencyProperty conversationProperty = DependencyProperty.Register( " conversation " , typeof (System.Collections.Generic.Dictionary < string , string > ), typeof (UpdateOrderStatusActivity));
[BrowsableAttribute( true )]
[CategoryAttribute( " Parameters " )]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public System.Collections.Generic.Dictionary < string , string > conversation
{
get
{
return ((System.Collections.Generic.Dictionary < string , string > )( base .GetValue(UpdateOrderStatusActivity.conversationProperty)));
}
set
{
base .SetValue(UpdateOrderStatusActivity.conversationProperty, value);
}
}
[Description( " Restaurant Order " )]
[Browsable( true )]
[Category( " Order " )]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public DinnerNow.Business.Data.RestaurantOrder IncomingOrder
{
get
{
return ((DinnerNow.Business.Data.RestaurantOrder)( base .GetValue(UpdateOrderStatusActivity.IncomingOrderProperty)));
}
set
{
base .SetValue(UpdateOrderStatusActivity.IncomingOrderProperty, value);
}
}
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
[BrowsableAttribute( true )]
[CategoryAttribute( " Parameters " )]
public string orderStatus
{
get
{
return ((System.String)( base .GetValue(UpdateOrderStatusActivity.orderStatusProperty)));
}
set
{
base .SetValue(UpdateOrderStatusActivity.orderStatusProperty, value);
}
}
public UpdateOrderStatusActivity()
{
InitializeComponent();
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
OrderService service = new OrderService();
service.UpdateOrderStatus(IncomingOrder, orderStatus, this .WorkflowInstanceId);
return ActivityExecutionStatus.Closed;
}
}
上面代码中的conversation属性和IncomingOrder属性所绑定的就是我在上一篇文章所说的SendActivity所发送过来的参数,其中conversation就是那个Context上下文.当然这里还有一个属性orderStatus,其实它的属性值是在相应的StateActivity中已被设置好了.以当前的"RestaurantOrderWorkflowInitialState"为例,其属性值为:
orderStatus="{ActivityBind RestaurantOrderWorkflow,Path=orderStatus}"
其中的Path=orderStatus即指向RestaurantOrderWorkflow.xoml.cs文件中的属性声明:
public static string orderStatus = "New Order";
这样当运行上面的Execute方法之后,当前订单的状态就会更新为New Order.
上面代码中的UpdateOrderStatus方法声明如下:
{
using (Business.OrderProcessing op = new DinnerNow.Business.OrderProcessing())
{
return op.UpdateOrderStatus(restaurantOrder, status, workflowId);
}
}
上面代码中的op.UpdateOrderStatus会执行下面的LINQ语句:
{
var orderItems = from od in db.OrderDetails
where od.RestaurantId == restaurantOrder.RestaurantId
&& od.OrderId == restaurantOrder.OrderId
select new
{
OrderDetailId = od.OrderDetailId
};
foreach (var orderItemId in orderItems)
{
var orderItem = db.OrderDetails.Single(oi => oi.OrderDetailId == orderItemId.OrderDetailId);
orderItem.WorkflowId = WorkflowId;
orderItem.Status = status;
orderItem.StatusUpdatedTime = DateTime.Now;
}
db.SubmitChanges();
return true ;
}
当然定单的状态会按图中所标记的箭头方向转向到下一个状态,在XML中可以在当前的StateActivity
节点中找到下面的内容:
其中的TargetStateName属性值即是下一个状态名称,其内容如下:
< StateInitializationActivity x:Name = " orderCookingInitialization " >
< ns1:UpdateOrderStatusActivity orderStatus = " {ActivityBind RestaurantOrderWorkflow,Path=orderStatusCooking} " IncomingOrder = " {ActivityBind RestaurantOrderWorkflow,Path=orderToProcess} " conversation = " {x:Null} " x:Name = " UpdateOrderStatusActivity1 " />
StateInitializationActivity >
< EventDrivenActivity x:Name = " OrderCooked " >
< ns0:ReceiveActivity x:Name = " receiveOrderReadyForPickup " >
< ns0:ReceiveActivity.ServiceOperationInfo >
< ns0:TypedOperationInfo Name = " OrderReadyForPickup " ContractType = " {x:Type DinnerNow.OrderProcess.IUpdateOrder} " />
ns0:ReceiveActivity.ServiceOperationInfo >
< ns0:ReceiveActivity.ParameterBindings >
< WorkflowParameterBinding ParameterName = " order " >
< WorkflowParameterBinding.Value >
< ActivityBind Name = " RestaurantOrderWorkflow " Path = " orderToProcess " />
WorkflowParameterBinding.Value >
WorkflowParameterBinding >
ns0:ReceiveActivity.ParameterBindings >
ns0:ReceiveActivity >
< SetStateActivity x:Name = " setStateOrderReadyForPickup " TargetStateName = " OrderReadyForPickup " />
EventDrivenActivity >
StateActivity >
在这里, 我们看到了与刚才的初始化状态相类似的状态节点配置,并且它也有TargetStateName,其属性值为OrderReadyForPickup,看到这里感觉状态机越来越像是一个链表,它指定着状态传递的方向.而最终的完成状态就是状态机工作流中的CompletedStateName属性值.上面的状态活动的EventDrivenActivity为:OrderReadyForPickup,而这个驱动事件又是那个请求发出的呢?这里我们需要再打开另外一个解决方案,它位于安装目录下\solution\DinnerNow - Kiosk\solution\DinnerNow - Kiosk.sln, 我们编译这个WPF项目,得到下面的运行截图:
当我们选取其中的一个定单之后,显示该订单的一些详细信息如下图:
我们在这里通过下拉框更新了当前订单的状态,其最终的C#运行代码如下(OrderStatusWindow.xaml.cs):
..
switch (newStatusText.Trim())
{
case "New Order":
// we need to get the selected order
// do nothing
break;
case "Ready for pickup":
orderUpdateClient.OrderReadyForPickup(new RestaurantOrder() { OrderId = new Guid(this.SelectedOrder.OrderIdentifier), RestaurantId = this.SelectedOrder.RestaurantId });
break;
case "Out for Delivery":
orderUpdateClient.OrderPickedUp(new RestaurantOrder() { OrderId = new Guid(this.SelectedOrder.OrderIdentifier), RestaurantId = this.SelectedOrder.RestaurantId }, Guid.NewGuid());
break;
case "Delivered":
orderUpdateClient.OrderDelivered(new RestaurantOrder() { OrderId = new Guid(this.SelectedOrder.OrderIdentifier), RestaurantId = this.SelectedOrder.RestaurantId });
break;
} ;
这里,还有一点需要说明的是在OrderDelivered这个StateActivity中的一些信息,因为在这个状态活动中
有对上一篇文章中所说的ProcessOrder(顺序工作流)的信息发送,请看下面代码段:
< StateInitializationActivity x:Name = " OrderDeliveredInitialization " >
< ns1:UpdateOrderStatusActivity orderStatus = " {ActivityBind RestaurantOrderWorkflow,Path=orderStatusOrderDelivered} " IncomingOrder = " {ActivityBind RestaurantOrderWorkflow,Path=orderToProcess} " conversation = " {x:Null} " x:Name = " updateOrderStatusActivity5 " />
< ns0:SendActivity x:Name = " restaurantOrderComplete " >
< ns0:SendActivity.ServiceOperationInfo >
< ns0:TypedOperationInfo Name = " RestaurantOrderComplete " ContractType = " {x:Type DinnerNow.OrderProcess.IProcessOrder} " />
ns0:SendActivity.ServiceOperationInfo >
< ns0:SendActivity.ChannelToken >
< ns0:ChannelToken Name = " completeOrderToken " EndpointName = " WSHttpContextBinding_IProcessOrder " />
ns0:SendActivity.ChannelToken >
ns0:SendActivity >
< SetStateActivity x:Name = " setStateActivity4 " TargetStateName = " OrderComplete " />
StateInitializationActivity >
StateActivity >
其中下面这一行就是要使用的ProcessOrder工作流中的操作信息:
这样就会将ProcessOrder流程走完了.并且定单的状态也会变为OrderComplete。
好了,订单状态的更新流转已介绍的差不多了,不过这里还有一个功能没有介绍,那就是DinnerNow提供了Window Mobile接收编辑发送功能,而这个功能也是订单流程的一部分,但这块内容与当前本文所讨论的技术没太大关联性.还是留到以后有时间再与大家聊一聊吧.
好的,今天的内容就先告一段落,大家如果有兴趣,欢迎在回复中进行讨论:)