監(jiān)理公司管理系統(tǒng) | 工程企業(yè)管理系統(tǒng) | OA系統(tǒng) | ERP系統(tǒng) | 造價(jià)咨詢管理系統(tǒng) | 工程設(shè)計(jì)管理系統(tǒng) | 甲方項(xiàng)目管理系統(tǒng) | 簽約案例 | 客戶案例 | 在線試用
X 關(guān)閉

XML網(wǎng)絡(luò)服務(wù):使用SOAP和ASP.NET創(chuàng)建可復(fù)用網(wǎng)絡(luò)部件

申請(qǐng)免費(fèi)試用、咨詢電話:400-8352-114

AMTeam.org

XML網(wǎng)絡(luò)服務(wù):使用SOAP和ASP.NET創(chuàng)建可復(fù)用網(wǎng)絡(luò)部件


Andrew Clinick

Microsoft 公司

2001年9月10日

今年的早些時(shí)候,我介紹了于.NET 的Microsoft? Visual Studio? for Applications (VSA) 和腳本,并且介紹了如何使用這些技術(shù)來使你的.NET 應(yīng)用程序變得可以被自定義的。由于每個(gè)人對(duì)應(yīng)用程序應(yīng)該是什么樣子好像都有些微不同的想法,而不管它的.NET如何,因此,對(duì)一個(gè).NET應(yīng)用程序進(jìn)行定義就成為一件多少有些不討好的事情。然而,通常一種技術(shù)包含在大多數(shù).NET應(yīng)用程序-網(wǎng)絡(luò)服務(wù)的定義中。因此,這個(gè)月我將介紹如何使用應(yīng)用于.NET Framework的VSA和腳本 來創(chuàng)建可自定義的網(wǎng)絡(luò)服務(wù),而特別是如何能幫助創(chuàng)建一個(gè)下層結(jié)構(gòu),使你的用戶可以使用并配置可以對(duì)這個(gè)網(wǎng)絡(luò)服務(wù)進(jìn)行自定義的腳本代碼,并且從創(chuàng)建這個(gè)下層結(jié)構(gòu)獲利。

為什么要?jiǎng)?chuàng)建可自定義的應(yīng)用程序?

在介紹使用Visual Studio for Applications 來創(chuàng)建可自定義的應(yīng)用程序的技術(shù)細(xì)節(jié)前,我覺得我們最好回到前面并且看一看為什么在創(chuàng)建.NET 應(yīng)用程序時(shí)創(chuàng)建為可自定義的應(yīng)用程序是如此重要。如果你已經(jīng)對(duì)這個(gè)概念有所認(rèn)識(shí),那么就簡(jiǎn)單地跳過這節(jié)。

當(dāng)設(shè)計(jì)和編寫一個(gè)應(yīng)用程序時(shí),你始終面對(duì)著你的用戶的要求,要為他們提供一個(gè)可以解決他們的問題并且通常使他們的生活更方便的程序。然而,不管你在創(chuàng)建這個(gè)程序的過程中付出多大努力去收集需求并和用戶交流,總是會(huì)有所完成的程序?qū)⒉荒芊纤杏脩舻男枨蟮慕Y(jié)論而放棄。需求不斷地變化是一貫如此的 (該死?。?-因?yàn)橐坏┯脩糸_始認(rèn)真地使用你的程序,他們就會(huì)不可避免地想到新的功能,這些功能是他們希望你的程序能實(shí)現(xiàn)來幫助他們工作。

如果你試圖去編寫將被不止一個(gè)公司使用的軟件,那么決定滿足哪個(gè)用戶的需求,解決哪個(gè)用戶的問題就變得十分困難。例如,如果你正在編寫一個(gè)訂單處理系統(tǒng),那么你能夠提供一個(gè)100%滿足你的客戶的恰當(dāng)?shù)纳虡I(yè)規(guī)則的機(jī)會(huì)是什么?對(duì)于一個(gè)沒有自定義的普通程序片斷不可能同時(shí)滿足所有用戶的需求。

你怎樣選擇來使你的程序可自定義的對(duì)你的用戶,對(duì)你的升級(jí)能力,同時(shí)對(duì)你的產(chǎn)品的銷售都會(huì)產(chǎn)生重大的影響。你有許多可選的選擇項(xiàng)目(重要性沒有特殊順序):

不提供自定義的能力

出于我已經(jīng)給出的所有原因,我將假定這不是一個(gè)很好的選擇。

為應(yīng)用程序提供源代碼,這樣你的客戶就可以對(duì)應(yīng)用程序進(jìn)行修改來滿足他們的需要


這提供了一些好處,其中從根本上是為你的客戶提供了完全的靈活性并且控制你的產(chǎn)品該如何執(zhí)行。這也許是一個(gè)可行的并且有吸引力的選項(xiàng)。然而,因此又冒出了許多問題,包括升級(jí)到新版本和保護(hù)你的知識(shí)產(chǎn)權(quán)的問題。我在這里不討論知識(shí)產(chǎn)權(quán)(沒有我的參與,在互聯(lián)網(wǎng)上也有足夠的人在討論這一點(diǎn)),但是升級(jí)問題對(duì)我們所有人都有影響。

例如,我們假設(shè)你提供了你的產(chǎn)品的1.0版的源代碼,而你的一些用戶更改了某些特殊部件的執(zhí)行。(我將用訂單處理系統(tǒng)的稅務(wù)計(jì)算模塊作為例子。)我們假定現(xiàn)在內(nèi)部的執(zhí)行事不同的,而這個(gè)模塊的外部界面被改為與客戶的內(nèi)部發(fā)票處理系統(tǒng)集成在一起。好的消息是,用戶現(xiàn)在已經(jīng)成功地實(shí)現(xiàn)把你的系統(tǒng)和他們的集成在一起,而且他們很高興。

在預(yù)計(jì)的時(shí)間內(nèi),你開始工作于你的產(chǎn)品的2.0版本,并且用戶也因?yàn)槟闾砑拥男鹿δ芏鴮?duì)購(gòu)買新版本很有興趣。接下來你會(huì)去安裝產(chǎn)品的2.0版本,但是發(fā)現(xiàn)由于稅務(wù)計(jì)算模塊已經(jīng)被更改,除了試圖把你添加的更改和那些由用戶做的更改合并到一起外,沒有其他容易一些的方法來進(jìn)行升級(jí)。

從表面上看,這好像不是一個(gè)主要的問題,但是當(dāng)你增加系統(tǒng)各處的更改時(shí),這就很快變成一個(gè)問題了。試想如果為了滿足客戶的需要對(duì)整個(gè)系統(tǒng)的各部分都進(jìn)行小更改。如果你試圖保留用戶已經(jīng)對(duì)系統(tǒng)所作的自定義的,那么提供一個(gè)從1.0版到2.0版的升級(jí)就變得令人吃驚的困難。實(shí)際上,這很快就會(huì)導(dǎo)致不能進(jìn)行升級(jí),因此,無論用戶是否希望升級(jí)到最新版本,他們都被限制在你的軟件的老版本-而你也被困于試圖去對(duì)你的軟件的無數(shù)不同版本進(jìn)行支持。

為你的應(yīng)用程序提供腳本解決方案

把腳本創(chuàng)建到你的應(yīng)用程序中提供了一種機(jī)制,使你可以不用把你的應(yīng)用程序的源代碼公布給你的用戶就可以進(jìn)行自定義的。它也允許你在你的應(yīng)用程序中設(shè)計(jì)詳細(xì)的自定義的點(diǎn)。如果你要把在自定義的投資最大化,那么為你的應(yīng)用程序設(shè)計(jì)自定義的點(diǎn)大概是你的應(yīng)用程序設(shè)計(jì)中最重要的部分(并且有時(shí)要占用最多的時(shí)間)。一個(gè)好的自定義的設(shè)計(jì)過程將不僅僅在自定義的你的系統(tǒng)時(shí)節(jié)省時(shí)間,并且使你能夠?qū)δ切┮M(jìn)行自定義的和什么時(shí)候進(jìn)行自定義的進(jìn)行控制。

腳本方法優(yōu)于源代碼的最大好處就是自定義的可以在產(chǎn)品升級(jí)時(shí)得以保存。例如,改變稅務(wù)計(jì)算模塊執(zhí)行的腳本可以在所安裝的模塊的2.0版本中繼續(xù)使用(當(dāng)然,假設(shè)目標(biāo)模塊是向后兼容的),這是由于自定義的是腳本的一部分而不是這個(gè)模塊的完全實(shí)現(xiàn)。

