版本控制

版本控制

版本控制(Revision control)是一種軟體工程技巧,籍以在開發的過程中,確保由不同人所編輯的同一檔案都得到更新。

基本定義


版本控制透過文檔控制(documentation control)記錄程序各個模組的改動,並為每次改動編上序號。這種方法是工程圖(engineering drawings)維護(maintenance)的標準做法,它伴隨著工程圖從圖的誕生一直到圖的定型。一種簡單的版本控制形式,例如,賦給圖的初版一個版本等級“A”。當做了第一次改變后,版本等級改為“B”,以此類推等等。

相關係統


1.軟體系統的版本控制是指可以自行運行的各子系統的版本控制。
2.軟體系統的版本號由評測小組的人員確定,由評測小組進行版本控制工作。
3.軟體系統的版本號由3部分構成,即主版本號+次版本號+修改號。主版本號1位,只有當系統在結構和功能上有重大突破改進后才發生變化;次版本號有2位;修改號8位,採用提交時的日期,當系統進行任何修改後,包括資料庫結構發生變化,修改號都要隨之改變。例如:Ver3.31.19990317
4.各子系統的版本號獨立。
5.各軟體系統應該有顯示詳細版本號的功能。例如help菜單下的about功能。系統提交存檔時,評測服務部要進行版本號檢查。
6.新系統開發完成、或已存檔的系統進行修改,修改完成後,進行提交存檔時,由評測評測小組系統分析工程師確定新版本號、或更改版本號。
7.軟體系統,產生新的版本后,老版本的軟體系統是否繼續保存,取決於以下條件:
a.老版本的系統如果有客戶還在使用,在客戶升級以前,必須繼續保存。
b.老版本的系統已經沒有客戶使用了,並且新版本的系統已經把老系統的文檔完整地升級過來,這樣可以刪除或復蓋老版本的系統資源。
c.對於要刪除或復蓋的老版本系統,可以統一備份起來。

本地版本控制系統


許多人習慣用複製整個項目目錄的方式來保存不同的版本,或許還會改名加上備份時間以示區別。這麼做唯一的好處就是簡單,不過壞處卻不少:有時候會混淆所在的工作目錄,弄錯了文件丟了數據就沒了退路。
為了解決這個問題,人們很久以前就開發了許多種本地版本控制系統,大多都是採用某種簡單的資料庫來記錄文件的歷次更新差異。
其中最流行的一種叫做rcs,現今許多計算機系統上都還看得到它的蹤影。甚至在流行的MacOSX系統上安裝了開發者工具包之後,也可以使用rcs命令。它的工作原理基本上就是保存並管理文件補丁(patch)。文件補丁是一種特定格式的文本文件,記錄著對應文件修訂前後的內容變化。所以,根據每次修訂后的補丁,rcs可以通過不斷打補丁,計算出各個版本的文件內容。

集中化的版本控制系統


接下來人們又遇到一個問題,如何讓在不同系統上的開發者協同工作?於是,集中化的版本控制系統(CentralizedVersionControlSystems,簡稱CVCS)應運而生。這類系統,諸如CVS,Subversion以及Perforce等,都有一個單一的集中管理的伺服器,保存所有文件的修訂版本,而協同工作的人們都通過客戶端連到這台伺服器,取出最新的文件或者提交更新。多年以來,這已成為版本控制系統的標準做法。
這種做法帶來了許多好處,特別是相較於老式的本地VCS來說。現在,每個人都可以一定程度上看到項目中的其他人正在做些什麼。而管理員也可以輕鬆掌控每個開發者的許可權,並且管理一個CVCS要遠比在各個客戶端上維護本地資料庫輕鬆容易得多。
事分兩面,有好有壞。這麼做最顯而易見的缺點是中央伺服器的單點故障。若是伺服器當機一小時,那麼在這一小時內,誰都無法提交更新,也就無法協同工作。如果中央伺服器的磁碟發生故障,並且沒做過備份或者備份得不夠及時的話,還會有丟失數據的風險。最壞的情況是徹底丟失整個項目的所有歷史更改記錄,被客戶端提取出來的某些快照數據除外,但這樣的話依然是個問題,你不能保證所有的數據都已經有人提取出來。本地版本控制系統也存在類似問題,只要整個項目的歷史記錄被保存在單一位置,就有丟失所有歷史更新信息的風險。

