DinnerNow中的Work Flow應用(上) --- 訂單流程

        作爲微軟最新技術應用的DEMO。dinnernow使用了: IIS7, ASP.NET Ajax Extensions, LINQ, WCF, WF,WPF,Windows PowerShell, Card Space以及 .NET Compact Framework. 本文將會繼續訂餐流程,來討論關於WF(Windows Work Flow Foundation), 在"訂單"這一應用場景中的設計思路:)
html

       首先請你們看一下這張圖,它標明瞭在定單這一業務流程中WF在DinnerNow架構中所實際執行的方法順序及所處位置.


       繼續上一篇中的選餐頁面,當咱們選擇了可口的飯菜以後,就要去進入定單及付費流程了.請單擊選餐頁面中的checkout按鈕,這時頁面會跳轉了checkout.aspx頁。當咱們輸入了Delivery Address的相關信息並提交信息後,頁面會繼續停留在當前頁上並要求咱們繼續輸入Payment Option,直到再次提交後,就會看到下面的頁面了:
   

當咱們確認輸入的相關信息後,點擊bringmymeal按鈕後,就開始建立相應的訂單信息了.下面咱們就開始今天的內容:)
web

     首先請用VS2008打開下面兩個解決方案:
     安裝目錄下\solution\DinnerNow - Web\DinnerNow - Web.sln
               \solution\DinnerNow - ServicePortfolio2\DinnerNow - ServicePortfolio2.sln
數據庫

  先切換到DinnerNow - Web.sln下的DinnerNow.WebUX項目中的check.aspx文件,找到SubmitOrder()方法:    編程

function  SubmitOrder()
{
    DinnerNow.ShoppingCartService.SubmitOrder(
        submitorder_onSuccess,service_onError,
null );
}

  由於上面的代碼最終會去調用ShoppingCartService.cs(位於當前項目的Code文件夾下)中的同名方法:  架構

[OperationContract]
public   void  SubmitOrder()
{
    ShoppingCartDataSource datasource 
=  (ShoppingCartDataSource)HttpContext.Current.Session[StateKeys.ShoppingCart];
    Order order 
=  (Order)HttpContext.Current.Session[StateKeys.Order]; // 獲取剛纔輸入的訂單信息
    order.SubmittedDate  =  DateTime.Now;
    order.OrderItems 
=  (from oi  in  shoppingCart.Items
                        select 
new  OrderItem()
                        {
                            Eta 
=  DateTime.Now.AddMinutes(oi.DeliveryTime),
                            MenuItemId 
=   new  Guid(oi.MenuItemIdentifier),
                            MenuItemName 
=  oi.MenuItemName,
                            Quantity 
=  oi.Quantity,
                            RestaurantId 
=   new  Guid(oi.RestaurantIdentifier),
                            RestaurantName 
=  oi.RestaurantName,
                            Status 
=   " Ordered " ,
                            StatusUpdatedTime 
=  DateTime.Now,
                            UnitCost 
=  oi.Price
                        }).ToArray
< DinnerNow.WebUX.OrderProcessingService.OrderItem > ();
    
    order.Total 
=  (from oi  in  shoppingCart.Items
                   select 
new  { orderAmount  =  oi.Price  *  oi.Quantity }).Sum(o  =>  o.orderAmount);

    datasource.SubmitOrder(order);

    HttpContext.Current.Session[StateKeys.ShoppingCart] 
=   null ;
}

  上面代碼中的HttpContext.Current.Session[StateKeys.Order]裏面存儲的就是剛纔咱們輸入的相關訂單信息(我我的認爲這裏使用Session有些不妥).ide

  另外上面的OrderItems的類型聲明以下(來自DinnerNow - ServicePortfolio2.sln下的DinnerNow.Business
項目中的Data\Order.cs文件):
    網站