Visual Studio for Applications提供了一種機(jī)制,它從把應(yīng)用于.NET Framework的腳本集成到你的應(yīng)用程序中獲利。也就是,它為你的客戶提供了一個(gè)全特性,集成的開發(fā)環(huán)境來編寫和調(diào)試腳本。

為客戶提供一種方法來把模塊聯(lián)接到你的應(yīng)用程序中

這是一種有趣的選擇,并且提供了許多腳本解決方案的好處,但是也給與你,應(yīng)用程序開發(fā)者和你的應(yīng)用程序的客戶很多挑戰(zhàn)。這項(xiàng)選擇有些類似于由Microsoft? Office和Visual Studio 所提供的“添加”模塊。你的應(yīng)用程序提供的集成界面部件需要一種機(jī)制,就像在適當(dāng)?shù)臅r(shí)候把添加的部件加載到你的應(yīng)用程序中那樣。

如果承擔(dān)應(yīng)用程序自定義的任務(wù)的大多數(shù)人都是有經(jīng)驗(yàn)的開發(fā)者,他們熟悉創(chuàng)建部件并且對(duì)接口有很好的理解,那么使用這種方法就是一種值得考慮的選擇。出于這個(gè)原因,我認(rèn)為這對(duì)腳本來說是一種免費(fèi)贈(zèng)送的特性,并且不應(yīng)當(dāng)被看作是與把腳本添加到你的應(yīng)用程序中相抵觸。這就是說,當(dāng)開發(fā)一個(gè)應(yīng)用程序時(shí)就會(huì)涉及到許多挑戰(zhàn)-特別是許多執(zhí)行解決方案時(shí)涉及的工作。你需要開發(fā)一個(gè)集成接口,一種把模塊加載到你的應(yīng)用程序中的機(jī)制,并且提供這種集成機(jī)制的相關(guān)文檔。這并不需要很困難,但是如果你選擇腳本方法,它就是你不需要的東西。

開發(fā)可自定義的網(wǎng)絡(luò)服務(wù)

為了說明怎樣使用.NET Framework腳本和Visual Studio for Applications來創(chuàng)建一個(gè)可自定義的網(wǎng)絡(luò)服務(wù),我將建立一個(gè)用于scripthappens.com的簡(jiǎn)單的計(jì)算網(wǎng)絡(luò)服務(wù)。這個(gè)網(wǎng)絡(luò)服務(wù)部件被故意做得很簡(jiǎn)單,因此我就可以全神貫注于介紹如果集成腳本,而不是陷于處理稅務(wù)計(jì)算所需要的代碼中。這個(gè)網(wǎng)絡(luò)服務(wù)有兩個(gè)方法CalculateTax 和 CalculateDiscount, 并且將被scripthappens.com 電子商務(wù)網(wǎng)站的處理系統(tǒng)調(diào)用。服務(wù)自身完全在Microsoft? Visual Basic? .NET 中執(zhí)行,但是用任何一種.NET語言都可以很好地開發(fā)。網(wǎng)絡(luò)服務(wù)提供了一個(gè).NET Framework腳本引擎運(yùn)行腳本來對(duì)系統(tǒng)運(yùn)行進(jìn)行自定義的,對(duì)腳本編寫者給予幫助,提供了一個(gè)代表了網(wǎng)絡(luò)服務(wù)接受到服務(wù)要求的狀態(tài)的對(duì)象模型。

網(wǎng)絡(luò)服務(wù)被設(shè)計(jì)成要從其他網(wǎng)絡(luò)應(yīng)用程序調(diào)用,包括所有在服務(wù)器和用戶機(jī)上的網(wǎng)絡(luò)應(yīng)用程序(Internet Explorer網(wǎng)絡(luò)服務(wù)行為)。由于使用完全是基于網(wǎng)絡(luò)設(shè)計(jì)的,所以我認(rèn)為所集成的Visual Studio for Applications 集成開發(fā)環(huán)境 (VSA IDE)可以通過網(wǎng)絡(luò)接口被調(diào)用也是很重要的。為了實(shí)現(xiàn)這一點(diǎn),我建立了一個(gè)簡(jiǎn)單的系統(tǒng),它使用XML和MIME文件映射,可以通過在一個(gè)網(wǎng)絡(luò)瀏覽器上的網(wǎng)頁(yè)來啟動(dòng)VSAIDE.后面更多地討論這個(gè)系統(tǒng)如何工作。

例子使用了Visual Studio for Applications 軟件開發(fā)工具包(VSASDK)來主機(jī).NET Framework引擎腳本和VSA IDE 。VSA SDK的關(guān)鍵設(shè)計(jì)點(diǎn)之一是允許應(yīng)用程序通過VSA來確定用戶編寫的腳本代碼的存儲(chǔ)格式和位置。為了實(shí)現(xiàn)這個(gè)目的,與SDK結(jié)合的主類向應(yīng)用程序開發(fā)者提供了一個(gè)機(jī)制,可以嵌入到開發(fā)者自己所堅(jiān)持的機(jī)制中。這是通過編寫一個(gè)部件來實(shí)現(xiàn)的,這個(gè)部件是一個(gè)使用固定接口的代碼提供者,ICodeProvider ,在VSA SDK中定義。VSA SDK中的主類將把這個(gè)代碼管理器用于應(yīng)用程序中所有的固定和修補(bǔ)的腳本代碼中,而且都在設(shè)計(jì)時(shí)(從VSA IDE當(dāng)中)和運(yùn)行過程中腳本被加載時(shí)。

由于編寫VSA IDE中的腳本的人很可能與存儲(chǔ)和使用這個(gè)腳本代碼的人使用不同的機(jī)器,所以代碼管理器可以被遠(yuǎn)程調(diào)用就十分重要了。因此,VSA SDK被設(shè)計(jì)成遠(yuǎn)程使用代碼管理器,就像網(wǎng)絡(luò)服務(wù)通過HTTP協(xié)議使用SOAP。這個(gè)示例應(yīng)用程序使用一個(gè)代碼管理器作為VSA IDE的網(wǎng)絡(luò)服務(wù),而在運(yùn)行時(shí)要加載代碼來對(duì)網(wǎng)絡(luò)服務(wù)進(jìn)行自定義的時(shí),在本地作為一個(gè).NET部件。

 
圖 1. Scripthappens.com 計(jì)算網(wǎng)絡(luò)服務(wù)的結(jié)構(gòu)

現(xiàn)在你該很希望對(duì)系統(tǒng)怎樣裝配到一起而成為一個(gè)整體有一個(gè)通常的理解,下面我就將進(jìn)入執(zhí)行的細(xì)節(jié)。

執(zhí)行網(wǎng)絡(luò)服務(wù)

計(jì)算網(wǎng)絡(luò)服務(wù)作為一個(gè)標(biāo)準(zhǔn).NET網(wǎng)絡(luò)服務(wù)被執(zhí)行,calculate.asmx ,和兩個(gè)方法在使用了WebMethod屬性的類中被定義為功能。WebMethod屬性告訴ASP.NET在運(yùn)行時(shí)放開這個(gè)方法,因此它可以被一個(gè)網(wǎng)絡(luò)服務(wù)調(diào)用-迄今為止沒有任何特例。

為了使網(wǎng)絡(luò)服務(wù)可以被自定義的,一個(gè)腳本引擎需要被加載來運(yùn)行任何用戶編寫的腳本代碼。在我最近的專欄中,我直接使用.NET Framework接口腳本,并且每次都調(diào)用源腳本代碼。這是很好的事情,由于它是一個(gè)客戶應(yīng)用程序,并且腳本代碼的性能并不是這個(gè)應(yīng)用程序主要考慮的問題。由于這是一個(gè)將用在一個(gè)服務(wù)器上的網(wǎng)絡(luò)服務(wù),無論如何,運(yùn)行時(shí)的性能都是關(guān)鍵。出于這個(gè)原因,我將使用.NET Framework引擎腳本的能力來加載預(yù)編譯代碼-特別是可以只調(diào)用予編譯代碼的輕-重量腳本引擎(light-weight script engine)(我在上個(gè)月當(dāng)作調(diào)用程序提到了這個(gè)引擎)。很幸運(yùn)的是VSA SDK提供了運(yùn)行時(shí)集成類,它使運(yùn)行引擎成為小菜一碟,因此我使用它把腳本代碼集成到網(wǎng)絡(luò)服務(wù)中。