分散式版本控制系統


於是分散式版本控制系統(DistributedVersionControlSystem,簡稱DVCS)面世了。在這類系統中,諸如Git,Mercurial,Bazaar還有Darcs等,客戶端並不只提取最新版本的文件快照,而是把原始的代碼倉庫完整地鏡像下來。這麼一來,任何一處協同工作用的伺服器發生故障,事後都可以用任何一個鏡像出來的本地倉庫恢復。因為每一次的提取操作,實際上都是一次對代碼倉庫的完整備份。
更進一步,許多這類系統都可以指定和若干不同的遠端代碼倉庫進行交互。籍此,你就可以在同一個項目中,分別和不同工作小組的人相互協作。你可以根據需要設定不同的協作流程,比方說層次模型式的工作流,這在以前的集中式系統中是無法實現的。

詳細內容


版本控制包括兩個方面:保正人人得到的是最新的版本,記錄需求的歷史版本。
如果有專門的需求管理商業工具可以助您一臂之力,由於我並沒有條件試用所有的需求管理工具,能夠向大家推薦的只有瑞理公司的RequisitePro,推薦的一個重要原因是它能夠把需求和瑞理的其他工具如Rose、TeamTest等聯繫起來,從而實現需求鏈。
能夠藉助工具將需求自動化固然很好,不過,工具使用不當也不會提高生產效率。需求管理的工具其實用簡單的Office和任一個關係型資料庫就可以解決,而且根據企業自身的特點,摸索出最適合企業用的工具。
版本控制的最簡單方法是在每一個公布的需求文檔的版本應該包括一個修正版本的歷史情況,即已做變更的內容、變更日期、變更人的姓名以及變更的原因並根據標準約定手工標記軟體需求規格說明的每一次修改。
一、版本控制業務流程
利用 WebLogic Workshop 的版本控制功能,能夠在不中斷當前正在運行的任何流程實例的情況下對業務流程進行更改。對業務流程進行版本控制時,便是創建了業務流程的子版本,該版本與其父版本共享同一公共 URI(介面)。運行時,標記為有效的流程版本便是將由外部客戶端通過公共 URI 來訪問的流程。
注意:可以對業務流程進行版本控制,但無法對與該流程關聯的單個控制項或其他與業務流程有關的組件(如 schema 和轉換)進行版本控制。對業務流程進行版本控制時,還必須對該流程的子流程進行版本控制,因為對父流程進行版本控制時,該控制對其子流程無效。
二、版本控制中的關鍵術語
簽入文件或目錄
此操作將工作目錄作為新版本複製回存儲庫。
簽出文件或目錄
此操作從存儲庫中將文件的最新修訂版本複製到工作空間。簽出目錄時,將簽出該目錄下的所有文件和子目錄。
提交文件或目錄
此操作與簽入文件或目錄相同。版本控制用戶會經常說他們“已提交更改”;這表示他們對各自文件的工作副本做了更改,並將這些更改提交到存儲庫。
衝突
當兩名開發人員對同一文件的工作副本進行更改,並將這些更改提交到存儲庫時,他們的工作可能會發生衝突。在這種情況下,CVS 或 Subversion 將檢測衝突,並要求某個人先解決該衝突,然後再提交他們的更改。
合併
將對相同文件的不同工作副本進行的多個更改合併到源存儲庫中。合併是一種管理衝突的策略,它允許多名開發人員同時工作(不必對文件進行鎖定),然後將他們的工作併入一個組合版本中。當對同一文件的不同行進行兩組更改時,合併這兩組更改很容易,而合併操作也可正常進行。但對文件的同一行或幾行進行更改時,將發生衝突,這就要求有人手動編輯該文件,然後才能將這些更改成功提交到源存儲庫。
存儲庫
具有受版本控制的所有文件的完整修訂歷史的共享資料庫。
解決
當兩名開發人員試圖提交發生衝突的更改,而造成文件內的衝突時,必須通過手動編輯該文件進行處理。必須有人逐行檢查該文件,以接受一組更改並刪除另一組更改。除非衝突解決,否則存在衝突的文件無法成功提交到源存儲庫中。
修訂版本
對各個文件進行具體更新的編號草案。每次編輯文件並將它提交回存儲庫時,該文件的修訂版本號將會增加。
版本
用於標識文件集 的編號方案,可在某個時間點標記並命名這些文件集。
工作空間
要在本地硬碟或 Unix 用戶帳戶上編輯的文件副本。在工作空間中編輯文件時,這些文件將不再與存儲庫同步。這就是進度!然後您需要將更改返回存儲庫,以便其他人可以看到這些更改。
Subversion 辭彙表
Subversion 置於稱為 APR(Apache 可移植運行庫)的可移植層上。這意味著 Subversion 應該在任何運行 Apache httpd 的操作系統上工作: Windows、Linux、BSD 的所有 flavors、Mac OS XNetware 以及其它操作系統。
分支
分支是指目錄和文件的現有原始樹的副本。分支的生命周期是從某事物的副本開始的,並從此副本處移動,生成自己的歷史。通常創建分支以嘗試新功能,同時不影響具有編譯器錯誤和小問題的開發的主分支。
檢出
檢查存儲庫,會在本地計算機上創建所需分支的副本。此副本包含了您指定的存儲庫的最新版本。
提交
文件的提交意味著已將對本地副本所做的更改更新到存儲庫中。提交文件后,用戶可以查看對特定文件執行“更新”后的最新版本。
衝突
有時候,當您更新存儲庫中的文件時,可能會遇到衝突。當兩個或多個用戶更改文件中的一些相同行時,將發生衝突。
Hook
Hook 是被存儲庫事件觸發的程序,例如創建新版本或修改無版本屬性。 Hook 中保留了足夠的信息,可以告知該事件是什麼、正被操作的目標是什麼,以及觸發此事件的人員的用戶名是什麼。
鎖定
鎖定是指一種機制,在此機制下,用戶請求獲得修改工作副本文件的更改的專有許可權。
合併
合併是指將某分支上的更改聯接到此主幹或同為主幹的另一個分支。
存儲庫
Subversion 的核心為存儲庫。它是一個存儲和共享數據的集中式系統。存儲庫以一組樹和分支的形式(即目錄和文件的層次結構)存儲信息。任何數量的客戶端都可以連接到存儲庫中,並對這些文件進行讀取和寫入。
存儲庫瀏覽器
在某些情況下中,可能需要直接在存儲庫中工作而不使用工作副本。這便是存儲庫瀏覽器的由來。它與資源管理器窗口具有同樣的圖標以及用於鍵入將顯示的存儲庫 URL 名稱的地址欄。它還具有諸如複製、移動和刪除等的命令。
存儲庫 URL
可以通過本地磁碟上的不同方法或通過網路協議訪問存儲庫。存儲庫位置通常指 URL。這些 URL 使用標準的語法,其中引用要指定的伺服器名稱和埠號。
撤消
如果檢查時決定取消對文件所做的更改,可以使用“撤消”命令跳轉回先前的更改。
修訂版本
每次存儲庫接受提交時,都將創建文件系統樹的新版本,稱為修訂版本。會為每個修訂版本分配唯一號,此號比上一修訂版本的號大。剛創建的存儲庫的初始修訂版本編號為零,且其中除了空的根目錄外不包含任何信息。
修訂圖形
修訂圖形是主幹位置的圖形化表示,其中分支與標記與主幹是分離的。這與樹結構非常相似,且很容易查看這類信息。
修訂版本號
創建新存儲庫時,其生命周期從修訂版本號零開始,且每次後續提交會將修訂版本號增加一。提交后,Subversion 客戶端將提供新的修訂版本號。每個修訂版本號都會在下方掛起一棵樹,每棵樹都是存儲庫對待每次提交的方式快照。
轉換
此子命令將更新工作副本以鏡像新的 URL;通常是共享工作副本中的公共祖先的 URL。這是將工作副本移動到新分支上的 Subversion 方法。
標記
標記主要指在每個文件上置入一個標籤,無論此文件的修訂版本號。這既可以在工作副本上執行,也可以在存儲庫自身執行;其效果相同。
更新
更新可以使工作副本與用戶對存儲庫所做的最新更改同步。它會取出文件的最新工作副本置於本地驅動器上。遵照滑塊規則,總是在更改文件前更新此文件。
工作副本
工作副本是指從存儲庫獲取的文件的現有副本和已更新副本。若要獲得工作副本文件,需要執行檢出。
複製 - 修改 - 合併開發周期
由於 CVS 和 Subversion 都是功能強大的工具,所以學習過程可能會讓人望而卻步。大量的書籍和網站提供全面的 CVS 知識庫,但提供 Subversion 知識庫的並不多。但是,不是非得學會了整本書才能在軟體開發實踐中迅速有效地使用 CVS 或 Subversion。
在與項目的整體開發周期保持一致的情況下,CVS 和 Subversion 都允許您進行自己的開發。
1、要開始項目工作之前,您需要簽出源代碼。您可以簽出該項目的整個 CVS 或 Subversion 存儲庫,也可以只簽出您希望處理的那些模塊。
2、通過修改這些文件和創建新文件,為項目作出貢獻。
周期的這一部分並不直接涉及 CVS 或 Subversion。您可在本地計算機上使用文件編輯器修改項目文件的工作副本。還可以保存並編譯您編輯過的文件,以測試您所做的更改如何影響正在處理的特定項目模塊,此過程不影響其他人對同一項目文件的工作。您所執行的任何操作都不影響其他項目參與者,除非您將更改合併到項目存儲庫中。
3、您在自己的工作空間中測試和調整您最新的更改,以確保這些工作不中斷或損壞整個項目。
4、最後,將您所做的更改返回或簽入項目文件的主要或“頂級”主體,將您的工作合併到最近的工作版本(在版本控制術語表中稱為 head)。
提交您的更改以與其他開發人員的工作合併是 CVS 和 Subversion 最強大的功能,但此功能也使它成為最危險的方面。有時可能因為一時糊塗,無意中復蓋了他人或您自己的更改。您所提交的更改將始終在某些方面與其他人的更改相衝突。在合作開發項目中,理解?衝突是使用 CVS 或 Subversion 時兩個特別關鍵的方面。
所有起作用的開發人員在項目生命周期中都在不斷重複著這個複製 - 修改 - 合併周期。CVS 和 Subversion 使得每個人都能同時處理項目文件,掌握其他人進行的最新更改,以及測試自己的更改如何影響整個項目,這些過程都不會中斷其他開發人員的周期。
三、版本控制的選擇
在八月份的時候,一些讀者寫信要求我說明如何控制介面的版本。實作新版介面時,有些情況你只需要強化現有類別,在其它情況你則需要實作一個可能使用前版的新類別。我想要提出的是相信大部分讀者都會遇到的組合情況,這裡列出當您在更新 Web 服務時最常面臨到的工作:
1.新增額外的方法。新方法在概念上和現有的 Web 服務是相關連的,而且應該在相同的端點上實作。
2.變更方法簽名碼。在這個實例中,輸入組件的數目和類型會改變。
3.更新數據模型。在這個實例中,資料型別會擴充且資料成員可能會改變名稱。
為了準備場景,我想要規劃一個簡單的 Web 服務,讓它能夠接受所有類型的改變,這個 Web 服務代表版本 1。類別和這個 Web 服務會對應一個命名空間同步進行。所有動作都會隨著這篇文章逐步發展。
版本 1
我要為稍後會作的修改提供一個起點。每一個區段都建置在這個初始 Web 服務之上,然而下面的區段卻是建立在彼此的 Web 服務上。這個服務是設定用來處理訊息,以便將兩個數字加起來,以及將一些基本個人資料轉換成字元串
【WebServiceAttribute(Namespace = NamespaceConsts.AYS15Oct2002)】
public class VersionOne : System.Web.Services.WebService {
【WebMethodAttribute】
public string GetDisplayValue( Person person ) {
return person.ToString();
}
【WebMethodAttribute】
public int Add( int a, int b ) {
return a + b;
}
}
命名空間字元串會儲存在一個位置中,即 NamspaceConsts 類別,來減少由於打字錯誤產生的問題。我會大量重複使用這個命名空間值,來減少輸入錯誤字元串的機會。
public class NamespaceConsts {
///
/// 這個內容值包含用於 XML 命名空間的字元串
/// http://msdn.microsoft.com/samples/AYS/2002/10/15/
///
public const string AYS15Oct2002 =
"http://msdn.microsoft.com/samples/AYS/2002/10/15/";
///
/// 這個內容值包含用於 XML 命名空間的字元串
/// http://msdn.microsoft.com/samples/AYS/2002/10/22/
///
public const string AYS22Oct2002 =
"http://msdn.microsoft.com/samples/AYS/2002/10/22/";
}
最後,第一版的 Web 服務採用了一些其它類別:
·PersonName:這個類別包含三個字元串成員變數分別紀錄一個人的名字、中間名和姓氏。
·USAddress:這個類別包含其它成員變數代表街道地址、城市、州和郵政編碼。
·Person:這個類別包含兩個公用成員,PersonName 和 USAddress。(很驚訝吧!)
所有的類別都使用 System.Xml.Serialization.XmlTypeAttribute 來確定當類別序列化成 XML 時,這些類別是使用相同的 XML 命名空間。在這裡以 Person 類別為例子。
【XmlTypeAttribute(Namespace=NamespaceConsts.AYS15Oct2002)】
public class Person {
public PersonName Name;
public USAddress Address;
public override string ToString() {
return string.Format( "{0}\n{1}",
Name.ToString(),
Address.ToString() );
}
}
這裡非常詳細的說明這個 Web 服務版本 1 的內容。
增加額外的訊息
其中一個更新 Web 服務的方法是增加這個 Web 服務能夠接受的額外訊息。該 Web 服務支持數字相加,那麼加入數字相減的功能呢?我們要如何增加這個新訊息而不中斷現有客戶端的聯機?首先讓我們來看看,如果嘗試原有的方法而且只增加一個新的 Web 方法會有什麼影響。對於這項試驗,我建立一個叫做 Service2a.asmx 的新 Web 服務端點。在這個情況下,版本 2a 的 Web 服務只是被用來顯示如何實作這些變更。這些變更也可以應用在版本 1 的 Web 服務。於是我複製 ServiceOne.asmx.cs 的程序代碼並且加入這個新的 Web 方法。結果如下。
【WebServiceAttribute(Namespace = NamespaceConsts.AYS15Oct2002)】
public class Version2a : System.Web.Services.WebService {
【WebMethodAttribute】
public string GetDisplayValue( Person person ) {
return person.ToString();
}
【WebMethodAttribute】
public int Add( int a, int b ) {
return a + b;
}
【WebMethodAttribute】
public int Subtract( int a, int b ) {
return a - b;
}
}
如果我接著將客戶端指向這個新端點,則 Microsoft® .NET 客戶端仍然可以持續地正常執行。這告訴我什麼?這告訴我如果增加一個新訊息而不是修改現有的,已經部署的客戶端將不需任何修改就可以持續運作。更重要的問題是:「這是正確的作法嗎?」從我的觀點來看,這個問題的答案是「否定的」。我是從一個已經完全部署 Web 服務的觀點嚴格地說,而不是從您決定如何從 Beta 版操作的觀點。
所以,這是正確的作法嗎?一般說來,每當你變更這個訊息的定義時,你應該變更這個通訊埠類型 (portType) 的限定名稱 (Qualified Name)。「限定名稱」是 XML 命名空間加上通訊埠類型名稱。
在這個情況下,所有的操作在邏輯上還是相關。把 Add 和 Subtract 作業當作相同 WSDL 通訊埠類型的一部份而將他們繫結在一起是十分合理的。Web 服務的使用者會期望有任何 Proxy 產生工具將這些作業保存在一起。在這個例子中,我們想要在當把所有的操作保存在一個繫結時,管理由這個輸入和輸出訊息所使用的 XML 命名空間。我們也想要讓現有客戶端可以繼續存取 Add 和 GetDisplayValue。最後,我們想要指出這個 Web 服務使用一個新的 XML 命名空間。要做到這點,我們需要加入一些屬性來設定這個由要求和響應訊息所使用的命名空間。
【WebServiceAttribute(Namespace = NamespaceConsts.AYS22Oct2002)】
public class Version2a : System.Web.Services.WebService {
【WebMethodAttribute】
【SoapDocumentMethodAttribute(
NamespaceConsts.AYS15Oct2002 + "GetDisplayValue",
RequestNamespace=NamespaceConsts.AYS15Oct2002,
ResponseNamespace=NamespaceConsts.AYS15Oct2002 )】
public string GetDisplayValue( Person person ) {
return person.ToString();
}
【WebMethodAttribute】
【SoapDocumentMethodAttribute(
NamespaceConsts.AYS15Oct2002 + "Add",
RequestNamespace=NamespaceConsts.AYS15Oct2002,
ResponseNamespace=NamespaceConsts.AYS15Oct2002 )】
public int Add( int a, int b ) {
return a + b;
}
【WebMethodAttribute】
【SoapDocumentMethodAttribute(
NamespaceConsts.AYS22Oct2002 + "Subtract",
RequestNamespace=NamespaceConsts.AYS22Oct2002,
ResponseNamespace=NamespaceConsts.AYS22Oct2002 )】
public int Subtract( int a, int b ) {
return a - b;
}
}
依預設,Microsoft® ASP.NET Web 服務是根據 SOAPAction 來傳送訊息。SOAP 動作是由連結這個 Web 服務的 XML 命名空間和呼叫的作業名稱來建立的。所以,如果 Web 服務使用 http://tempuri.org/ 這個 XML 命名空間而且包含一個名為 foo 的作業,預設的 SOAPAction 會是 http://tempuri.org/foo。每個公開的作業會設定 SOAPAction 和命名空間來建立一組更新的作業。這個由 ASP.NET 產生的 WSDL 會將所有的作業放在同一個繫結中。作為這個 WSDL 的讀取器,我可以看出 Subtract 有時候被加在 Add 和 GetDisplayValue 的後面;作為舊 WSDL 的使用者,原來的客戶端會持續運作。任何新的客戶端同樣能夠呼叫 Subtract 並且使用修改過的 XML 命名空間。這個端點不需要中斷就能夠正確地對舊的和新的客戶端來產生響應。然而,這個端點做不到一件重要的事:證明這一個端點支持兩個繫結。我們要如何做到這點?
因為我們選擇建立這個 Web 服務的方式的不同,現有的屬性方法,亦即允許開發人員指定特定的作業所屬的繫結,在這個情況是沒有作用的。Add 和 GetDisplayValue 的訊息並不會因為任何方法而改變,意味著我不能只是「正確地」設定屬性然後就繼續進行。關於這點,您有兩個選擇:
1.撰寫一些額外的程序代碼而且放棄在一個端點上支持兩個版本。
2.將這兩個版本的繫結信息儲存在各自的檔案、並為這個端點撰寫一個自訂的 WSDL 檔案,然後關閉這個 Web 應用程序文件。
讓我們依序來看這兩種選擇。
撰寫額外的程序代碼
這個選擇的程序代碼存在 Version2b.asmx 中。如果親手來撰寫 WSDL 的主意讓你有點卻步,你可以另外選擇撰寫額外的程序代碼 (親手撰寫 WSDL 實在不是一件輕鬆事,而您可能不會想要自尋煩惱)。如果你只是要讓 ASP.NET 能夠運作,撰寫額外程序代碼的選擇會要求您部署一個新的端點。這個選擇並不會維持命名空間和現有客戶端的兼容性,意味著這個 Web 應用程序會有兩個 .ASMX 端點:一個是版本 1,而另一個是版本 2。
當我開始進行這項工作時,我的第一個直覺是,從 VersionOne 來衍生出版本 2 的 Web 服務類別,以一個新的 XML 命名空間來加入方法,然後將新命名空間中 Add 的要求重新導向到基礎類別中的 Add。不管變得更好還是更糟,VersionOne 的方法是可以透過繼承而被採用的,而且 Add 和 GetDisplayValue 在這兩個命名空間的訊息名稱會導致衝突。為什麼?因為 ASP.NET 會將相同的訊息名稱最後都視為衝突問題。我可以改變這個訊息名稱,但是將訊息名稱設定為 Addv2 實在很不吸引我。WSDL 檔案的 targetNamespace 會顯示版本信息,但我不想將這個信息記錄在這個作業名稱中 -- 這隻會讓事情變得更混亂。
我的下一個嘗試是使用委派。既然功能並沒有改變,這個 Web 服務應該可以使用前一版的方法。委派結果可以用。程序代碼有三個函式:Add、GetDisplayValue 和新的 Subtract 函式。
【WebServiceAttribute(Namespace = NamespaceConsts.AYS22Oct2002)】
public class Version2b : WebService {
【WebMethodAttribute】
public string GetDisplayValue( Person person ) {
// 呼叫原始函式
VersionOne v1 = new VersionOne();
return v1.GetDisplayValue( person );
}
【WebMethodAttribute】
public int Add( int a, int b ) {
// 呼叫原始函式
VersionOne v1 = new VersionOne();
return v1.Add( a, b );
}
【WebMethodAttribute】
public int Subtract( int a, int b ) {
return a - b;
}
}
如您所見,對 Add 和 GetDisplayValue 的呼叫會委派原來版本的要求。
自訂的 WSDL
讓我們來看一看,可以怎麼樣證明 Service2a.asmx 已經實作版本 1 和 版本 2 的繫結。一個 WSDL 檔案以 /documentation/@targetNamespace 屬性來表示它的版本,在版本 1 中 targetNamespace 屬性的內容值是 http://msdn.microsoft.com/samples/AYS/2002/10/15/,在版本 2 則是使用 http://msdn.microsoft.com/samples/AYS/2002/10/22/。
目前我能確知的是,不可能找到方法讓 ASP.NET 自動產生正確的 WSDL。這裡是我們接下來的步驟:
1.儲存 VersionOne.asmx Web 服務的 WSDL。
2.儲存 Version2a.asmx Web 服務的 WSDL。
3.從 WSDL 文件中移除服務元素。
4.將版本 1 命名空間的結構描述 (Schema) 放進各自的 XSD 檔案。
5.撰寫並儲存 Version2a.asmx Web 服務的 WSDL 檔案。
6.將文件關閉。
步驟 1 到 3 可以由聯機到 .ASMX 網頁、檢視 WSDL 然後儲存所呈現的 WSDL 到磁碟來完成。然後開啟儲存的檔案而且移除服務組件。在步驟 4,我建立一個新檔案 MessageTypes.xsd,並將 VersionOne.wsdl 中型別區段的內容存到這個檔案,然後加入這個 XSD XML 命名空間和版本 1 XML 命名空間的命名空間宣告。最後,VersionOne.wsdl 的型別區段被簡化如下:
namespace="http://msdn.microsoft.com/samples/AYS/2002/10/15/"
location="http://localhost/AYS15Oct2002/MessageTypes.xsd" />
這個 Web 服務的新 WSDL 是:
xmlns:s1="http://msdn.microsoft.com/samples/AYS/2002/10/22/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:s0="http://msdn.microsoft.com/samples/AYS/2002/10/15/"
targetNamespace="http://msdn.microsoft.com/samples/AYS/2002/10/Impl"
xmlns="http://schemas.xmlsoap.org/wsdl/">
location="http://localhost/AYS15Oct2002/VersionOne.WSDL" />
location="http://localhost/AYS15Oct2002/Version2a.WSDL" />
location="http://localhost/AYS15Oct2002/Version2a.asmx" />
location="http://localhost/AYS15Oct2002/Version2a.asmx" />
注意到這兩個連接埠的位置是相同的。最後關閉這個文件。要做到這點,必須在 web.config 中的 /configuration/system.web/webServices/protocols 區段加入下面幾行:
那麼這個選擇完成了什麼事情?我們修改了 WSDL,讓它準確反映兩個 Web 服務版本都接受的訊息。建立的新 WSDL 顯示這個端點了解兩份文件中所定義的訊息。這個基礎 Web 服務因其編碼方式而能夠接受兩種版本的訊息。
增加新方法不是開發 Web 服務的唯一方式。接下來,讓我們來看假使您想變更方法簽名碼時,要如何處理。
變更方法簽名碼
在這個例子中,我假設您想要讓現有版本仍然能夠運作,而且您想要改變特定訊息的內容。讓我們來看看改變 Add 訊息以接受一個整數數組,然後傳回這些整數的總和。這個新的 Add 訊息和舊的是不兼容的。
我可以賦予這兩個訊息不同的名稱使它們都在同一個端點操作,但這不是我想要做的。這個 WSDL 需要反映出這個方法是較新且較佳的 Add 的形式。為了讓這項分別更清楚,並與其它函式一起運作,我建立一個新的 Web 服務叫做版本 2c。我也決定將 GetDisplayValue 移到新的 XML 命名空間。這個函式還是呼叫內部的 GetDisplayValue 方法來利用現有的程序代碼和未來的錯誤修正。
【WebServiceAttribute(Namespace=NamespaceConsts.AYS22Oct2002)】
【WebServiceBinding("Version2", NamespaceConsts.AYS22Oct2002 )】
public class Version2c : System.Web.Services.WebService {
【WebMethodAttribute】
【SoapDocumentMethodAttribute(Binding="Version2")】
public string GetDisplayValue( Person person ) {
VersionOne v1 = new VersionOne();
return v1.GetDisplayValue( person );
}