[DataContract]
[Serializable]
public   class  OrderItem
{
    [DataMember]
    
public   string  RestaurantName {  get set ; }
    [DataMember]
    
public  Guid RestaurantId {  get set ; }
    [DataMember]
    
public  Guid MenuItemId {  get set ; }
    [DataMember]
    
public   string  MenuItemName {  get set ; }
    [DataMember]
    
public   string  MenuItemImageLocation {  get set ; }
    [DataMember]
    
public   int  Quantity {  get set ; }
    [DataMember]
    
public   decimal  UnitCost {  get set ; }
    [DataMember]
    
public   string  Status {  get set ; }
    [DataMember]
    
public  DateTime StatusUpdatedTime {  get set ; }
    [DataMember]
    
public  Guid WorkflowId {  get set ; }
    [DataMember]
    
public  DateTime Eta {  get set ; }
}

  這個類中的字段WorkflowId保存是訂單所使用的工做流id, 這個值在訂單建立時不會被賦值,但當訂單狀態更新時會被賦值.而相應的工做流的狀態會持久化到SQLSERVER數據庫中(會在接下來的內容中加以說明)。ui

  目前咱們仍是要再回到 SubmitOrder() 代碼段中,找到datasource.SubmitOrder(order)這行代碼,而這行代碼要執行的是下面的方法:this

public   void  SubmitOrder(Order order)
{
    
using  (DinnerNow.WebUX.OrderProcessingService.ProcessOrderClient proxy  =   new  ProcessOrderClient())
    {
        proxy.Processorder(order); 
// 該行代碼未來執行綁定到SVC服務上的工做流
    }

    
this .items  =   new  List < ShoppingCartItem > ();
}

   
        上面代碼中的ProcessOrderClient類實際上添加SVC引用時系統自動生成的類.
       而引用的路徑即: http://localhost/dinnernow/service/OrderProcess.svc
       該文件位於ServicePortfolio2.sln下的DinnerNow.ServiceHost/OrderProcess.svc
spa

       因此這裏咱們要切換到ServicePortfolio2.sln解決方案下.並打開DinnerNow.ServiceHost項目中的
OrderProcess.svc ,這個文件中的頭部是這樣聲明的:

    <% @ ServiceHost Language = " C# "  Debug = " true "  
 Factory
= " System.ServiceModel.Activation.WorkflowServiceHostFactory "  
      Service
= " DinnerNow.OrderProcess.ProcessOrder "   %>

      其中的Factory="System.ServiceModel.Activation.WorkflowServiceHostFactory"會綁定到一個工
做流,且這個工做流會有持久化訪問數據庫的屬性.固然咱們能夠在web.config(位於這個項目中)找到下面的內容:

< workflowRuntime name = " WorkflowServiceHostRuntime "  validateOnCreate = " true "
  enablePerformanceCounters
= " true " >             
  
< services >
    
< add type = " System.Workflow.Runtime.Hosting.SharedConnectionWorkflowCommitWorkBatchService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 "   />
    
< add type = " System.Workflow.Runtime.Hosting.SqlWorkflowPersistenceService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35 "   />
    
< add type = " System.Workflow.Runtime.Tracking.SqlTrackingService,System.Workflow.Runtime,Version=3.0.00000.0, Culture=neutral,PublicKeyToken=31bf3856ad364e35 "   />
  
</ services >
  
< commonParameters >
    
< add name = " ConnectionString "  value = " Data Source=daizhj\daizhj;User ID=sa;Password=123123;Initial Catalog=dinnernow;Pooling=true "   />
  