VSA SDK運(yùn)行類被設(shè)計(jì)來提供一個(gè)IVsa接口的抽象,并且提供一個(gè)有效的途徑來把引擎集成到你的應(yīng)用程序中。由于你的應(yīng)用程序很可能在一大堆部件中集成這個(gè)腳本引擎,因此我們盡量保證代碼所需要使用的類的數(shù)量保持最少。我特別希望集成可以用三行代碼完成。(三行代碼對(duì)迷你代碼來說好像是很合適。雖然為什么三行代碼是重要數(shù)字已經(jīng)迷失在VSA設(shè)計(jì)過程的迷霧中。)

計(jì)算網(wǎng)絡(luò)服務(wù)的構(gòu)造函數(shù)創(chuàng)建了一個(gè)運(yùn)行庫(kù)和代碼管理器的實(shí)例,它將被用來給部件加載編譯過的腳本。我將在后面文章中介紹代碼管理器是怎樣執(zhí)行的,但是它是一個(gè)執(zhí)行ICodeProvider 的類,并且已經(jīng)被包括在項(xiàng)目中,所以就只是一個(gè)創(chuàng)建新實(shí)例的問題了。

運(yùn)行時(shí)類有許多重載構(gòu)造函數(shù)。既然這樣,我就使用了我認(rèn)為將是最常用的構(gòu)造函數(shù),它使用自定義的名字和將要用的名稱。名稱大概是VSA中最重要的概念之一,因?yàn)樗鼘?duì)于.NET Framework腳本和VSA IDE來說都是至關(guān)重要的。實(shí)質(zhì)上,名稱是腳本代碼的唯一標(biāo)識(shí),而你應(yīng)當(dāng)注意的是你如何去構(gòu)造它。名稱對(duì)于通過代碼管理器保存和重新找回任何代碼也是很重要的。一個(gè)代碼管理器實(shí)質(zhì)上是為你的應(yīng)用程序把一個(gè)名稱轉(zhuǎn)換為存儲(chǔ)機(jī)制。例如,這個(gè)例子中的代碼管理器把com.scripthappens://calculate 轉(zhuǎn)換為在當(dāng)前工作目錄下的一個(gè)層次文件夾vsa projectscalculate 。

一旦運(yùn)行時(shí)類的實(shí)例被創(chuàng)建,所有剩下的事情就是來提供一個(gè)代碼管理器的實(shí)例,類將使用這個(gè)實(shí)例來找回任意腳本代碼,告訴類去調(diào)用這段代碼,并且接下來運(yùn)行這段代碼-所有工作都在三行代碼中完成?。ê玫?,因此我不會(huì)對(duì)創(chuàng)建的代碼管理器或類的實(shí)例進(jìn)行技術(shù),而他們不會(huì)記錄在我的代碼計(jì)數(shù)器的行數(shù)記錄當(dāng)中。)

  Try
            ' 創(chuàng)建代碼管理器的新實(shí)例
            myCodeProvider = New DiskCodeProvider()
            ' 創(chuàng)建運(yùn)行時(shí)類的新實(shí)例
            myRTClass = New Runtime("calculator", "com.scripthappens://calculate")
            ' 把代碼管理器設(shè)置為磁盤代碼管理器的實(shí)例
            myRTClass.SetCodeProviderLocal(myCodeProvider)
            ' 加載已編譯的代碼
            myRTClass.Load(Nothing)
            ' 運(yùn)行代碼
            myRTClass.Run()

        Catch e As Exception
            ' 拋出一個(gè)相當(dāng)沒用的意外來告訴用戶某些工作開始了
            ' 致命錯(cuò)誤
            Throw New Exception("Unable to load the customization")
        End Try

網(wǎng)絡(luò)服務(wù)現(xiàn)在已經(jīng)準(zhǔn)備好運(yùn)行任何用戶認(rèn)為合適的腳本了,但是迄今為止我們沒有為腳本提供任何對(duì)象模型來照著編寫。運(yùn)行時(shí)類提供了兩個(gè)方法來把對(duì)象添加到引擎中:AddObject 和AddEventSourceObject 。AddObject 把對(duì)象添加到引擎的全局范圍中,但是腳本代碼不能響應(yīng)任何由這些對(duì)象激發(fā)的事件。AddEventSourceObject 把對(duì)象添加到類或者模塊中,并且不使人驚奇的是,源于這個(gè)對(duì)象的事件可以被描述。

為了保持簡(jiǎn)單化,計(jì)算網(wǎng)絡(luò)服務(wù)有一個(gè)激發(fā)事件的對(duì)象,稱為CalculateObjectModel (是不是很容易記住的名字?)。在這里我將與你一起在最前面,這里,在Beta 2版中有一個(gè)錯(cuò)誤,為什么運(yùn)行時(shí)類要檢查是否你提供的對(duì)象實(shí)例非空。本應(yīng)當(dāng)引起注意在構(gòu)造函數(shù)中把eventsourceobject 添加到引擎中,但是我需要使用對(duì)象中的構(gòu)造函數(shù)來設(shè)置內(nèi)部只讀的屬性,基于把數(shù)據(jù)傳送到網(wǎng)絡(luò)的方法。由于那些數(shù)據(jù)在構(gòu)造時(shí)是不可得的,我必須把a(bǔ)ddeventsourceobject 調(diào)用添加到每個(gè)方法中。Visual Studio for Applications 最終版本沒有對(duì)實(shí)例的數(shù)據(jù)進(jìn)行檢查,這就允許我把一個(gè)空實(shí)例傳入并且在方法被調(diào)用時(shí)創(chuàng)建實(shí)例。

正確獲得對(duì)象模型

當(dāng)設(shè)計(jì)這個(gè)網(wǎng)絡(luò)服務(wù)時(shí),我試著去想關(guān)于用戶怎樣才能進(jìn)行自定義的來符合他們的要求。同樣的,我花了許多時(shí)間來設(shè)計(jì)對(duì)象模型。這個(gè)我最終得到的模型是一個(gè)在稅務(wù)計(jì)算進(jìn)行前后都會(huì)激發(fā)事件的模型。我認(rèn)為這將給出更多的靈活性,由于它將運(yùn)行主要的事情-執(zhí)行你自己的稅務(wù)計(jì)算例程-并且也允許腳本作者做一些他們希望的后面的計(jì)算處理。

一旦我決定用這個(gè)前和后處理事件模型,我就開始考慮關(guān)于我如何裝配這些對(duì)象模型來使腳本編寫員的生活更輕松些-不影響到網(wǎng)絡(luò)服務(wù)的外部設(shè)計(jì)。一個(gè)重要的設(shè)計(jì)目標(biāo)是,在提供一個(gè)可以簡(jiǎn)單地從腳本語言調(diào)用美國(guó)網(wǎng)絡(luò)方法來訪問內(nèi)部狀態(tài)的對(duì)象模型同時(shí),確保網(wǎng)絡(luò)服務(wù)在實(shí)際執(zhí)行中保持本來的無國(guó)籍(stateless)。為了達(dá)到這個(gè)目的,我創(chuàng)建了一個(gè)新的類,它執(zhí)行腳本編寫者將會(huì)看到的對(duì)象模型,并且在美國(guó)網(wǎng)絡(luò)方法中使用它。

為了說明這個(gè),我將解釋我怎樣在網(wǎng)絡(luò)服務(wù)中執(zhí)行CalculateTax 方法。這個(gè)方法相當(dāng)簡(jiǎn)單。它得出一個(gè)總數(shù),和進(jìn)行稅務(wù)計(jì)算所得到的狀態(tài),作為證明。(我知道這是一個(gè)非常美國(guó)中心化的方法,但是它畢竟只是個(gè)演示。)為了確保網(wǎng)絡(luò)服務(wù)在執(zhí)行中保持本來的無國(guó)籍,總數(shù)和狀態(tài)信息不會(huì)送到方法的外部。無國(guó)籍編程對(duì)于可測(cè)量性來說有重要的好處,但是并不需要把變成變得復(fù)雜-特別是如果你不是一個(gè)有經(jīng)驗(yàn)的程序員,這對(duì)于那些編寫腳本來自定義的你的應(yīng)用程序的人來說總是個(gè)問題。

為了幫助腳本編寫者,我創(chuàng)建了一個(gè)簡(jiǎn)單的對(duì)象模型,它把當(dāng)前網(wǎng)絡(luò)方法的狀態(tài)作為只讀屬性表現(xiàn)出來(這些數(shù)值在類的構(gòu)造函數(shù)中設(shè)置)和有很多可以用來返回腳本自定義的結(jié)果的可讀/寫的屬性。除了屬性之外,這個(gè)對(duì)象也將給出腳本編寫者將用來對(duì)其變寫代碼的事件模型。我為這個(gè)前和后處理事件使用了一個(gè)簡(jiǎn)單名字轉(zhuǎn)換BeforeEventName 和 AfterEventName 。

