程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WF從入門到精通(第十九章):把工作流發布為Web服務

WF從入門到精通(第十九章):把工作流發布為Web服務

編輯:關於.NET

學習完本章,你將掌握:

1.了解要把你的工作流暴露為XML Web服務來使用的話,各個工作流活動該怎樣進行設計

2.了解在ASP.NET中宿主工作流需要些什麼

3.看看在基於XML Web服務的工作流中如何進行錯誤(fault)處理

4.針對各種情況對你的基於XML Web服務的工作流進行配置

在前一章“在你的工作流中調用Web服務”中,你看到了如何從你客戶端一側的工作流中使用WF所提供的InvokeWebService活動來調用XML Web服務。但是,在那章的應用程序范例中的XML Web服務是一個典型的ASP.NET的XML Web服務——沒有什麼特別的。

在這最後一章中,你將學會怎樣對工作流進行處理並自動地把工作流暴露為XML Web服務以讓客戶去使用。這並不像創建一個工作流程序集庫然後從一個Web服務項目中引用它那樣簡單,但是話又說回來,一旦你理解了一些基本概念並在一個應用程序范例中看看它的實現後,要做到也並不困難。

備注:本章的焦點是把WF作為XML Web服務集成到ASP.NET中使用。但是,在暴露XML Web服務的時候你應該意識到許多關鍵的問題,最大的問題是安全。對安全的充分討論遠遠超過了我可以在此做的介紹,但是下面這個鏈接應該可以為你帶來幫助:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/THCMCH12.asp。假如你想把你的工作流暴露為一個XML Web服務的話,我強烈建議你查閱一下ASP.NET安全方面的最佳實踐,尤其是圍繞XML Web服務的實踐。

把工作流暴露為一個XML Web服務

你不能在ASP.NET環境中直接執行工作流的部分原因其實在本書中你已經了解過,默認情況下,工作流運行時是以異步的方式去執行工作流實例的。事實上,在非Web應用程序中使用工作流的時候,這是一個很重要的特性。

但是在基於Web的環境下,這會帶來一個問題。如果一個ASP.NET請求發來後,不管它是一個XML Web服務還是一個ASP.NET Web頁面,工作流實例都要開始執行並且運行時要把控制權返回給ASP.NET。你的XML Web服務或者ASP.NET頁面會立即並持續執行准備輸出並可能會在工作流實例完成之前結束。因為工作流實例是異步的,它和你的ASP.NET應用程序並行執行,因此你的ASP.NET代碼可能會很快地完成並返回一個工作流處理過程並未完成的響應結果。

提示:在ASP.NET Web頁面中正確地執行工作流實例實際上就是對ASP.NET異步Web頁面進行調用,該話題超出了本書的范圍。但是下面這個鏈接能帶給你一些具體的細節:http://msdn.microsoft.com/msdnmag/issues/05/10/WickedCode/。

這個問題至少會給我們帶來兩個挑戰。首先,我們需要禁用,或者至少要對我們的工作流的異步執行方式進行變通。我們需要它們以同步的方式執行,就像它們和我們的頁面或者XML Web服務中使用的是同一個線程一樣,以讓該Web應用程序在我們結束之前是不會把響應結果返回給調用者的。當然,這並未解決長時間運行的工作流的問題,這是我們將需要去克服的第二個挑戰性難題。

長時間運行的工作流所帶來的難題緊緊地和基於Web的應用程序自身的性質聯系在一起。在這最後一章中你知道了Web應用程序從本質上是無狀態的。所發送的在數毫秒間就斷開的請求是完全意識不到對方的,除非我們創建一個框架來提供這種能力。Web應用程序也在Web服務器上執行,它通常是非常昂貴的系統,旨在為許許多多的客戶端提供服務。假如一個工作流要花費大量時間才能完成,那麼它會完全占有Web服務器並降低應用程序的可伸縮性(指的是為越來越多的客戶端請求提供服務的能力)。

解決的辦法是進行狀態管理以及對長時間運行的工作流進行持久化。假如你的工作流程要在超過一個以上的基於Web的調用(ASP.NET頁面請求或者XML Web服務)後才能完成的話,你必須持久化該工作流實例並在下一個執行周期期間重新加載它。這也是為什麼我在前一章中的“長時間運行的XML Web服務”一節中提到重新生成保存了session狀態的cookie的原因。因為客戶端也必須意識到有這種可能性並考慮到會有超過一個以上的請求-響應的情況。

