作爲微軟最新技術應用的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.svcspa
因此這裏咱們要切換到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請求的流程.而這些內容會在下一篇文章中進行介紹.
今天的內容就先告一段落,你們若是有興趣,歡迎在回覆中進行討論:)