Public Class CalculateObjectModel
    ' 處理數(shù)據(jù)的成員變量
    Private m_amount As Decimal
    Private m_state As String
    Private m_taxAmount As Decimal
    Private m_discount As Decimal
    Private m_custID As Integer
    ' 定義事件
    Event BeforeCalculateTax()
    Event AfterCalculateTax()
    Event BeforeCalculateDiscount()
    Event AfterCalculateDiscount()
    ' 用來設(shè)置總數(shù)和狀態(tài)的構(gòu)造函數(shù)
    Public Sub New(ByVal amount As Decimal, ByVal state As String)
        m_amount = amount
        m_state = state
        m_custID = Nothing
    End Sub
    ' 用來設(shè)置總數(shù)和custID 的構(gòu)造函數(shù)
    Public Sub New(ByVal amount As Decimal, ByVal custID As Integer)
        m_amount = amount
        m_custID = custID
        m_state = Nothing
    End Sub
    ' 為了讓編寫人員更簡(jiǎn)單編寫代碼的一些屬性
    Public Property Discount() As Decimal
        Get
            Return m_discount
        End Get
        Set(ByVal Value As Decimal)
            m_discount = Value
        End Set
    End Property
    Public Property Tax() As Decimal
        Get
            Return m_taxAmount
        End Get
        Set(ByVal Value As Decimal)
            m_taxAmount = Value
        End Set
    End Property
    Public ReadOnly Property Amount() As Decimal
        Get
            Return m_amount
        End Get
    End Property
    Public ReadOnly Property state() As String
        Get
            Return m_state
        End Get
    End Property
    Public ReadOnly Property CustID() As Integer
        Get
            Return m_custID
        End Get
    End Property
End Class

為腳本提供對(duì)象模型

一個(gè)設(shè)計(jì)得很好的對(duì)象模型是偉大的,但是如果它不提供腳本引擎就是沒有用處的,而對(duì)象中的事件也不會(huì)按正確的順序被激發(fā)。網(wǎng)絡(luò)服務(wù)中的CalculateTax 方法在我的例子中不會(huì)真正地做很多事情,與創(chuàng)建對(duì)象模型的一個(gè)實(shí)例不同,在對(duì)象模型中激發(fā)事件BeforeCalculateTax ,從對(duì)象返回稅務(wù)總數(shù),并且激發(fā)AfterCalculateTax 事件。很明顯,一個(gè)真正的網(wǎng)絡(luò)服務(wù)將在激發(fā)事件間做更多的事情。

在方法的執(zhí)行過程中,我我遇到了一項(xiàng)有趣的挑戰(zhàn),而我認(rèn)為當(dāng)你開始編寫你的對(duì)象模型時(shí)也會(huì)遇到這個(gè)問題。也就是:我如何在一個(gè)類的新實(shí)例中產(chǎn)生事件?通常,你會(huì)使用RaiseEvent 方法來激發(fā)事件,但是不幸的是用來激發(fā)事件的代碼沒有運(yùn)行在對(duì)象模型的實(shí)例中,因此在對(duì)象模型和調(diào)用代碼間就需要一些階層的簡(jiǎn)介聯(lián)系。

最初,我認(rèn)為這應(yīng)該是很容易解決的-只是在對(duì)象模型中執(zhí)行FireEvent 方法并且把你想激發(fā)的事件的名稱傳遞過去就行了,并且它就是那樣。像這樣做工作正常,但是有一個(gè)不幸的附加效果:方法FireEvent 暴露給腳本編寫者,這將導(dǎo)致一些相當(dāng)有趣的死鎖狀態(tài)。試想如果BeforeCalculateTax 的事件句柄與BeforeCalculateTax同時(shí)調(diào)用方法FireEvent -無限循環(huán)和咬牙切齒就跟著發(fā)生了。幸運(yùn)的是,這可以簡(jiǎn)單地通過把方法FireEvent 設(shè)定為內(nèi)部的或Visual Basic用法中的“友”來避免,這樣做的意思是,方法會(huì)被運(yùn)行在同一集合中的代碼所看到而對(duì)于腳本代碼是不可見的。

 Public Function CalculateTax(ByVal amount As Decimal,_
 ByVal state As String) As Double
        ' 為腳本創(chuàng)建對(duì)象模型的實(shí)例
        ' 并且在構(gòu)造函數(shù)中設(shè)置總數(shù)和狀態(tài)
        CalcOM = New CalculateObjectModel(amount, state)

        Try
            ' 用VSA Beta 2編程在引擎運(yùn)行前不能有EventSourceObject的空實(shí)例
            myRTClass.AddEventSourceObject("CalculateCustomization",
 "CalcOM", CalcOM)
            ' 需要重置引擎來確??梢缘玫叫耬ventsourceobject
            myRTClass.Reset()
            ' 再次運(yùn)行代碼
            myRTClass.Run()
        Catch e As Exception
            ' 拋出意外
            Throw e
        End Try

        ' 在對(duì)象模型中調(diào)用FireEvent方法來激活BeforeCalculateTax事件
        CalcOM.FireEvent("BeforeCalculateTax")

        ' 當(dāng)然實(shí)際上你很可能在這里做一些通常的計(jì)算
        ' 把$1加到稅上來演示發(fā)生了一些事情
        CalcOM.Tax += 1
        ' 在對(duì)象模型中調(diào)用FireEvent方法來激發(fā)AfterCalculateTax事件
        CalcOM.FireEvent("AfterCalculateTax")

        ' 從對(duì)象模型返回稅務(wù)計(jì)算的總數(shù)
        Return CalcOM.Tax
    End Function

當(dāng)網(wǎng)絡(luò)服務(wù)被調(diào)用時(shí),腳本代碼將真正的執(zhí)行稅務(wù)計(jì)算,并且服務(wù)將通過對(duì)象模型中的稅屬性從腳本返回結(jié)果。所有現(xiàn)在剩下的都是把腳本編寫者的能力添加到實(shí)際編寫腳本和把它存到服務(wù)器當(dāng)中。

為腳本提供開發(fā)環(huán)境

.NET Framework和VSA的腳本提供的超過Microsoft? Windows?腳本的一個(gè)主要好處是有完全特性的VSA IDE,它為腳本編寫者創(chuàng)建它們自己的自定義的腳本提供了第一流的編輯和調(diào)試環(huán)境。scripthappens.com 網(wǎng)絡(luò)服務(wù)無論在編輯和調(diào)試腳本代碼方面都從VSA IDE獲得完全的好處。把VSA IDE集成到你的應(yīng)用程序中是通過為你要使用的腳本語言設(shè)計(jì)時(shí)引擎而完成的。(在Visual Studio for Applications 1.0版中,我們只有時(shí)間去為Visual Basic .NET 設(shè)計(jì)事件引擎。)

設(shè)計(jì)時(shí)引擎的關(guān)鍵設(shè)計(jì)點(diǎn)是它應(yīng)該和用來控制像代碼項(xiàng)目和對(duì)象的腳本引擎(IVsa )有相同的接口(因此你就不必為了公共功能學(xué)習(xí)不同的主接口)。當(dāng)然它應(yīng)該也裝配一系列設(shè)計(jì)時(shí)接口(IVsaDT ),來提供對(duì)VSA IDE的訪問。VSA SDK提供了一個(gè)設(shè)計(jì)時(shí)集成類,它使得對(duì)VSA IDE集成變得容易,很大程度上與運(yùn)行時(shí)類使得主管運(yùn)行一個(gè)腳本引擎變得一樣的方法相同。

由于網(wǎng)絡(luò)服務(wù)沒有一個(gè)特定的Windows用戶,我設(shè)計(jì)了一個(gè)機(jī)制藉此VSA IDE可以從一個(gè)網(wǎng)絡(luò)瀏覽器來例示。由于VSA IDE可以被一系列接口所控制,在網(wǎng)頁(yè)中從腳本調(diào)用IDE就不是一個(gè)選擇了。Windows腳本只能通過IDispatch 調(diào)用對(duì)象,并且VSA IDE可以做一些潛在不安全的事情(像從磁盤上讀寫),因此這將不能與大多數(shù)網(wǎng)站的主要安全框架在一起工作。為了提供從一個(gè)瀏覽器對(duì)VSA IDC的訪問,我已經(jīng)從國(guó)際互聯(lián)網(wǎng)探索器的MIME處理特性得到好處來創(chuàng)建.NET Windows Forms應(yīng)用程序,解釋一個(gè)XML文件(由網(wǎng)絡(luò)服務(wù)器產(chǎn)生),并且使用IvsaDT接口來啟動(dòng)VSA IDE。在我介紹程序怎樣做這些事情的細(xì)節(jié)之前,對(duì)Microsoft? Internet Explorer MIME句柄做些解釋在這種請(qǐng)況下就變得重要了。