Internet信息服務(IIS)特別擅於節約系統資源。在一個典型的客戶端應用程序中,其實在本書中到目前為止你所看到過的每一個應用程序當中,工作流運行時是在應用程序開始執行的時候被啟動並貫穿該應用程序的生命周期。然而,IIS要收回服務器的資源。我們作為ASP.NET的程序員,這就意味著下面的兩件事。

首先,我們必須以某種方式來決定在什麼地方以及怎樣來啟動工作流運行時。在同一個ASP.NET應用程序中的不同請求會被放到不同的線程上進行處理,但是它們在同一個應用程序域(AppDomain)中執行。就像你或許還記得的,在每一個AppDomain只能有唯一的一個WF工作流運行時的實例在執行。因此它並不像在你的ASP.NET應用程序每收到一個請求的時候就創建一個工作流運行時的實例那樣簡單。這樣做可能會導致工作流運行時產生異常。

其次,我們需要找到一種方法來讓我們的工作流以同步的方式執行。或者,如果我們的工作流需要長時間運行的話,我們必須在開始執行工作流實例的時候、停止執行的時候、持久化工作流實例的時候以及把工作流當前的狀態返回給客戶端的時候進行同步。為此,我們需要替換默認的工作流運行時的線程調度服務。要替換該線程調度服務,我們需要重新配置工作流運行時。

創建工作流運行時

如果你看了前一章的XML Web服務的話,你會看到我向該ASP.NET范例添加了一個特殊的文件:Global.asax。這個文件包含了一些在ASP.NET應用程序生命周期中的一些重要的事件處理程序。在Global.asax中,其中的一個事件處理程序是應用程序啟動事件,它在創建一個HttpApplication實例的時候通過ASP.NET觸發。假如你想的話,你可以在這個Application_Start事件處理程序中來啟動工作流運行時。(在前一章中我做了一些類似的工作,我創建了一個靜態的Dictionary對象以便進行股票的查詢。)

備注:假如你想學習關於ASP.NET中應用程序生命周期方面的知識的話,可以看看msdn2.microsoft.com/en-us/library/aa485331.aspx提供的一些細節。

你將面對的問題是,對於個別要訪問工作流運行時的Web資源的請求來說,你需要為其提供一些機制。完成這件事最顯而易見的解決辦法是從你創建的一個容器類中去引用工作流運行時,然後把它放到ASP.NET的緩存(cache)中。事實上,這幾乎就差不多了。

WF團隊知道人們想在ASP.NET應用程序中使用工作流運行時,他們也知道啟動工作流運行時會是困難的,因此他們已經為我們創建了WorkflowWebRequestContext類。

WorkflowWebRequestContext巧妙地為我們解決了兩個難題。首先,它維護了一個工作流運行時的單一實例。當你使用它的CurrentWorkflowRuntime訪問器的時候,假如該工作流運行時的單一實例為null的話,它就會為你創建該工作流運行時。如果該工作流運行時已經被創建過,就將返回被緩存的這個工作流運行時。這是我們貫穿本書一直在使用的,從第2章“工作流運行時”所引入的WorkflowFactory類的正確的模式。它所解決的另一個難題是所有的Web資源都能訪問到WorkflowWebRequestContext,並能由此獲得訪問工作流運行時的能力。

配置服務

有了一個恰當的機制來檢索我們的ASP.NET應用程序(頁面或者XML Web服務)所要使用的這個唯一的工作流運行時,我們現在需要轉到添加我們需要的服務上來。我們至少需要一個服務,但更多的服務有可能也是必須。我們需要添加到運行時的這個服務是手動線程調度服務(manual thread scheduling service),但是持久化服務和事務服務也是通常要進行添加的。

你最初可能會認為這些服務的添加方式和我們貫穿本書添加其它服務所使用的添加方式是同一種方式。也就是說,你可能會認為下面的方法也能夠工作:

WorkflowRuntime workflowRuntime = WorkflowWebRequestContext.Current.WorkflowRuntime;

string connString = ConfigurationManager.ConnectionStrings["MyPersistenceDB"].ConnectionString;
workflowRuntime.AddService(new SqlWorkflowPersistenceService(connString));

但是,在ASP.NET環境中這會失敗。到我們訪問工作流運行時的時候,它已經被WorkflowWebRequestContext啟動了。一旦工作流被啟動後,你就不能再添加特殊的服務,持久化就是其中一個。而且,在ASP.NET環境中,其它的工作流實例可能已經正使用工作流運行時,因此你也不能停止它來添加想要的服務然後對它進行重啟。因此,必須有一些其它的方式來進行工作流運行時的配置。