</ commonParameters >
</ workflowRuntime >


       而屬性ConnectionString就是持久化時用到的數據庫連接串.

     看到這裏,本文的內容將會轉到WF上了,由於上面的C#代碼中的proxy.Processorder(order)方法在是WF中綁定的.按照上面的SVC聲明,找到ProcessOrder.xoml文件(位於當前解決方案中DinnerNow.OrderProcess項目下).

  這裏咱們經過雙擊該文件查看其工做流的大體流程:
   
 
      咱們在第一個Activity上擊鼠標右鍵在菜單中找到"屬性",以下圖:


   其中的ServiceOperationInfo中的綁定屬性就是該Activity所實現的操做,這裏寫的是:
     DinnerNow.OrderProcess.IProcessOrder.Processorder,看來對於外部發來的SOAP請求要先到達這裏被活動處理.

  經過上圖中的屬性咱們能夠看出這個Activity是一個ReceiveActivity, 這個組件是.net 3.5 才引入的,以下圖所示:
                 

  該組件主要用於處理Web服務請求.固然有進就有出,在這個順序工做流中還使用了另一個新引入的組件:SendActivity, 將會在後面加以說明:)

   即然找到了請求的操做,下一步就要好好看一下這個工做流了,由於順序工做流本地比較好理解,這裏就直接一個一個activity加以說明了.

  首先請加開Workflow\ProcessOrder.xoml.cs文件,它的前半部分代碼內容以下:

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  RestaurantOrder[] Order {  get set ; }

   [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
   [BrowsableAttribute(
true )]
   
public  DinnerNow.Business.Data.Order IncomingOrder
   {
       
get
       {
           
return  ((DinnerNow.Business.Data.Order)( base .GetValue(DinnerNow.OrderProcess.ProcessOrder.incomingOrderProperty)));
       }
       
set
       {
           
base .SetValue(DinnerNow.OrderProcess.ProcessOrder.incomingOrderProperty, value);
       }
   }

   [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
   [BrowsableAttribute(
true )]
   
public  Guid orderID
   {
       
get
       {
           
return  ((System.Guid)( base .GetValue(DinnerNow.OrderProcess.ProcessOrder.orderIDProperty)));
       }
       
set
       {
           
base .SetValue(DinnerNow.OrderProcess.ProcessOrder.orderIDProperty, value);
       }
   }

}

 

    能夠看到這裏使用了DependencyProperty(依賴屬性), 有關這方面的內容能夠參見這篇文章:
  《WF編程》系列之38 - 依賴屬性 
 
   這裏使用它是爲了活動數據綁定(在activity之間進行數據傳遞),由於在上面提到過,客戶端網站要提到定單信息過來,而訂單的數據將會綁定到這個工做流文件的IncomingOrder屬性上.

  如今有了數據,該執行建立定單的操做了,而這個任務交給了下一個Activity---"saveOrderActivity1", 咱們能夠從該活動的類型上看出它是一個SaveOrderActivity(DinnerNow.WorkflowActivities項目下),而這個活動類型的代碼段以下:

public   partial   class  SaveOrderActivity: Activity
{
    
public   static  DependencyProperty IncomingOrderProperty  =  DependencyProperty.Register( " IncomingOrder " typeof (DinnerNow.Business.Data.Order),  typeof (SaveOrderActivity));
    
public   static  DependencyProperty orderIDProperty  =  DependencyProperty.Register( " orderID " typeof (System.Guid),  typeof (SaveOrderActivity));

   
public  SaveOrderActivity()
   {
       InitializeComponent();
   }

   [Description(
" Customer Order " )]
   [Browsable(
true )]
   [Category(
" Order " )]
   [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
   
public  DinnerNow.Business.Data.Order IncomingOrder
   {
         
get
       {
           
return  ((DinnerNow.Business.Data.Order)( base .GetValue(SaveOrderActivity.IncomingOrderProperty)));
       }
       
set
       {
           
base .SetValue(SaveOrderActivity.IncomingOrderProperty, value);
       }
   }
   [DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
   [BrowsableAttribute(
true )]
   [CategoryAttribute(
" Parameters " )]
   
public  Guid orderID
   {
       
get
       {
           
return  ((System.Guid)( base .GetValue(SaveOrderActivity.orderIDProperty)));
       }
       
set
       {
           
base .SetValue(SaveOrderActivity.orderIDProperty, value);
       }
   }

   
protected   override  ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
   {
       OrderService service 
=   new  OrderService();

       orderID 
=  service.CreateOrder(IncomingOrder);
       
return  ActivityExecutionStatus.Closed;

   }
}

 

  固然這裏也用了DependencyProperty來接收從上一個活動(ReceiveActivity)傳遞來的參數,並對其進行操做。清注意上面代碼段中的Execute方法,它便是用來建立訂單的代碼.該方法中的語句:

     service.CreateOrder(IncomingOrder);

     所執行的操做以下:

public  Guid CreateOrder(DinnerNow.Business.Data.Order newOrder)
{
    
using  (Business.OrderProcessing op  =   new  DinnerNow.Business.OrderProcessing())
    {
        
return  op.CreateOrder(newOrder);
    }
}

  
     固然上面代碼中的OrderProcessing是對訂單(CRUD)服務的外部封裝.我我的認爲這種封裝仍是很好
必要的(毫不是***子放屁,畫蛇添足),它會讓業務操做和數據(庫)操做相分離,由於上面代碼中的:

op.CreateOrder(newOrder)   

     將會是LINQ方式的數據表操做,以下:
 

public  Guid CreateOrder(DinnerNow.Business.Data.Order newOrder)
{
    
// Check customer exists in database
    var customerId  =  (from c  in  db.Customers
                      
where  c.UserName  ==  newOrder.CustomerUserName
                      select c.CustomerId).FirstOrDefault();

    
if  (customerId  ==  Guid.Empty)
    {
        
//  need to add a new customer OR is this a bug?
        customerId  =  Guid.NewGuid();
        db.Customers.InsertOnSubmit(
new  DinnerNow.Data.Customer() { CustomerId  =  customerId, UserName  =  newOrder.CustomerUserName, UserId  =  Guid.Empty });
    }

    var orderId 
=  (newOrder.OrderId  ==  Guid.Empty  ?  Guid.NewGuid() : newOrder.OrderId);

    var order 
=   new  DinnerNow.Data.Order()
    {
        City 
=  newOrder.City,
        ContactTelephone 
=  newOrder.ContactTelephone,
        CustomerID 
=  customerId,
        OrderId 
=  orderId,
        OrderPayments 
=   new  DinnerNow.Data.OrderPayment()
        {
            Address 
=  newOrder.Payment.Address,
            City 
=  newOrder.Payment.City,
            Country 
=  newOrder.Payment.Country,
            CreditCardNumber 
=  newOrder.Payment.CardNumber.Substring( 0 4 ),
            CreditCardType 
=  newOrder.Payment.CreditCardType,
            ExpirationDate 
=  newOrder.Payment.ExpirationDate,
            PostalCode 
=  newOrder.Payment.PostalCode,
            State 
=  newOrder.Payment.State,
            NameOnCard 
=  newOrder.Payment.NameOnCard,
            OrderID 
=  orderId,
            PaymentID 
=  Guid.NewGuid()
        },
        PostalCode 
=  newOrder.PostalCode,
        State 
=  newOrder.State,
        StreetAddress 
=  newOrder.StreetAddress,
        SubmittedDate 
=  newOrder.SubmittedDate,
        Total 
=  newOrder.Total
    };

    order.OrderDetails.AddRange(from od 
in  newOrder.OrderItems
                                select 
new  DinnerNow.Data.OrderDetail()
                                {
                                    OrderDetailId 
=  Guid.NewGuid(),
                                    OrderId 
=  orderId,
                                    MenuItemId 
=  od.MenuItemId,
                                    Quantity 
=  od.Quantity,
                                    RestaurantId 
=  od.RestaurantId,
                                    UnitCost 
=  od.UnitCost,
                                    Status 
=   " New Order " ,
                                    StatusUpdatedTime 
=  DateTime.Now,
                                     ETA 
=  od.Eta 
                                });

    db.Orders.InsertOnSubmit(order);
    db.SubmitChanges();
    
return  order.OrderId;
}

     上面代碼主要是向數據庫中的Order(訂單表),OrderDetail(訂單名稱表)兩個表中插入記錄.同時將插入訂單記錄的編號返回給依賴屬性Guid orderID.

  而接下來的工做就是要去獲取該訂單與餐館綁定關係信息了.這個任務被下一個Activity所執行,即:
     CreateRestaurantOrdersCode
      

private   void  CreateRestaurantOrders( object  sender, EventArgs e)
{
    DinnerNow.Business.OrderProcessing service 
=   new  DinnerNow.Business.OrderProcessing();
    Order 
=  service.GetRestaurantOrders(orderID);
}

    而上面的GetRestaurantOrders(orderID)最終會調用下面的代碼(DinnerNow.Business\OrderProcessing.cs):
     

public  DinnerNow.Business.Data.RestaurantOrder[] GetRestaurantOrders(Guid orderID)
{
    var ordersByRestaurant 
=  (from od  in  db.OrderDetails
                              
where  od.OrderId  ==  orderID
                              select 
new  DinnerNow.Business.Data.RestaurantOrder()
                              {
                                  OrderId 
=  od.OrderId,
                                  RestaurantId 
=  od.RestaurantId
                              }).Distinct();
    
return  ordersByRestaurant.ToArray();
}

  
     由於代碼很簡單,就很少說了:)

     執行完上述操做後,就須要將新建立的訂單轉入業務流程了,這時會根據業務操做的不斷推動,從而使該訂單狀態不斷獲得更新,但由於這些後續操做不是在幾秒或幾分鐘分就要完成了,它要在餐館與訂餐人中的不斷交互中推動,就像是在淘寶買東西差很少,有的交易能夠要十天半個月甚至更長時間內纔會完成.因此在工做流中使用的持久化,也就是前面所說的數據庫存儲:)

    好了,完成了這個ACTIVITY以後,工做流的下一站就是replicatorActivity1,咱們能夠在該活動的屬性頁中找到它的相關設置以下圖:
            
   

     能夠看出它的執行方式是: parallel(並行)初始化childData: Activity=ProcessOrder, Path=Order 
而它的ChildInitialize方法(構造方法)爲:ChildInit,該方法的內容以下:

private   void  ChildInit( object  sender, ReplicatorChildEventArgs e)
{
    RestaurantOrderContainer container 
=  e.Activity  as  RestaurantOrderContainer;
    (container.Activities[
0 as  SendActivity).ParameterBindings[ 0 ].Value  =  e.InstanceData  as  RestaurantOrder;
    (container.Activities[
0 as  SendActivity).ParameterBindings[ 1 ].Value  =   new  Dictionary < string , string > ((container.Activities[ 1 as  ReceiveActivity).Context);
}

    看來它是要將要請求發送的數據(RestaurantOrderContainer提供)綁定到SendActivity上,它包括一個RestaurantOrder,和一個活動的上下文信息.好的,這裏有必要介紹一下SendActivity, 它是在.net 3.5中引入的發送Soap請求的組件,有了它就能夠發送服務請求到指定的svc(服務)上並可獲取執行該服務的返回結果.

  而這什麼要使用它,這裏不妨對下一篇文章中的內容作一下介紹:該SendActivity將會調用OrderUpdateService.svc服務,而這個SVC偏偏又是一個狀態機工做流,它主要執行定單狀態的流轉更新操做.因此DinnerNow實現的是一個從順序工做流向另外一個狀態機工做流發送SOAP請求的流程.而這些內容會在下一篇文章中進行介紹.  

  今天的內容就先告一段落,你們若是有興趣,歡迎在回覆中進行討論:)

相關文章
相關標籤/搜索