Internet Explorer使用內(nèi)置的Windows能力來允許MIME所包含的類型與應(yīng)用程序聯(lián)合使用。MIME包括的類型是你在Windows中應(yīng)該很熟悉的相似文件擴(kuò)展名映射的超集。我創(chuàng)建一個(gè)MIME包含類型,VSA配置文件,并且用用擴(kuò)展名.vsa代表包含類型。 使用可以從文件夾選項(xiàng)控制面板小應(yīng)用程序(applet)得到的文件類型編輯器,我把MIME包含的類型與運(yùn)行VSA IDE的.NET應(yīng)用程序結(jié)合在一起。結(jié)果是,無論何時(shí)一個(gè).vsa或VSA配置文件MIME包含類型被下載,VSA IDE主應(yīng)用程序都將被運(yùn)行,將解釋文件中的XML,并且使用包含在XML中的信息來加載腳本代碼并展示VSA IDE。這個(gè)方法當(dāng)然不是尖端科學(xué),并且在許多方面它的技術(shù)非常低。然而它為從一個(gè)網(wǎng)絡(luò)瀏覽器內(nèi)部運(yùn)行VSA IDE提供一個(gè)可擴(kuò)展的和一流的解決方案。

這種方法有一個(gè)很明顯的問題,然而:你怎樣把MIME映射和應(yīng)用程序添加到你的用戶機(jī)器上,如果他們?cè)诮⒛愕膽?yīng)用程序之前就下載了.vsa文件呢?幸運(yùn)的是,Windows為這個(gè)提供了一個(gè)很好的解決方案,但是為了運(yùn)行,它要求你的用戶在有一個(gè)域服務(wù)器的內(nèi)部局域網(wǎng)上面工作。

如果你的機(jī)器聯(lián)接到一個(gè)域,這樣當(dāng)一個(gè)用戶下載一個(gè)已經(jīng)告知管理器的文件類型的文件時(shí)你就可以告知MIME和文件擴(kuò)展名管理器,并且他們不會(huì)安裝軟件,Windows會(huì)自動(dòng)為他們安裝管理器。這意味著你可以確保你的用戶在下載一個(gè).vsa類型文件時(shí)總是會(huì)另VSA IDE顯露出來。需要更多關(guān)于安裝這個(gè)解決方案的信息,可以查看Step-by-Step Guide to Software Installation and Maintenance 。

管理VSA IDE

現(xiàn)在我們已經(jīng)知道MIME管理器怎么能提供一種機(jī)制來幫助通過一個(gè)網(wǎng)絡(luò)瀏覽器管理VSA IDE,那么讓我們看一看,.NET Windows Forms程序?qū)嶋H是怎樣使用VSA SDK 設(shè)計(jì)時(shí)類與VSA IDE和代碼管理器進(jìn)行信息交換,來找到并存儲(chǔ)源腳本和已編譯的代碼。

這個(gè)例子中的主應(yīng)用程序的關(guān)鍵是在.vsa文件中包含的XML。我們所選擇用于例子的XML計(jì)劃提供了所有需要加載腳本代碼和腳本對(duì)象模型的信息。很明顯,由于網(wǎng)絡(luò)服務(wù)的對(duì)象模型是相當(dāng)知名的,因此我們本應(yīng)該很把它牢固地嵌入到應(yīng)用程序中。無論如何,我們認(rèn)為試圖開發(fā)一個(gè)你可以當(dāng)作你將來的Visual Studio for Applications項(xiàng)目的基礎(chǔ)的通用系統(tǒng)是重要的。例如,如果我們?yōu)閟cripthappens.com真正開發(fā)這個(gè)系統(tǒng),有一個(gè)我們可以在將來的項(xiàng)目中再次使用的解決方案將是件很不錯(cuò)的事情。

XML模式例子

<application
   name="calculator"
   targeturl="http://localhost/ScriptHappens/default.aspx"
   moniker="com.scripthappens://calculate"          
   language="Microsoft.VisualBasic.Vsa"
   codeProviderURL="http://localhost/scripthappens/codeprovider.asmx" >

   <reference
   name="ScriptHappens"
   assembly="C:\Inetpub\wwwroot\scripthappens\bin\scripthappens.dll"
   />

   <class name="Calculate" >
      <event name="calculateObjectModel" type = "scripthappens.calculateObjectModel" />
   </class>

</application>

這個(gè)程序的主要部分是應(yīng)用程序元素。里面包含所有程序相關(guān)的信息,包括名稱和自定義的名字,這應(yīng)該與運(yùn)行時(shí)集成相似。除了運(yùn)行時(shí),targetURL和代碼管理器網(wǎng)絡(luò)服務(wù)外,設(shè)計(jì)時(shí)引入了許多概念。targetURL 用于用戶在他們的VSA IDE中運(yùn)行他們的代碼的時(shí)候。由于你的應(yīng)用程序?qū)嶋H上將控制用戶編寫的腳本代碼的運(yùn)行,而不是IDE,所以VSA IDE不能只是運(yùn)行代碼。

為了解決這個(gè)問題,IDE將回調(diào)主應(yīng)用程序來做加載腳本代碼所需要的事情。由于這個(gè)例子展示了如何在網(wǎng)絡(luò)服務(wù)中使用腳本,targetURL 就被添加到調(diào)用計(jì)算網(wǎng)絡(luò)服務(wù)的網(wǎng)頁(yè)中。這意味著在VSA IDE中運(yùn)行代碼將造成計(jì)算網(wǎng)絡(luò)服務(wù)被啟動(dòng),而任何用戶添加到他/她的腳本代碼中的斷點(diǎn)將造成IDE在網(wǎng)絡(luò)服務(wù)運(yùn)行時(shí)調(diào)試他們的腳本。

VSA Design-Time 類使用一個(gè)代碼管理器來處理腳本代碼的持久性,但是代替使用局部實(shí)例,它將把代碼管理器作為網(wǎng)絡(luò)服務(wù)來調(diào)用。對(duì)于VSA SDK來說這是關(guān)鍵的設(shè)計(jì)點(diǎn)。我們希望確保代碼的持久性已經(jīng)是可能的事情,并且特別是它要可以處理遠(yuǎn)程代碼存儲(chǔ),而這些代碼也許是在防火墻后面,因?yàn)檫@對(duì)我們來說是關(guān)鍵的情節(jié)。為了通過一個(gè)網(wǎng)絡(luò)服務(wù)調(diào)用代碼管理器,所有你所需要的是使用方法SetCodeProviderRemote 并且給執(zhí)行IcodeProvider 的網(wǎng)絡(luò)服務(wù)提供URL。在例子中,我已經(jīng)讓代碼管理器在codeprovider.asmx 中執(zhí)行。這里我們感興趣的是,計(jì)算網(wǎng)絡(luò)服務(wù)在局部使用代碼管理器。那就是,它不會(huì)通過SOAP創(chuàng)建實(shí)例,但是使用與網(wǎng)絡(luò)服務(wù)相同的執(zhí)行。

設(shè)立設(shè)計(jì)時(shí)(Design Time) 引擎

從應(yīng)用程序部件獲得的信息會(huì)在創(chuàng)建設(shè)計(jì)時(shí)類時(shí)使用。設(shè)計(jì)時(shí)類與運(yùn)行時(shí)間類的設(shè)計(jì)多少有些不同。它需要可以允許主應(yīng)用程序管理任何引擎,因?yàn)閂SA IDE將被用來編輯系統(tǒng)中的代碼,并且這里將會(huì)有多于一個(gè)的引擎在系統(tǒng)中使用來提供自定義的。例如,如果在解決方案中有兩個(gè)網(wǎng)絡(luò)服務(wù),那么每個(gè)網(wǎng)絡(luò)服務(wù)中就會(huì)有一個(gè)腳本引擎。VSA IDE將不得不對(duì)兩個(gè)腳本項(xiàng)目都進(jìn)行引導(dǎo)。結(jié)果是,設(shè)計(jì)時(shí)類提供了一個(gè)簡(jiǎn)單的方法來創(chuàng)建多個(gè)設(shè)計(jì)時(shí)引擎。比通過引擎中的接口提供一個(gè)抽象要好,它只是傳回一個(gè)引擎的實(shí)例,你將依靠Ivsa和IvsaDT對(duì)其進(jìn)行編程。為了說明這一點(diǎn),我將不去注意代碼的要求來設(shè)立設(shè)計(jì)時(shí)引擎并運(yùn)行它。