事實上,術語“配置(configuration)”比你可能猜想的還要貼切。要被添加的服務從ASP.NET應用程序的配置文件Web.config中讀出。一個特殊的節被專門用來進行工作流的配置,被<WorkflowRuntime />界定的XML元素由WF定義。因為工作流運行時使用的是一個自定義的配置節,你也會預料到在<configSections />元素中也有一個節標識。服務本身使用在<WorkflowRuntime />中找到<Services />元素進行配置。

我將馬上為你展示一個典型的Web.config文件,但是在此之前,還有一點需要考慮。由誰來創建WorkflowWebRequestContext對象呢?這個對象並不是拆盒即用(out-of-the-box)的ASP.NET實現的一部分,因此必須由某物在某處來對它的生命周期進行負責。XML Web服務對特定的工作流實例的調用也是這種情況(回憶起了SessionId了嗎?),它必須被映射到這些實例上,並且假如實例碰巧被持久化,它們也必須被重新加載以便執行。

完成所有這些任務的對象是一個被稱作WorkflowWebHostingModule的ASP.NET HTTP 模塊(HttpModule)。ASP.NET HTTP 模塊,正如你可能知悉的,它們是一些擴展對象,你能把它們放進你的基於Web的應用程序的請求-響應路徑中。它們就是用在這種類型的任務上:為你的ASP.NET應用程序增加擴展性功能。碰巧,通過你應用程序的Web.config文件也能對附加的ASP.NET HTTP 模塊進行配置。

備注:為了學習關於HTTP 模塊的更多知識,以及他們怎樣增強ASP.NET HTTP的請求-響應管線,可以看看http://msdn.microsoft.com/en-us/library/ms178468(zh-cn,VS.80).aspx。

清單19-1為你提供了一個基本的Web.config文件,裡面包含了許多基於WF的應用程序宿主到ASP.NET環境中去所需進行配置的公共組件。注意它包含的信息指出了要把WF宿主到你的ASP.NET應用程序中:你仍然需要為你的應用程序添加一些恰當的ASP.NET配置(就像是<system.web />節一樣)。

備注:清單19-1只適用於當前WF的正式發布版本。但是,如果新的版本發布後,你配置文件中的對應的版本號和公開密鑰的值也將需要進行更新。你或許也需要修改連接字符串以便和你所安裝的SQL Server相匹配。

清單19-1 進行工作流擴展的Web.config

Web.config

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
    <configSections>
        <section name="WorkflowRuntime" type="System.Workflow.Runtime.Configuration.WorkflowRuntimeSection, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
    </configSections>
    <WorkflowRuntime Name='WorkflowServiceContainer">
        <Services>
            <add type="System.Workflow.Runtime.Hosting.ManualWorkflowSchedulerService, 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.hosting.DefaultWorkflowTransactionService, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
        </Services>
    </WorkflowRuntime>
    <appSettings />
    <connectionStrings>
        <add name="MyPersistenceDB" connectionString="server=(local)\SQLEXPRESS;database=WorkflowStore;Integrated Security=true" />
    </connectionStrings>
    <system.web>

        <httpModules>
            <add type="System.Workflow.Runtime.Hosting.WorkflowWebHostingModule, System.Workflow.Runtime, Version=3.0.00000.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="WorkflowHost" />
        </httpModules>
    </system.web>
</configuraton>

工作流的內務處理(Husekeeping)

在本章中目前為止討論的所有內容都圍繞著ASP.NET的架構以及它宿主基於WF的應用程序的必備條件展開。在我們接觸WF的基於XML Web服務的活動之前,我們先花點時間探討一下怎樣架構基於工作流的XML Web服務的應用程序。

當你使用ExternalDataExchange和本地通信服務的時候,對在本書中所描述過的過程來說,第一步無論無何都是創建一個接口。這個接口對將被用來在你的應用程序和工作流之間傳遞信息的方法和數據進行識別,它是整個通信過程的服務基礎。

XML Web服務的工作流也遵循相似的模式。你創建一個接口,它的方法將成為基於Web服務的方法(當我們使用.NET的時候,我們稱它們為Web方法)。接口和工作流通常都被放到一個單獨的程序集(assembly)中去,你再在ASP.NET宿主應用程序中引用它們。(ASP.NET具有運行時動態編譯代碼的能力。我們將通過使用一個預編譯的程序集來回避這一點。)然後,當我提及WF的基於XML Web服務的活動和接口、方法進行交互的時候,我所提到的那個接口正是我將生成的這個接口。

