最初,我並沒有想過要開設這麼一個專欄,我是在 2004 年 2 月開始醞釀這個想法的。當時,我在位 於雷蒙德的 Microsoft 總部參與一項針對即將推出的代號為“Burton”的產品的軟件設計評 審。每次評審會議上,我都會舉手提出相同的問題:“有擴展點嗎?”兩天時間裡,我總是得 到一個令我忍俊不禁的答案:“有的,Brian,你可以自定義。”Burton 就成了後來的 Visual Studio® Team System,而如何對其進行自定義即是本專欄的所有內容。
首先,和許多專欄的思路一樣,我將向您簡要介紹如何在客戶端和服務器端擴展並增強 Visual Studio Team System。接下來,我將解釋如何為 Microsoft® Word 構建一個插件,使您能夠將 Word 文檔簽入、簽出 Team Foundation Server 版本控制存儲庫。不過在開始之前,請先訪問 msdn.microsoft.com/vstudio/extend 以獲取 Visual Studio 2005 SDK 的副本。該 SDK 包含自定義和 擴展整個 Visual Studio 產品線的文檔和示例。為了構建此插件,您需要 Visual Studio 2005 Professional(或者任何包括 Team Suite 的基於角色的 SKU)的副本、Visual Studio 2005 Tools for Office Second Edition Beta 和 Visual Studio 2005 Team Explorer。由於本專欄的內容主要是關於擴 展 Team System 的,所以我不會介紹關於將代碼掛接到 Word 基礎架構的具體細節,但會提供所有代碼 供您參考。如果您需要有關編寫 Office 插件的詳細說明,請訪問 msdn.microsoft.com/office/tool/vsto。
深入探討 Team Foundation Server
開始之前, 您需要了解 Team Foundation Server 與圖 1 所示的客戶端插件或其他相關的客戶端之間的核心交互過 程。如果您要使用 Team Foundation Server,首先需要做的是連接到一個有效服務器。為此,您必須知 道服務器名、要使用的協議(HTTP 或 HTTPS)以及端口。為了執行版本控制操作,您需要一個包含工作 文件夾的工作區。工作區代表了在 Team Foundation Server 中受版本控制的項目的客戶端副本,可作為 進行工作的獨立區域。在客戶端計算機中,每個用戶可以有多個工作區。而每個工作區支持本地文件路徑 (即工作文件夾)與版本控制存儲庫中的路徑之間的多重映射。在第一次結合 Team Foundation Server 來使用 Visual Studio 2005 中的源代碼控制功能時,Team Explorer 工具會使用您的計算機名來創建一 個默認工作區。這樣,插件需要執行所有這些操作,以便能夠將文檔放入 Team Foundation Server 版本 控制存儲庫或從中取出。
圖 1 將 Word 文檔簽入 Team Foundation Server 版本控制
Microsoft 將實現 Team System 編程所需要的 API 分散放置在多個程序集中。插件與 Team Foundation Server 交互時所使用的核心 API 集包含在作為 Team Explorer 的安裝部分而安裝的三個程序集中。分別是 Microsoft.TeamFoundation.Client.dll、Microsoft.TeamFoundation.VersionControl.Client.dll 和 Microsoft.TeamFoundation.VersionControl.Common.dll。Team Explorer 安裝應用程序將程序集安裝到 全局程序集緩存 (GAC) 中。然而,安裝程序不會對這些程序集進行注冊,使其在 Visual Studio 2005 的“添加引用”(Add References) 對話框中顯示。您需要修改 Windows 注冊表,以使 “添加引用”(Add References) 對話框能顯示這些程序集(請參見 support.microsoft.com/kb/306149),也可手動浏覽定位這些程序集。您可以在 %Program Files% \Microsoft Visual Studio 8\Common7\IDE\PrivateAssemblies\ 位置找到可浏覽的程序集的副本。
連接服務器
首先要創建一個類庫,該類庫需要有對上述程序集的引用。在類庫中,您要創建稱 為 tfsvcUtil 的類以便使用 Team Foundation Server API 進行繁重的工作,該類包含一組共享成員( 在 Visual C#® 中是靜態成員)。要注意的是,需要連接到有效的 Team Foundation Server 之後才 能使用插件。當使用 Team Explorer 連接運行 Team Foundation Server 的計算機時,Team Explorer 將嘗試使用當前的身份憑據進行連接。如果身份憑據無效,Team Explorer 將顯示一個對話框,要求用戶 輸入有效的用戶名和密碼。插件的連接行為也是如此。
要使用 Team Foundation Server 進行工作,您必須首先獲取根對象 TeamFoundationServer 的一個 實例。API 允許您以多種方式連接服務器,具體取決於您對重用同一連接的需要。就插件而言,將在整個 插件生存期內使用單引用,因此您需要使用 TeamFoundationServerFactory.GetServer 方法,具體來說 ,就是使用即可接受服務器名又可接受對實施 IcredentialsProvider 的對象的引用的重載方法。如果身 份驗證失敗,您要能夠提供備用憑據,因此在調用 GetServer 之前,應當創建一個 UICredentialsProvider 實例。一旦調用 GetServer,您要通過調用從 GetServer 返回的 TeamFoundationServer 實例中的 Authenticate 方法來向服務器驗證身份。假定已經與服務器相連,您 需要獲取一個對 Team Foundation Server Version Control 服務的引用。
如前所述,Microsoft 將 Team Foundation Server 設計為具有可擴展性。服務器上分散部署的服務可以體現這一點。Team Foundation Server 圍繞版本控制、工作項跟蹤和 Team Build 提供了一組核心服務。每項核心服務有自 己的一組類,當您要與某一特定功能進行交互時使用這些類。使用 Version Control 服務時所用的最高 級別的對象是 VersionControlServer 類。可以通過調用有效 TeamFoundationServer 引用中的 GetService 方法來檢索此類型的實例。圖 2 列出了實現所討論的各個方面所必需的代碼。在此還可以看 到一些您稍後要使用的基本錯誤處理變量和其他類級別變量。
Figure 2 連接和身份驗證
' Team Foundation Server Version Control Utilities Public Class tfsvcUtil Private Shared serverValidated As Boolean = False Private Shared m_tfs As TeamFoundationServer = Nothing Private Shared m_tfsVCS As VersionControlServer = Nothing Private Shared m_userWorkspace As Workspace = Nothing Private Shared m_serverName As String = Nothing Private Const MSG_UNEXPECTED As String = _ "Unexpected exception in tfsUtil.{0}." Public Shared ReadOnly Property IsServerReady() As Boolean Get Return serverValidated End Get End Property Public Shared Sub Connect(ByVal fqServerName As String, _ ByVal showLoginUI As Boolean) Try m_serverName = fqServerName If showLoginUI Then Dim icp As ICredentialsProvider = New UICredentialsProvider m_tfs = TeamFoundationServerFactory.GetServer( _ m_serverName, icp) Else m_tfs = TeamFoundationServerFactory.GetServer(m_serverName) End If m_tfs.Authenticate() m_tfsVCS = CType(m_tfs.GetService( _ GetType(VersionControlServer)), VersionControlServer) serverValidated = True Catch ex As Exception Throw New tfsUtilException( _ String.Format(MSG_UNEXPECTED, "Connect"), ex, m_serverName) End Try End Sub Public Shared Sub Connect(ByVal serverName As String, _ ByVal protocol As String, ByVal port As Integer, _ ByVal showLoginUI As Boolean) m_serverName = String.Format("{0}://{1}:{2}", _ protocol, serverName, port) tfsvcUtil.Connect(m_serverName, showLoginUI) End Sub End Class
查看圖 2 中的代碼,您會注意到有兩種 Connect 方法。一種要求 Team Foundation Server 名稱必須采用包含協議、服務器名和端口的完全限定格式。另一種將各項服務器連接數據作為單 獨的項來接受。兩種方式均提供了一個參數,用於指定當驗證失敗時 Team Foundation Server 是否應顯 示登錄用戶界面。當所提供的身份憑據不符合要求時,您預料到會出現登錄失敗,有了這個參數,您就可 以在此類情況下重用該代碼。
一旦可以與服務器連接,接下來就是確定文檔的放置位置。為了對 文檔實施版本控制,Team Foundation Server 需要至少存在一個在啟用版本控制的情況下創建的團隊項 目。目前,Microsoft 尚未提供創建團隊項目的 API,因此在嘗試對文檔進行版本控制之前,需要您手動 完成這項工作。您的團隊項目的根樹干將被命名為 $/%TeamProjectName%。從此處可以定義任意數量的文 件夾和子文件夾。VersionControlServer 對象有一個 GetTeamProject 方法,您可以使用該方法訪問 ServerItem 屬性以獲取代表該根樹干的正確字符串。有一件事情需要您了解。如果創建了一個團隊項目 ,而沒有定義源代碼管理文件夾(在使用“新建團隊項目”向導時的一個有效選項),在調用 GetTeamProject 時,將會出現 Microsoft.TeamFoundation.VersionControl.Client.VersionControlException 異常。從 ServerItem 屬性返回的字符串提供了構建文檔文件夾結構的基礎。
連接服務器只是該語句執行結果的一部分。為了將文檔移入、移出服務器,客戶端的計算機還需要一 些配置。如前所述,Team Foundation Server 版本控制使用工作區的概念來管理版本控制的客戶端部分 。如果要將一個文件簽入、簽出,以至檢查文件是否處於版本控制下,您需要有一個工作區。要注意的是 ,您首次使用 Team Explorer 並處理版本控制下的文件時,Team Explorer 將使用您的計算機名為您創 建一個默認的工作區。您可以在 Visual Studio 2005 中通過選擇“文件”|“源代碼管 理”|“工作區”菜單項來管理您的工作區。然而,該插件不能使用這個已創建的工作區 ,因此如有必要,需要添加一些代碼來創建一個工作區。
創建工作區之前,需要檢查是否已經存 在一個工作區,以及檢查要添加到版本控制中的文檔位置是否已被映射到處於活動狀態的團隊項目。您可 以從 Workstation 類入手,使用 Workstation 對象為希望添加到源代碼控制中的文檔獲取 WorkspaceInfo 實例。
' Get workstation object Dim userWorkstation As Workstation = Workstation.Current ' We need the path of the of the document: Dim docPath As String = Environment.GetFolderPath( _ Environment.SpecialFolder.MyDocuments) & "\Test.doc" Dim docWSInfo As WorkspaceInfo = _ userWorkstation.GetLocalWorkspaceInfo(docPath)
如果文檔路徑被映射到服務器路徑,則對 GetLocalWorkspaceInfo 的調用將返回一個 hydrated(合 成)實例,否則,將不返回任何內容。您需要理解工作區和文件夾映射的一個關鍵問題。在檢查是否存在 映射時,GetLocalWorkspaceInfo 並不按照完全匹配的原則來查找,而是部分匹配即可。例如,如果有一 個路徑 C:\Docs\Specs\MySpec.doc,並且存在一個對 C:\Docs\Specs\ 路徑的文件夾映射, WorkspaceInfo 實例將按預期返回結果。然而,如果存在一個對 C:\Docs 路徑的映射,即使不存在對 C:\Docs\Specs\ 路徑的映射,API 也將返回一個有效實例。一般而言,這種隱含的關聯性正是您所需要 的。Team Foundation Server 以遞歸方式將現有映射的文件系統子文件夾映射到版本控制存儲庫中的子 文件夾。如果您沒有映射,可以通過使用 Workspace 對象的 CreateMapping 方法創建一個映射。在擁有 有效的 WorkspaceInfo 對象時,獲取 Workspace 就變得簡單了,只需調用 VersionControlServer 實例 的 GetWorkspace 即可。
版本控制下的文檔
此時,插件簽入、簽出 Word 文檔的核心功能已經可以運行了。假設您可以 登錄到運行 Team Foundation Server 的計算機,並可以訪問有效的團隊項目和本地工作區,您可以嘗試 將文件置於版本控制之下。您可以找到開展工作所需的作為 Workspace 類成員的相關方法。對於插件, 您需要能夠調用 PendAdd、PendEdit 和 CheckIn。
PendAdd 是您第一次將文檔置於版本控制下時 所使用的方法。如果查看 API 的文檔,您會看到有六種重載。您可以隨意選擇將單個項加入隊列或同時 將一組項加入隊列。該方法有可支持遞歸和調整鎖定級別能力等不同版本。對於該插件,您一次只簽入一 個文檔,這樣使用可接受文件名的第一個重載就足以滿足要求。當調用 PendAdd 時,它將返回一個整數 ,該數字代表排隊待簽入的項的數量。要進行實際的簽入,您需要使用 GetPendingChanges 方法來查詢 Workspace,以獲取包含掛起的更改的列表。Microsoft 重載了此方法,提供了九種版本。對於該插件, 您要使用可接受字符串作為輸入的那個方法版本。因為您是在一個單一文件路徑中傳遞數據,PendAdd 應 當返回 1。當您調用 GetPendingChanges 時,它應返回一個包含一個元素的 PendingChange 數組。使用 其他版本的 GetPendingChanges 時,您需要謹慎小心。原因在於您可能試圖簽入尚未准備好的文件,例 如,您正在 Visual Studio 2005 中編輯的代碼。
一旦獲取到了包含掛起的更改的 PendingChange 數組,您可以將該數組實例和簽入說明傳遞給 CheckIn 方法以進行實際簽入操作。如果 該方法執行成功,Team Foundation Server 將返回一個整數,該數字代表待簽入的 Changeset(變更集 )的數量。變更集是一組提交的對 Team Foundation Server 版本控制存儲庫中存儲的一個或多個項的修 改。它還可以包含工作項數據、簽入說明和策略重寫信息。作為簽入過程的一部分,Team Foundation Server 客戶端 API 將為文檔啟用只讀位。圖 3 列出了兩種來自 tfsvcUtil 類的方法,這些方法將先執 行初始的 PendAdd 操作,然後執行 CheckIn 操作。當檢查代碼時,您應當查看獲取 Workspace 所必需 的額外代碼。另外,還有一個幫助器方法 GetDefaultWorkspace,如有必要,該方法可以獲取或創建默認 工作區。此代碼假定您已成功執行了 Connect 方法。
Figure 3 向版本控制添加文檔
Public Shared Function AddDocumentToVCR( _ ByVal docPath As String, _ ByVal serverPath As String) As Boolean If serverValidated Then ' We need the Workstation Dim userWorkstation As Workstation = Workstation.Current ' We need the Workspace Dim docWSInfo As WorkspaceInfo = _ userWorkstation.GetLocalWorkspaceInfo(docPath) If docWSInfo Is Nothing Then ' there's no existing mapping ' so let's get the default workspace m_userWorkspace = GetDefaultWorkspace(userWorkstation) Else m_userWorkspace = m_tfsVCS.GetWorkspace(docWSInfo) End If ' Now the question is does the workspace ' we're working with have a mapping Dim docWorkingFolder As New WorkingFolder(serverPath, _ IO.Path.GetDirectoryName(docPath)) ' It appears that CreateMapping is happy to let us try and ' create a mapping even if an entry already exists ' This eliminates the need to search m_userWorkspace.CreateMapping(docWorkingFolder) Return m_userWorkspace.PendAdd(docPath) = 1 Else Throw New tfsUtilException(MSG_SERVER_NOT_VALIDATED) End If End Function Public Shared Function CheckInDocument(ByVal docPath As String, _ ByVal comment As String) As Integer If serverValidated Then If m_userWorkspace Is Nothing Then Dim userWorkstation As Workstation = Workstation.Current Dim docWSInfo As WorkspaceInfo = _ userWorkstation.GetLocalWorkspaceInfo(docPath) m_userWorkspace = m_tfsVCS.GetWorkspace(docWSInfo) End If Dim pc As PendingChange() = m_userWorkspace.GetPendingChanges(docPath) If pc IsNot Nothing AndAlso pc.Length > 0 Then Return m_userWorkspace.CheckIn(pc, comment) Else Return -1 End If Else Throw New tfsUtilException(MSG_SERVER_NOT_VALIDATED) End If End Function Private Shared Function GetDefaultWorkspace( _ ByVal userWorkstation As Workstation) As Workspace ' Get the users default's workspace Dim defaultWorkspaceInfo As WorkspaceInfo = _ userWorkstation.GetLocalWorkspaceInfo(m_tfsVCS, _ Environment.MachineName, m_tfs.AuthenticatedUserName) Dim defaultWorkspace As Workspace = Nothing If defaultWorkspaceInfo Is Nothing Then ' Need to create it defaultWorkspace = m_tfsVCS.CreateWorkspace( _ Environment.MachineName, m_tfs.AuthenticatedUserName, _ "Auto-generated by tfsUtil") Else ' Get other defaultWorkspace = m_tfsVCS.GetWorkspace(defaultWorkspaceInfo) End If Return defaultWorkspace End Function
您可通過調用 Workspace 對象中的 PendEdit 來簽出文件以進行編輯。 Microsoft 也重載了此方法,提供了五種版本。和以前一樣,您要使用的是接受單一文件名的方法版本。 您可以從隨本專欄提供的下載內容中找到針對此操作的代碼,訪問位置為 msdn.microsoft.com/msdnmag/code07.aspx。它幾乎與 PendAdd 完全一樣。
調整和完成
結合 Team Foundation Server 使用的插件所必需的核心代碼基本都准備就緒了,但還有其他幾件事情需 要去做:
向 Team Foundation Server 提供諸如服務器名、協議和端口等信息。
指定團隊 項目和服務器路徑。
指定將文檔和注釋包含在簽入操作中。
構建 Word 2003 應用程序插 件以便將所有窗體和代碼掛接到命令欄,並處理與 Word 文檔相關的事件。
如果前述內容中提供 的那些代碼要從 Word 2003 的插件中運行,這些項需要一個 GUI。遺憾的是,盡管 Microsoft 在托管代 碼中為版本控制編寫了 Team Explorer 功能和 UI,但用於指定 Team Foundation Server 信息或向版本 控制添加文檔的實際對話框並沒有公開。在代碼下載內容中,您可以找到三種 Windows 窗體,這些窗體 可滿足以上所述的要求。
您需要的第一個窗體是 frmAddTFS。如果查看圖 4,您可以看到該窗體 與 Microsoft 對話框及其相似。可以使用該窗體指定服務器名、協議和端口。該窗體只包含少量代碼。 您可以對其進行擴展,以通過 Active Directory® 查找運行 Team Foundation Server 的計算機、 檢查 SSL 以及執行其他操作。
圖 4 Add Team Foundation Server 窗體
第二個窗體 frmAddtoSCC(如圖 5 所示),在設計和實施方面更加復雜。為了模擬 Microsoft 版本 ,我為 tfsvcUtil 類添加了其他一些幫助器方法:GetAllProjects 和 GetFolders。GetAllProjects 僅 用一行代碼即完成了相關操作。GetFolders 使用 VersionControlServer 對象的 GetItems 方法來獲取 所提供的路徑的單級文件夾。當您擴展一個節點時,窗體調用此方法來填充下級文件夾,這使您能夠在存 儲庫中指定文檔存儲位置。您可以看到還有一些額外的代碼,這些代碼可加載樹視圖控件,並可處理對添 加子文件夾的支持等。
圖 5 Add Document to Version Control 窗體 (單擊該圖像獲得較大視圖)frmCheckIn(圖 1 所示的 對話框)顯示了您要簽入的文檔並提供了注釋文本框。如果您已經使用了 Team Explorer 版本,您會發 現窗體的操作更加簡便。在今後的專欄內容中,我將進一步增強該窗體的功能以支持工作項和簽入說明。
最後,有一些與 Word 插件相關的注意事項。我不打算介紹插件的所有具體細節,但以下幾點需要指出: 首先,在 tfsvcUtil 的可下載版本中,您可以找到另外三種幫助器方法:Disconnect、 GetDocumentVCStatus 和 IsDocumentUnderVersionControl。Disconnect 是一個清理方法,使您可以變 更運行 Team Foundation Server 的計算機。插件可以使用 GetDocumentVCStatus 來獲取針對活動文檔 的版本控制狀態,以使命令欄按鈕保持同步。IsDocumentUnderVersionControl 檢查在版本控制中是否存 在該文檔,以及是否已將包含該文檔的文件夾映射到一個工作區。
其次,Team Foundation Server 客戶端 API 需要一個針對文檔的獨占鎖定以進行簽入。為了做到這 一點,插件必須首先關閉文檔,執行簽入,然後再重新打開文檔。
總結
Visual Studio Team System 幾乎為產品的各個方面都提供了擴展性選項,Version Control 服務就 是一個不錯的示例。我們可以使用豐富的 API 來創建一個簡單的插件,使您能夠通過該插件將 Word 文 檔放入 Team Foundation Server 版本控制存儲庫,在庫中,這些文檔可被輕松共享和管理。
將您想向 Brian 詢問的問題和提出的意見發送至 [email protected].