XML程序?qū)⒃谥鲬?yīng)用程序的Windows Form的加載事件中進(jìn)行解析。為了對(duì)XML進(jìn)行解析,我使用了XML解析器,它作為.NET Framework的一部分使用,簡(jiǎn)單地通過應(yīng)用程序部件的屬性重復(fù),并為后面使用設(shè)計(jì)時(shí)類存儲(chǔ)數(shù)據(jù)。為了提供一些什么在進(jìn)行的用戶反饋,程序使用Windows Form Progress Bar 控件并且把過程數(shù)值設(shè)置為只讀屬性。這相當(dāng)簡(jiǎn)單,但是相當(dāng)有效。

' 打開在命令行輸入的XML文件
                Dim xml As XmlDocument
                xml = New XmlDocument()
                xml.Load(args(1))

                Dim node As XmlNode
                node = xml.SelectSingleNode("application")

                Me.ProgressBar1.Value = 5
                ' 忽略whitespace
                '        myXML.WhitespaceHandling = WhitespaceHandling.None
                Me.ProgressBar1.Value = 10

                ' 得到名字
                custName = node.Attributes.ItemOf("name").Value
                Me.ProgressBar1.Value = 15

                ' 得到targeturl
                targetUrl = node.Attributes.ItemOf("targeturl").Value
                Me.ProgressBar1.Value = 20
                ' 得到名稱

                moniker = node.Attributes.ItemOf("moniker").Value
                Me.ProgressBar1.Value = 25

                ' 得到引擎語言
                language = node.Attributes.ItemOf("language").Value
                Me.ProgressBar1.Value = 30

                ' 得到代碼管理器URL
                codeProviderURL = node.Attributes.ItemOf
("codeProviderURL").Value
                Me.ProgressBar1.Value = 35

一旦所有應(yīng)用程序部件的信息都被解析,程序就會(huì)有足夠的信息來開始使用設(shè)計(jì)時(shí)類。在scripthappens.com 類中,我創(chuàng)建了一個(gè)類,它擴(kuò)展了VSA 設(shè)計(jì)時(shí)類來使得集成更簡(jiǎn)單。我當(dāng)然推薦你在使用設(shè)計(jì)時(shí)類的時(shí)候做相似的事情。這個(gè)類有一個(gè)把所有信息包含在應(yīng)用程序部件中的構(gòu)造函數(shù),因此它只關(guān)系到創(chuàng)建類的一個(gè)實(shí)例并把所有消息傳送到構(gòu)造函數(shù)中。

' 創(chuàng)建webhost類的實(shí)例

      myhost = New dthost(Me, custName, moniker, targetUrl, language, codeProviderURL)
                Me.ProgressBar1.Value = 60
類dthost 的構(gòu)造函數(shù)使用代碼管理器URL為設(shè)計(jì)時(shí)類設(shè)置代碼管理器來加載腳本代碼。作為防范,類要進(jìn)行檢查來看看URL是否為空和它是否使用局部代碼管理器。一旦代碼管理器被設(shè)立,設(shè)計(jì)時(shí)類就有了所有需要用來加載或創(chuàng)建一個(gè)VSA項(xiàng)目的信息,因此可以很安全地創(chuàng)建一個(gè)新的設(shè)計(jì)時(shí)引擎。設(shè)計(jì)時(shí)類被設(shè)計(jì)為可以很輕松地通過提供一個(gè)引擎的集合來處理多個(gè)引擎,VsaEngines 。這個(gè)集合有一個(gè)方法,create ,它將返回一個(gè)新引擎并且添加到這個(gè)集合中。在這個(gè)例子中,為了保持簡(jiǎn)單,這里只使用了一個(gè)引擎,但是有一個(gè)集合將使你的生活稍微輕松一些。

  
 '------------------------------------------------------------------
            ' 為這個(gè)引擎設(shè)置代碼管理器
            '------------------------------------------------------------------
            If "" = strCodeProviderURL Then
                SetCodeProviderLocal(New DiskCodeProvider())
            Else
                SetCodeProviderRemote(strCodeProviderURL)
            End If

一旦代碼管理器被設(shè)置完畢,主類就將查看這里是否已經(jīng)提供了這個(gè)名稱的存在地項(xiàng)目。這實(shí)現(xiàn)起來相當(dāng)簡(jiǎn)單,只是調(diào)用基類VSA SDK DT中的方法 LoadEngineSource 并且抓住任何意外。如果項(xiàng)目已經(jīng)存在,那么我們就做得差不多了,因?yàn)轫?xiàng)目中包含了對(duì)象模型和引用所需要的所有信息。然而如果項(xiàng)目不存在,那么我們就需要從集合中移走老的引擎,并且繼續(xù)白手起家創(chuàng)建新的引擎。新的引擎將從現(xiàn)在開始使用來添加代碼,對(duì)象模型和參考。在這階段,除了我們要對(duì)引擎進(jìn)行初始化就沒有別的什么事情了,因此代碼調(diào)用引擎中的方法InitNew 。這告訴引擎已經(jīng)進(jìn)行了初始化,而它可以繼續(xù)并且完成創(chuàng)建新引擎的過程。

     
 Try
                '------------------------------------------------------------------
                ' 試圖加載我們的引擎
                '------------------------------------------------------------------
                LoadEngineSource(strMoniker, Nothing)

                '------------------------------------------------------------------
                ' 如果成功把newEngine設(shè)置為False
                '------------------------------------------------------------------
                newEngine = False

            Catch e As Exception
                '------------------------------------------------------------------
                ' 加載失敗這是一個(gè)新類型引擎
                '------------------------------------------------------------------
                VsaEngines.Remove(strMoniker)

                dtEngine = VsaEngines.Create(strMoniker, strLanguage, Nothing)
                engine = dtEngine

                engine.InitNew()

                '------------------------------------------------------------------   
                ' 設(shè)置引擎名稱
                '------------------------------------------------------------------
                engine.Name = strCustName
            End Try

在主應(yīng)用程序中,有一個(gè)對(duì)dtClass 的接口中的屬性NewEngine 的簡(jiǎn)單檢查。如果是一個(gè)新引擎,那么我們需要把參考和對(duì)象模型添加到引擎中;否則,就沒有事情要做,因?yàn)轫?xiàng)目已經(jīng)被加載了。

把參考和對(duì)象模型添加到引擎中

這里我們有一個(gè)已經(jīng)準(zhǔn)備好為項(xiàng)目添加源代碼、對(duì)象模型和參考的DT引擎。主應(yīng)用程序使用的XML程序包含所有將被添加到VSA項(xiàng)目中的參考信息,因此這是需要添加到引擎中的第一樣?xùn)|西。把參考添加到設(shè)計(jì)時(shí)引擎與有腳本的.NET Framework引擎十分相同。所有的引擎都執(zhí)行IVsa 。這只是用VsaItemType.Reference 的項(xiàng)目類型調(diào)用CreateItem 并把項(xiàng)目中的AssemblyName 設(shè)置為集合的路徑的一種方式。XML包含要加載的參考的名字和路徑,因此這就相當(dāng)簡(jiǎn)單了。為了使這更簡(jiǎn)單,類dtHost 通過方法AddReference提供了這樣的功能,它取得了名字和路徑。

'獲得參考
                nodeList = node.SelectNodes("reference")
                For Each subNode In nodeList
                    refName = subNode.Attributes.ItemOf("name").Value
                    refAssembly = subNode.Attributes.ItemOf("assembly").Value
                    myhost.AddReference(refName, refAssembly)
                Next

一旦所有的參考被添加盜引擎中,所有要去做的事情就是添加對(duì)象模型,腳本編寫者將用它來編程。向引擎中添加對(duì)象是通過調(diào)用引擎的一個(gè)代碼項(xiàng)目中的方法AddEventSource 而實(shí)現(xiàn)的。在例子中使用的XML中,所有的對(duì)象都是事件源對(duì)象,它們將被添加到類項(xiàng)目中。為了幫助這些動(dòng)作的執(zhí)行,類dtHost 提供了一個(gè)創(chuàng)建和簡(jiǎn)介事件源對(duì)象的代碼項(xiàng)目的抽象。

                Dim nodeList As XmlNodeList
                nodeList = node.SelectNodes("class")

                Dim classNode As XmlNode
                Dim stepit As Integer

                Dim eventNodeList As XmlNodeList

                stepit = 20 / nodeList.Count

                For Each classNode In nodeList
                    className = classNode.Attributes.ItemOf("name").Value
                    myhost.AddClass(className)

                    eventNodeList = classNode.SelectNodes("event")
                    Dim eventNode As XmlNode

                    For Each eventNode In eventNodeList
                        eventName = eventNode.Attributes.ItemOf("name").Value
                        eventType = eventNode.Attributes.ItemOf("type").Value
                        myhost.AddEvent(className, eventName, eventType)
                    Next
                    Me.ProgressBar1.Value = Me.ProgressBar1.Value + stepit
                Next