要記住的另外一件事是通常.NET的XML Web服務通過一個.asmx文件暴露出來。這個文件,或者它的代碼後置文件,將包含一個派生自System.Web.Services.WebService基類的類定義。個別標識了System.Web.Services.WebMethod特性的方法將被指定為Web方法。.asmx文件本身包含了一個<%@WebService%>標示語句,它用來告知ASP.NET這是一個XML Web服務而不是一個Web頁面,並且它還提供了一些其它的特定信息。

就像任何其它的XML Web服務一樣,基於WF的XML Web服務也需要.asmx文件,它也包含Web服務的標示語句,因為ASP.NET和IIS要憑此來識別XML Web服務。好消息是我們並不需要親自去創建真正的ASP.NET XML Web服務項目。WF向Microsoft Vislual Studio中增加了一個把工作流發布為ASP.NET項目的菜單項並為我們集成到了我們的解決方案中。這真是一個棒極了的功能,我們將在本章的晚些時候體驗一番。

使用WebServiceInput活動

好吧,我們繼續深入。基於WF的XML Web服務必須至少有一個WebServiceInput活動以及一個或更多的WebServiceOutput活動。輸入活動和輸出活動是相互聯系的:每個輸出活動必須和一個輸入活動關聯。(WebServiceFault活動也是一樣的。)你不能只拖入一個WebServiceInput而沒有輸出活動(WebServiceOutput)或者失敗活動(WebServiceFault),同樣,你也不能只有輸出活動或者失敗活動而沒有最起碼的一個WebServiceInput。每種情況都會使你導致驗證失敗和編譯錯誤。

當你把一個WebServiceInput活動放進你的工作流後,你需要設置幾個屬性,也許還要至少去處理一個事件。其中重要的屬性顯示在表19-1中。InputReceived事件讓你能為工作流進行環境的初始化工作。而下面這些屬性用來對WebServiceInput活動進行配置。

表19-1 WebServiceInput活動的重要屬性

 屬性 功能 InterfaceType 獲取或者設置接口的數據類型,它可用來識別出潛在的可作為Web方法使用的方法。 IsActivating 獲取或者設置該活動的激活狀態。該值指示是否應該在接收數據的時候啟動工作流,假如你的工作流中存在多個WebServiceInput活動的話,你可以通過設置其中一個活動的這個屬性為true來確定讓它去啟動工作流。在你的工作流中必須有一個WebServiceInput活動設置它的這個屬性為true。假如有超過一個以上的WebServiceInput活動都把這個屬性設置為true,那麼會有不止一個的Web服務調用也能啟動工作流。假如兩個WebServiceInput活動共用一個session cookies,那麼第二個調用會被阻塞(等待),一直到第一個完成。 MethodName 獲取或者設置被暴露為Web方法的方法名稱。這個方法也必須是指定的接口所定義的方法。      表19-1中的這三個屬性都必須對其指定值,盡管IsActivating默認的是False。忽略了對其中任何一個屬性值的分配工作,都會導致驗證失敗和編譯錯誤。

IsActivating可以讓你能控制次Web服務對你的工作流進行調用。主調用,也就是啟動工作流的Web服務方法調用,應該把它的IsActivating設置為True。假如你的工作流允許客戶端進行次調用,就像在基於狀態的工作流中接受輸入事件一樣,次WebServiceInput活動應該把IsActivating設置為False否則會導致工作流重啟。假如你允許你的工作流啟動第二個實例,如果它和第一個工作流實例共享同一個session狀態的話,第二個實例將會被阻塞並可能會導致死鎖。因此,使用多個activating的WebServiceInput活動是非常謹慎的。

當你指定MethodName時要記住,和該方法相關的方法參數需要進行綁定或者以其它方式指定。在Visual Studio中,當你為MethodName選擇了一個方法後,該方法的參數會自動為你在活動的屬性面板上呈現出來,你可以使用貫穿本書已經用過的屬性綁定對話框對這些參數進行綁定。當開始學習本章的范例應用程序時你也將看到這些內容。

使用WebServiceOutput活動

WebServiceOupput活動通過返回WebServiceInput活動中的MethodName的值來完成XML Web服務的處理。出於這個因素,你必須指定WebServiceOutput的InputActivityName屬性為你工作流中所存在的WebServiceInput活動中的一個。錯誤地指定輸入活動的名稱會導致驗證失敗和編譯錯誤。

