做爲微軟最新技術應用的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文件,發現如下服務配置節點:
<service behaviorConfiguration="WorkflowHostBehavior" name="DinnerNow.OrderProcess.RestaurantOrderWorkflow">
<endpoint address="" binding="wsHttpContextBinding" contract="DinnerNow.OrderProcess.IUpdateOrder" />
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
看來要去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>
<!--<CodeActivity x:Name="codeActivity1" ExecuteCode="AcceptOrderCode" />-->
</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".
接下來我們看到了下面這一行代碼:
<ns0:TypedOperationInfo Name="StartRestaurantOrder" ContractType="{x:Type DinnerNow.OrderProcess.IUpdateOrder}" />
到這裏,我們找到了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
節點中找到下面的內容:
<SetStateActivity x:Name="setStateActivity3" TargetStateName="OrderCooking" />
其中的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工作流中的操作信息:
<ns0:TypedOperationInfo Name="RestaurantOrderComplete" ContractType="{x:Type DinnerNow.OrderProcess.IProcessOrder}" />
這樣就會將ProcessOrder流程走完了.並且定單的狀態也會變爲OrderComplete。
好了,訂單狀態的更新流轉已介紹的差不多了,不過這裏還有一個功能沒有介紹,那就是DinnerNow提供了Window Mobile接收編輯發送功能,而這個功能也是訂單流程的一部分,但這塊內容與當前本文所討論的技術沒太大關聯性.還是留到以後有時間再與大家聊一聊吧.
好的,今天的內容就先告一段落,大家如果有興趣,歡迎在回覆中進行討論:)