對(duì)象模型在服務(wù)器上的裝配和性能

在運(yùn)行服務(wù)器上的腳本時(shí)決定如果把對(duì)象模型添加到引擎中是很重要的,這里腳本代碼的速度和可測(cè)量性都是很重要的。一個(gè)VSA引擎可以用兩種方式添加對(duì)象:作為一個(gè)全局對(duì)象(不能激發(fā)事件)和作為一個(gè)事件源對(duì)象。全局對(duì)象令人驚訝的在腳本代碼的全局都有效,而結(jié)果是定義好不變的。靜態(tài)對(duì)象在一個(gè)單線程環(huán)境中運(yùn)行時(shí)是很好的,但是對(duì)于多線程環(huán)境的關(guān)注也是很重要的考慮。

在一個(gè)多線程的環(huán)境中,這樣的代碼在ASP.NET服務(wù)器中運(yùn)行,那里在任何時(shí)刻都潛在地會(huì)有許多單腳本的實(shí)例在運(yùn)行,并且用腳本聲明的靜態(tài)變量將被幾乎所有腳本所共享。因此,如果一個(gè)全局對(duì)象在腳本中,那么這個(gè)運(yùn)行的腳本的所有實(shí)例都將共享相同的對(duì)象實(shí)例。

為了說明這個(gè)潛在的問題,試想如果腳本中有一個(gè)名為“title”的簡(jiǎn)單字符串屬性定義的全局對(duì)象。腳本的第一個(gè)實(shí)例把title屬性設(shè)置為“Hello World,”,并且會(huì)返回屬性的數(shù)值。在只有一個(gè)腳本實(shí)例在運(yùn)行時(shí),這是很好的?,F(xiàn)在再設(shè)想一個(gè)相同的情節(jié),但是這次是在設(shè)置屬性和返回?cái)?shù)值之間,第二個(gè)引擎加載了一些但腳本稍微不同的相同對(duì)象模型。第二個(gè)腳本的第一行把title屬性設(shè)置為“hi from script 2”。由于在第一個(gè)腳本返回屬性的數(shù)值時(shí),對(duì)象模型的實(shí)例被兩個(gè)腳本所共享,那么就會(huì)得到“hi from script 2”。兩個(gè)腳本在同一時(shí)刻都試圖去訪問屬性時(shí),事情也許會(huì)變得更糟-不是很好的景象。

幸運(yùn)的是,這里有一種方法上下文隔離,來解決多多線程環(huán)境中的靜態(tài)變量的問題。上下文隔離確保靜態(tài)變量不能在所有實(shí)例中被共享,而Visual Studio for Applications 使用這種機(jī)制來確保你不會(huì)陷入我前面所描述的競(jìng)爭(zhēng)問題。上下文隔離通過為每個(gè)線程創(chuàng)建靜態(tài)變量來管理這些。運(yùn)行在每個(gè)線程中的腳本代碼把靜態(tài)代碼看作與以前一樣,但是它看到的是為線程提供的上下文隔離的拷貝。

不幸的是,使用上下文隔離不是沒有代價(jià)的。為每個(gè)靜態(tài)變量創(chuàng)建一份拷貝,并且提供一個(gè)下部基礎(chǔ)來處理所有這些拷貝,招致要考慮性能的降低。在我們的性能測(cè)試站點(diǎn),我們看到與使用實(shí)例變量相比性能下降了50%。

為了避免使用靜態(tài)變量,并且避免他們的性能影響,看來要采用依靠服務(wù)器的方法了。由于創(chuàng)建在服務(wù)器上有效運(yùn)行的自定義的代碼是我們這版中的一員,因此幸運(yùn)的是這是我們已經(jīng)加到Visual Studio for Applications的東西。這里有兩種方法讓你可以把對(duì)象模型添加到得到靜態(tài)聲明變量的VSA:全局對(duì)象,和模型中包含的事件源對(duì)象。然而,添加到類項(xiàng)目中的事件源對(duì)象得到實(shí)例變量,它們將沒有任何性能問題。

如果你對(duì)模塊中的事件源對(duì)象必須是靜態(tài)的感到疑惑;原因就是在模塊中聲明的所有變量都是靜態(tài)的。因此-并且這將很有希望不會(huì)造成驚奇-我極力推薦你在類中給你為了在服務(wù)器上運(yùn)行腳本提供的全部對(duì)象模塊使用事件源對(duì)象。如果你使用靜態(tài)對(duì)象,那么VSA確保變量的什么包含ContextStatic 屬性,它告訴.NET創(chuàng)建上下文隔離,這樣就不會(huì)有腳本代碼陷入競(jìng)爭(zhēng)的問題了。(這一定會(huì)比它需要的慢很多。)因此所有例子中的事件源對(duì)象都被添加到類中來給出一個(gè)很好的例子。

            Me.ProgressBar1.Value = 90
                myhost.InitCompleted()

                Me.ProgressBar1.Value = 100
                Button1.Visible = True

在使用引擎前,所有還需要做的事情是通過調(diào)用方法InitCompleted 來告訴它初始化已經(jīng)完成。

   '  對(duì)引擎做的所有事情都已經(jīng)完成,因此調(diào)用InitCompleted
            dtEngine.InitCompleted()

介紹IDE

現(xiàn)在VSA設(shè)計(jì)時(shí)引擎已經(jīng)得到所有所需的信息來允許開始編輯腳本,因此主應(yīng)用程序需要通知引擎使IDE可見。幸運(yùn)的是,這非常非常容易實(shí)現(xiàn),只要調(diào)用引擎中的方法ShowIDE 就可以了。為了使這個(gè)過程顯得有些活力,類dtHost 提供了一個(gè)方法Show 去調(diào)用方法ShowIDE ,而把它包含在一個(gè)catch塊中來俘獲任何意外并且為使用類dtHost 的應(yīng)用程序拋出適當(dāng)?shù)囊馔?。在顯示了IDE之后,主應(yīng)用程序把自己最小化來使用戶可以看到VSA IDE。

   '顯示VSA IDE
                myhost.Show()
                '把自己最小化使用戶可以看到VSA IDE
                Me.WindowState = FormWindowState.Minimized

編寫腳本代碼

可以得到給一個(gè)應(yīng)用程序編寫腳本的全特性開發(fā)環(huán)境所需要的事情是你不再必須做你自己的(這通常是作為文本框的改變而結(jié)束。)。同樣,因?yàn)镮DE為你提供了所有信息,而你也不用為可以得到哪些對(duì)象而擔(dān)心,因此編寫腳本是如此的簡(jiǎn)單。

當(dāng)顯示調(diào)用把VSA IDE第一次顯示在用戶面前時(shí),將向用戶介紹所有從XML添加的項(xiàng)目信息,而一個(gè)代碼窗口將顯示添加到項(xiàng)目中的類。

 
圖2. VSA IDE 顯示項(xiàng)目和類

為了編寫腳本代碼來自定義的計(jì)算對(duì)象,腳本編寫著者必須選擇他或她想要描述的事件。在VSA IDE中做這件事情是相當(dāng)簡(jiǎn)單的;只是從代碼編輯器上面的對(duì)象列表中選擇calculateObjectModel,而接下來從事件列表中選擇事件。

 
圖 3. 對(duì)象列表框

 
圖4. 事件信息

選擇事件將造成一個(gè)事件句柄被自動(dòng)添加到類中,腳本編寫者就可以用來編寫腳本了。

 
圖5. 所添加的事件句柄

由于VSA IDE給為VSA引擎提供的對(duì)象提供了完全的Microsoft? IntelliSense?支持,因此對(duì)應(yīng)用程序提供的對(duì)象模塊進(jìn)行訪問也是相當(dāng)簡(jiǎn)單的。用戶只需要輸入對(duì)象的名稱和域,VSA IDE就會(huì)提供所有對(duì)象成員的列表。如果你已經(jīng)使用過Visual Basic for Applications (VBA),這對(duì)你來說不應(yīng)當(dāng)陌生,但是對(duì)于一個(gè)腳本用戶,這是一個(gè)主要步驟。

 
圖6. calculateObjectModel的成員信息