WebServiceOutput只有一個方法參數要進行綁定:如果有的話,它正是接口中所標出的,綁定到和本輸出活動相聯系的WebServiceInput活動的方法的返回結果。如果該方法返回的是void,則沒有要綁定的返回值。然後WebServiceOutput活動會迫使為這個Web方法調用發送工作流處理結束的信號。

有趣的是,對於一個輸入你可以有多個輸出結果。例如,假如多個輸出活動被放到了Parallel活動的各個執行路徑中,或者被放在IfElse活動的不同分支內,這些都可能導致上述的情況。通過你工作流的不同路徑就可能導致不同的輸出結果。你不能在一條執行路徑上放置多個輸出活動。(對於WebServiceFault活動也是一樣。)假如WF識別出在同一條執行路徑上有一個以上的WebServiceOutput或者WebServiceFault與WebServiceOutput之間的組合的情況,WF會使這些活動無效,並且你需要通過移開或者刪除那些違反規則的活動來糾正執行邏輯上的錯誤。

使用WebServiceFault活動

WebServiceFault活動和WebServiceOutput活動關系密切。它們二者碰巧都象征著對特定調用進行處理的工作流的終止。而且事實證明,WebServiceFault可以像WebServiceOutput一樣去使用。

WebServiceFault也只和一個WebServiceInput活動發生聯系。就像它的近親WebServiceOutput活動一樣,WebServiceFault也有一個唯一的輸出屬性Fault需要你必須去進行綁定,Fault要被綁定到一個基於System.Exception的字段或者屬性,它代表了要向客戶端報告的異常。最終,Fault會被轉換成一個SoapException並通過通信線路發送給客戶端。但是,允許你去綁定任何你想要的異常。ASP.NET會自動地把你提供的異常轉換為SoapException。

創建一個Web服務項目

本書的這最後一個應用程序范例我們將完全從零開始創建。我們通過創建一個簡單的控制台應用程序來起步,它不需要引用WF程序集。這是因為工作流將由ASP.NET XML Web服務承載,我們將會在接下來的一節創建這個工作流程序集。在准備好工作流程序集後,我們將把該工作流發布成一個ASP.NET Web服務項目並做一些調整,接著會在這個原始的控制台應用程序中添加調用該工作流的代碼。

創建基本工作流應用程序

1.打開Visual Studio,依次點擊“文件”、“新建”、“項目”菜單項。

2.當打開“新建項目”對話框後,在“項目類型”面板中選擇“Windows”。然後在模板列表中選中“控制台應用程序”。

3.在“名稱”字段中輸入QuoteGenerator,在“位置”字段中輸入\Workflow\Chapter19。然後點擊“確定”。

這會創建你將用來測試XML Web服務的控制台應用程序和一個你可以繼續添加更多項目的解決方案文件。

添加順序工作流庫

1.在Visual Studio的解決方案資源管理器上右鍵點擊QuoteGenerator解決方案的名稱,這將彈出其快捷菜單。從快捷菜單中依次點擊“添加”、“新建項目”。

2.這會打開“添加新項目”對話框。如果Visual C#樹狀控件節點還沒有展開的話請展開它,然後在項目類型面板中選擇“Workflow”並從模板列表中選擇“順序工作流庫”。在“名稱”字段中輸入GeneratorFlow,最後點擊“確定”。

3.Visual Studio會在QuoteGenerator解決方案中添加該順序工作流項目並為你自動打開工作流視圖設計器。在創建工作流前,我們先來創建你將會用到的接口。為此右鍵點擊GeneratorFlow項目,然後在呈現的菜單中依次點擊“添加”、“類”。當打開“添加新項”對話框後,在“名稱”字段中輸入IGenerateQuote.cs並點擊“確定”。

4.修改Visual Studio為你自動創建的該類的定義,內容如下:

public interface IGenerateQuote

5.為IGenerateQuote添加下面的方法,然後保存該文件:

decimal GetQuote(string symbol);

6.現在你創建好了一個接口,然後在GenerateFlow項目中選中Workflow1.cs,並在解決方案資源管理器的右鍵菜單上點擊“視圖設計器”回到視圖設計器界面上來。

7.從Visual Studio的工具箱中拖拽一個WebServiceInput活動到你的工作流定義中。

8.現在我們來設置webServiceInputActivity1的屬性。首先點擊InterfaceType屬性去激活浏覽(...)按鈕。然後點擊該浏覽按鈕,這會打開“浏覽並選擇 .NET 類型”的對話框。GeneratorFlow.IgenerateQuote應該已經被添加到“類型名稱”字段中因為它屬於當前的項目並且也只有這一個接口。最後點擊“確定”。