保存代碼

一旦你滿意你編寫的代碼,你就需要能保存它。Visual Studio for Applications 和.NET Framework腳本的一個(gè)關(guān)鍵好處是用來運(yùn)行腳本的應(yīng)用程序可以確定這些代碼存儲(chǔ)在哪里。所有開發(fā)者都對(duì)點(diǎn)擊保存安鈕感到厭煩。當(dāng)你點(diǎn)擊保存,VSA IDE將回調(diào)主應(yīng)用程序并且提供你已經(jīng)寫好的所有代碼,因此主應(yīng)用程序可以保存它。你的應(yīng)用程序選擇保存你的應(yīng)用程序代碼的地方對(duì)你完全是不隱瞞的。許多人選擇把它保存在數(shù)據(jù)庫(kù)中。為了保持事情的簡(jiǎn)單,scripthappens.com 計(jì)算網(wǎng)絡(luò)服務(wù)把它的代碼存儲(chǔ)在磁盤上scripthappens vroot 的文件夾中。

VSA SDK關(guān)注大多數(shù)要保存代碼的下層結(jié)構(gòu),來用代碼管理器真正實(shí)現(xiàn)代碼的延續(xù)。你所有的代碼所需要做的是確定是保存為源代碼形式還是編譯后的形式。通常,你會(huì)希望保存編譯后的代碼,特別是當(dāng)編寫服務(wù)器自定義的時(shí)。在例子的代碼中,類dtHost 提供了一個(gè)先保存源代碼,編譯這個(gè)源代碼(只是在還沒有編譯時(shí)),最后再保存編譯后的狀態(tài)的方法SaveVsaEngine 。

Sub SaveVsaEngine()
        Try
            '------------------------------------------------------------------
            ' 保存源狀態(tài)
            '------------------------------------------------------------------       
            SaveEngineSource(engine.RootMoniker, Nothing)

            '------------------------------------------------------------------
            ' 編譯并保存自定義的代碼
            '------------------------------------------------------------------
            If engine.Compile() Then
                SaveEngineCompiledState(engine.RootMoniker, Nothing)
            End If

        Catch e As Exception
            MsgBox("Error Saving Engine") ' & e.StackTrace)
        End Try

End Sub

當(dāng)方法SaveEngineSource 和 SaveEngineCompiledState 在類VSA SDK DT 中被調(diào)用時(shí),這個(gè)類調(diào)用代碼管理器方法PutSourceCode 和 PutBinaryCode 依靠SOAP,來存儲(chǔ)代碼。

運(yùn)行代碼

已經(jīng)編寫了腳本代碼并且被你的代碼管理器保存到你代碼倉(cāng)庫(kù)。所有接下來要做的就是運(yùn)行代碼并且看它如何工作了。

由于腳本代碼在服務(wù)器上運(yùn)行,確保它以正確的方式運(yùn)行就十分重要了。你的應(yīng)用程序?qū)⑦\(yùn)行腳本,所以VSA IDE依靠主應(yīng)用程序來確保腳本運(yùn)行得正確。XML程序定義在應(yīng)用程序部件中包含了一個(gè)targetURL 屬性來幫助解決這個(gè)問題。

例子中的dtHost 類使用targetURL 的數(shù)據(jù)來告訴VSA運(yùn)行時(shí)類在用戶要運(yùn)行或調(diào)試腳本時(shí)該啟動(dòng)哪個(gè)URL。設(shè)計(jì)時(shí)類執(zhí)行IVSADTSite ,它在用戶運(yùn)行代碼并啟動(dòng)URL時(shí)被VSA IDE回調(diào)。當(dāng)URL被啟動(dòng)時(shí),用戶輸入調(diào)用網(wǎng)絡(luò)服務(wù)所需的消息并且提交它。當(dāng)消息被提交給網(wǎng)絡(luò)服務(wù)時(shí),網(wǎng)絡(luò)服務(wù)就將被實(shí)例化,然后加載已編譯的腳本代碼并運(yùn)行腳本代碼。

由于你不用做運(yùn)行代碼之外的其他事情,調(diào)試腳本就相當(dāng)自由了。與只是運(yùn)行腳本唯一不同的地方是腳本編寫者要選擇他們希望中斷和運(yùn)行代碼的行。運(yùn)行又造成URL被加載,而腳本在網(wǎng)絡(luò)服務(wù)被調(diào)用時(shí)被加載;當(dāng)腳本代碼運(yùn)行時(shí),VSA IDE已經(jīng)準(zhǔn)備好在斷點(diǎn)中斷代碼。

 
圖7. 調(diào)試腳本代碼

存儲(chǔ)并找回腳本代碼

在本文的各處,我已經(jīng)提到VSA運(yùn)行時(shí)和設(shè)計(jì)時(shí)類似如何使用代碼管理器存儲(chǔ)和找回腳本代碼,但是我還沒有詳細(xì)介紹如何執(zhí)行代碼管理器。為了正確運(yùn)行代碼管理器,就需要完全的腳本診所(一個(gè)我將在以后做的東西)。然而,我將在這里復(fù)習(xí)一下基礎(chǔ)知識(shí),這樣呢就可以知道例子中的代碼管理器是如何使用的了。

一個(gè)代碼管理器是一個(gè).NET部件,它執(zhí)行ICodeProvider 接口并把名稱翻譯為應(yīng)用程序所使用的存儲(chǔ)形式。例如,一個(gè)代碼管理器會(huì)把名稱轉(zhuǎn)換為一系列SQL服務(wù)器或XML文檔中的查詢。這個(gè)接口被設(shè)計(jì)來允許加載,存儲(chǔ)并刪除源和二進(jìn)制代碼。設(shè)計(jì)將保持簡(jiǎn)單和無國(guó)籍,這樣部件就可以在本地調(diào)用或作為網(wǎng)絡(luò)服務(wù)。

為了保持設(shè)置過程的簡(jiǎn)單,這篇文章的例子中的代碼管理器巴所有腳本代碼保存到磁盤上。所有源和二進(jìn)制腳本都被存儲(chǔ)在網(wǎng)絡(luò)服務(wù)器中的scripthappens vroot 的vsa項(xiàng)目文件夾中。代碼管理器使用名稱中的應(yīng)用程序信息,并且在vsa項(xiàng)目文件夾中創(chuàng)建文件夾,因此com.scripthappens://calculate 最后成為vsa projectscalculate 。當(dāng)代碼管理器被從設(shè)計(jì)時(shí)主類中調(diào)用時(shí),VSA項(xiàng)目,代碼項(xiàng)目,調(diào)試信息和已編譯的程序保存在文件夾中。

 
圖8. scripthappens的代碼存儲(chǔ)

當(dāng)應(yīng)用程序運(yùn)行時(shí),運(yùn)行時(shí)主類將使用代碼管理器來從calculate文件夾加載已編譯二進(jìn)制腳本。

總結(jié)

Visual Studio for Applications 和.NET Framework腳本為你提供了一個(gè)很好的工具,來創(chuàng)建你或你的客戶可以進(jìn)行自定義的來滿足變化的需要的應(yīng)用程序。我希望這篇文章給你提供了如何創(chuàng)建可自定義的.NET應(yīng)用程序的很好的介紹。特別是,我希望你會(huì)知道怎樣使用VSA來創(chuàng)建可自定義的網(wǎng)絡(luò)服務(wù)并且提供從網(wǎng)絡(luò)瀏覽器對(duì)VSA IDE的訪問。在這里,我將感謝Wayne King ,VSA SDK組中的一個(gè)測(cè)試員,他把我的一些模糊白板上的設(shè)計(jì)概念如此好地應(yīng)用到例子中。那么,我們真的很希望聽到你的反饋,歡迎在VSA新聞組上與我們聯(lián)系,或者在msscript@microsoft.com 。

代碼門診部

Andrew Clinick 是一個(gè)Microsoft Programmability組的資深的程序管理員,因此,如果涉及到腳本,他總會(huì)想辦法進(jìn)行處理。

發(fā)布:2007-03-24 17:59    編輯:泛普軟件 · xiaona    [打印此頁(yè)]    [關(guān)閉]
相關(guān)文章:
上海OA系統(tǒng)
聯(lián)系方式

成都公司:成都市成華區(qū)建設(shè)南路160號(hào)1層9號(hào)

重慶公司:重慶市江北區(qū)紅旗河溝華創(chuàng)商務(wù)大廈18樓

咨詢:400-8352-114

加微信,免費(fèi)獲取試用系統(tǒng)

QQ在線咨詢