9.點擊IsActivating屬性去激活其下拉箭頭。點擊該下拉箭頭,從列表的可選項中選擇True。

10.選中MethodName屬性去激活其下拉箭頭。點擊該下拉箭頭,選中其唯一的一個選項:GetQuote。一旦你選中它後,請注意,symbol屬性會被添加到該活動的屬性中。

11.選中symbol屬性會像前面一樣呈現出你熟悉的浏覽(...)按鈕。點擊該浏覽按鈕打開“將‘symbol’綁定到活動的屬性”對話框。點擊“綁定到新成員”選項卡,在“新成員名稱”中輸入Symbol。你需要確認你選中的是“創建屬性”選項,最後點擊“確定”。該webServiceInputActivity1活動此時會指出存在驗證錯誤(也就是說,仍然呈現出一個包圍了驚歎號的紅色小圓圈)。這是因為你的輸入和輸出(活動)沒有成對,你很快將解決該問題。

12.我們現在需要在XML Web服務第一次執行的時候做一些初始化的工作,因此在webServiceInputActivity1的InputReceived屬性中輸入CreateStocks,然後按下回車鍵。Visual Studio會自動為你插入CreateStocks事件處理程序並為你自動切換到代碼編輯狀態下。你需要回到工作流視圖設計器界面上來繼續完成該工作流。

13.從工具箱中拖拽一個Code活動到webServiceInputActivity1的下面。在它的ExecuteCode屬性中輸入UpdateMarketValues。一旦Visual Studio為你插入UpdateMarketValues事件處理程序後再次回到工作流視圖設計器界面上來。

14.接下來要插入的工作流活動是IfElse活動。把它拖拽到設計器界面上來並放到你剛剛放入的Code活動的下面。

15.選中ifElseActivity1的左邊的分支,點擊它的Condition屬性去激活其下拉箭頭。從它的選項列表中選擇代碼條件然後展開其後面的加號(+),這會出現另一個Condition屬性,然後在第二個Condition屬性中輸入TestKnownStock並按下回車鍵。一旦在Visual Studio再次為你添加完事件處理程序後,你需要重新回到工作流視圖設計器界面上來。

16.拖拽一個Code活動到設計器界面上,把它放到ifElseActivity1的左邊分支中。在它的ExecuteCode屬性中輸入RecordStockValue,在Visual Studio添加了其事件處理程序後重新回到工作流視圖設計器界面上來。

17.現在拖拽一個WebServiceOutput活動到你剛剛插入的Code活動的下面。

18.指定webServiceOutputActivity1的InputActivityName屬性為webServiceInputActivity1,方法是點擊InputActivityName屬性去激活其下拉箭頭,然後點擊該下拉箭頭選中webServiceInputActivity1活動。注意(ReturnValue)會被添加到webServiceOutputActivity1的屬性列表中。

19.因為我們想返回某只股票的價值,因此我們需要把某個工作流綁定到webServiceOutputActivity1的(ReturnValue)屬性。選擇這個(ReturnValue)屬性去激活其浏覽(...)按鈕,然後點擊該浏覽按鈕。當“將‘(ReturnValue)’綁定到活動的屬性”對話框打開後,選擇“綁定到新成員”選項卡並在“新成員名稱”中輸入StockValue。確保選中的是“創建屬性”選項,然後點擊“確定”。

20.你最後一個要添加的活動是WebServiceFault。拖拽一個WebServiceFault活動到工作流視圖設計器界面上並把它放到ifElseActivity1的右邊分支中。

21.就像webServiceOupputActivity1一樣,你需要為webServiceFaultActivity1指定InputActivityName屬性。選中它的InputActivityName屬性去激活其下拉箭頭,然後從列表中選擇webServiceInputActivity1選項。(只有這唯一的一個選項可供選擇。)

22.你接下來需要為webServiceFaultActivity1提供恰當的錯誤(fault)處理,因此選中它的Fault屬性去激活其浏覽(...)按鈕。點擊該浏覽按鈕打開“將‘Fault’綁定到活動的屬性”對話框。點擊“綁定到新成員”選項卡並在“新成員”名稱中輸入StockFault。確保選中的是“創建屬性”選項,然後點擊“確定”。

23.該工作流現在在視圖設計器上的設計工作就已經完成了,現在要打開Workflow1.cs文件進行代碼編輯。在解決方案資源管理器中選中Workflow1.cs文件,然後點擊“查看代碼”工具條按鈕。

24.首先要添加的代碼是兩個可讓我們訪問ASP.NET的using語句。把它們添加到現有using語句的後面:

using System.Web;
using System.Web.Caching;

25.接下來讓我們來完成CreateStocks事件處理程序。找到CreateStocks,然後為該方法添加下面的代碼:

CreateStocks事件處理程序

System.Collections.Generic.Dictionary<string, decimal> stockVals =
    HttpContext.Current.Cache["StockVals"] as
    System.Collections.Generic.Dictionary<string, decimal>;
if (stockVals == null)
{
    // Create and cache the known stock values.
    stockVals =
       new System.Collections.Generic.Dictionary<string, decimal>();
    stockVals.Add("CONT", 28.0m);
    stockVals.Add("LITW", 22.0m);
    stockVals.Add("TSPT", 24.0m);

    // Add to the cache.
    HttpContext.Current.Cache.Add("StockVals", stockVals, null,
                                  Cache.NoAbsoluteExpiration,
                                  Cache.NoSlidingExpiration,
                                  CacheItemPriority.Normal, null);
} // if

26.找到UpdateMarketValues事件處理程序。為UpdateMarketValues處理程序添加下面的更新市值的模擬代碼:

UpdateMarketValues處理程序

// Iterate over each item in the dictionary and decide
// what its current value should be. Normally we'd call
// some external service with each of our watch values,
// but for demo purposes we'll just use random values.
//
// Note this is essentially the same simulation code as
// found in Chapter 10
Random rand = new Random(DateTime.Now.Millisecond);
System.Collections.Generic.Dictionary<string, decimal> currentStockVals =
   HttpContext.Current.Cache["StockVals"] as
   System.Collections.Generic.Dictionary<string, decimal>;
System.Collections.Generic.Dictionary<string, decimal> newStockVals =
   new System.Collections.Generic.Dictionary<string, decimal>();
foreach (string key in currentStockVals.Keys)
{
    // Pull the item's value
    decimal currentPrice = (decimal)currentStockVals[key];

    // Set up the simulation
    decimal newPrice = currentPrice;
    decimal onePercent = currentPrice * 0.1m;
    Int32 multiplier = 0; // no change

    // We'll now roll some dice. First roll: does the
    // market value change? 0-79, no. 80-99, yes.
    if (rand.Next(0, 99) >= 80)
    {
        // Yes, update the price. Next roll: will the
        // value increase or decrease? 0-49, increase.
        // 50-99, decrease
        multiplier = 1;
        if (rand.Next(0, 99) >= 50)
        {
            // Decrease the price.
            multiplier = -1;
        }

        // Next roll, by how much? We'll calculate it
        // as a percentage of the current share value.
        // 0-74, .1% change. 75-89, .2% change. 90-97,
        // .3% change. And 98-99, .4% change.
        Int32 roll = rand.Next(0, 99);
        if (roll < 75)
        {
            // 1% change
            newPrice = currentPrice + (onePercent * multiplier * 0.1m);
        }
        else if (roll < 90)
        {
            // 2% change
            newPrice = currentPrice + (onePercent * multiplier * 0.2m);
        }
        else if (roll < 98)
        {
            // 3% change
            newPrice = currentPrice + (onePercent * multiplier * 0.3m);
        }
        else
        {
            // 4% change
            newPrice = currentPrice + (onePercent * multiplier * 0.4m);
        }
    }
    else
    {
        // No change in price
        newPrice = currentPrice;
    }

    // Update the data store
    newStockVals.Add(key, newPrice);
}

// Add to the cache
HttpContext.Current.Cache["StockVals"] = newStockVals;

27.我們需要添加驗證股票代碼的邏輯,因為它可能不是我們所能識別的代碼。搜索Workflow1.cs代碼找到TestKnowStock事件處理程序,然後為該方法體添加下面的代碼:

TestKnowStock事件處理程序

// Retrieve the cached stock values.
System.Collections.Generic.Dictionary<string, decimal> stockVals =
    HttpContext.Current.Cache["StockVals"] as
    System.Collections.Generic.Dictionary<string, decimal>;

// Check to see if the ticker symbol is in the
// known stock list. Fault if not
e.Result = true;
if (String.IsNullOrEmpty(Symbol) || !stockVals.ContainsKey(Symbol))
{
    // We don't have the desired stock in our list,
    // so return a fault.
    e.Result = false;
    StockFault = new System.Exception("The desired stock ticker symbol" +
                                      " is unknown.");
}

28.你最後要添加的幾行代碼是把股票市值指定給返回值屬性。RecordStockValue方法對此進行處理,因此定位到RecordStockValue方法並添加下面的代碼:

RecordStockValue事件處理程序

// Place updated stock value in the dependency property
// for return to the caller.
System.Collections.Generic.Dictionary<string, decimal> stockVals =
   HttpContext.Current.Cache["StockVals"] as
   System.Collections.Generic.Dictionary<string, decimal>;
StockValue = (decimal)stockVals[Symbol];

29.該工作流現在就完成了。保存所有打開的文件並編譯該工作流。在繼續之前糾正任何你可能遇到的編譯錯誤。

工作流現在就做好了集成進ASP.NET中去的准備,以便外部客戶端能連接到該服務並發出請求獲取股票市值。盡管你可以創建一個新的ASP.NET應用程序並親自進行這些所有魔術般的配置,但WF團隊熱心地為你提供了一個極好的生成ASP.NET項目的功能,這不會出現差錯,而且使用起來也相當容易。

創建ASP.NET Web應用程序

1.這是如此的容易,他們應該考慮給想出這個主意的WF開發人員加薪。直接右鍵點擊解決方案資源管理器中的GeneratorFlow項目,然後選擇“作為 Web 服務發布”即可。然後Visual Studio將彈出一個確認對話框。點擊“確定”關閉該確認對話框。

2.這簡直太容易了,讓我們來重命名這個.asmx文件。右鍵點擊解決方案資源管理器中最新生成的GeneratorFlow.Workflow1_WebService.asmx文件的名稱,然後選擇“重命名”菜單項。把它的名稱修改為QuoteService.asmx。

3.按下Shift+F6或者在“生成”主菜單中選擇“生成 Web 站點”。這一步驟可以不需要,但是它將預編譯該Web應用程序,這樣,當你引用它去檢索它的WSDL的時候,你將在此時節約一些時間。假如存在任何錯誤,你應該糾正它們,但你不應該會發現有什麼錯誤。假如你收到什麼警告信息的話,選擇忽略它們。

最後的任務是回到最初創建的程序文件中,向其所在的項目添加Web引用並測試該XML Web服務。

創建XML Web服務客戶端應用程序

1.在解決方案資源管理器中右鍵點擊QuoteGenerator項目的名稱,然後選擇“添加 Web 引用”。

2.當“添加 Web 引用”對話框打開後,點擊“此解決方案中的 Web 服務”鏈接。

3.因為在QuoteGenerator解決方案中只有唯一的一個XML Web服務,因此只會出現QuoteService XML Web服務這唯一的一個服務。點擊QuoteService鏈接繼續前進。

4.Visual Studio會顯示它所找到的QuoteService XML Web服務的方法。在這個例子中,只有這唯一的一個方法。假如你樂意的話,你可以點擊該方法進行測試。現在,我們點擊“添加引用”把該Web引用添加到QuoteGenerator項目中。

備注:“Web 引用名”中包含了localhost字符串。假如你有多個Web引用,你可把它修改為更富有含義的字符串以便更加容易維護並對Web服務進行定向。

5.添加Web引用後,打開Program.cs文件進行編輯。在解決方案資源管理器中選中Program.cs文件,然後點擊“查看代碼”工具條按鈕。

6.尋找Main方法。為其添加下面這些代碼:

Main方法

// Create the proxy.
QuoteGenerator.localhost.Workflow1_WebService ws =
   new QuoteGenerator.localhost.Workflow1_WebService();

try
{
    // Call the service a few times to
    // test its logic.
    decimal val = ws.GetQuote("CONT");
    Console.WriteLine("The latest value for CONT is: {0}",val.ToString("C"));

    val = ws.GetQuote("LITW");
    Console.WriteLine("The latest value for LITW is: {0}",val.ToString("C"));

    val = ws.GetQuote("TSPT");
    Console.WriteLine("The latest value for TSPT is: {0}", val.ToString("C"));

    // Error test
    val = ws.GetQuote("ABC");
    Console.WriteLine("The latest value for ABC is: {0}",val.ToString("C"));
}
catch (Exception ex)
{
    Console.WriteLine("Error checking stock value: {0}", ex.Message);
}

7.編譯該解決方案,糾正任何編譯錯誤。

8.按下Shift+F5或者直接按下F5以調試模式來執行本應用程序。你應看到下面的輸出結果。假如輸出結果滾動得太快,控制台窗口在你能看到輸出之前就被關閉的話,可以在Main方法中設置一個斷點並一步一步地前進,直到你看到所有的輸出結果。

全書完

本文配套源碼

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved