HOW TO:定義 UML 模型的驗證條件約束
在 Visual Studio Ultimate 中,可以定義驗證條件約束,用於測試模型是否符合指定的條件。例如,您可以定義條件約束,確保使用者無法建立繼承關聯性的迴圈。條件約束會在使用者嘗試開啟或儲存模型時叫用,但也可以由使用者手動叫用。如果條件約束失敗,則定義的錯誤訊息會加入至錯誤視窗。您可以將這些條件約束封裝到 Visual Studio Integration Extension (VSIX) 中,並將其散發給其他 Visual Studio Ultimate 使用者。
您也可以定義可根據外部資源 (如資料庫) 來驗證模型的條件約束。
注意事項 |
---|
如果您要根據圖層圖表驗證程式碼,請參閱在圖層圖表中加入自訂架構驗證。 |
需求
Visual Studio SDK是一個您可以從 Visual Studio 組件庫取得的東西。
Visual Studio Visualization and Modeling SDK是一個您可以從 Visual Studio Visualization and Modeling SDK on Code Gallery取得的東西。
套用驗證條件約束
驗證條件約束適用於下列三種情況:當您儲存模型時、當您開啟模型時,以及當您按一下 [架構] 功能表上的 [驗證 UML 模型] 時。雖然您通常會定義讓一個條件約束套用於多種情況,但在前述每一種情況下,將只會套用針對該情況所定義的條件約束。
Visual Studio 錯誤視窗會報告驗證錯誤,您可以按兩下錯誤,以選取發生錯誤的模型項目。
如需套用驗證的詳細資訊,請參閱 驗證 UML 模型。
定義驗證擴充功能
若要建立 UML 設計工具的驗證擴充功能,您必須建立會定義驗證條件約束的類別並內嵌在 Visual Studio Integration Extension (VSIX) 中。VSIX 可做為能安裝條件約束的容器。定義驗證擴充功能的替代方法有二種:
使用專案範本在它自己的 VSIX 中建立驗證擴充功能。 這是較快速的方法。如果您不要將您的驗證擴充功能與其他類型的擴充功能 (例如,功能表命令、自訂工具箱項目或筆勢處理常式) 結合在一起,請使用此方法。您可以一個類別中定義數個條件約束。
建立個別的驗證擴充功能和 VSIX 專案。 如果您要將數個擴充功能類型結合成相同的 VSIX,請使用此方法。例如,如果您的功能表命令預期模型會遵守特定的約束條件,則可以將它內嵌在相同的 VSIX 做為驗證方法。
若要在它自己的 VSIX 中建立驗證擴充功能
在 [新增專案] 對話方塊的 [模型專案] 底下,選取 [驗證擴充功能]。
開啟新專案中的 .cs 檔,並修改類別以實作您的驗證條件約束。
如需詳細資訊,請參閱實作驗證條件約束。
重要事項 確定 .cs 檔案包含下列 using 陳述式:
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
您可以藉由定義新方法來加入其他條件約束。若要將方法識別為驗證方法,必須使用和初始驗證方法一樣的方式使用屬性標記。
按 F5 鍵測試您的條件約束。如需詳細資訊,請參閱執行驗證。
複製您專案建置的檔案 bin\*\*.vsix,藉此將功能表命令安裝到其他電腦上。如需詳細資訊,請參閱安裝驗證條件約束。
加入其他 .cs 檔案時,通常會需要下列 using 陳述式:
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;
using Microsoft.VisualStudio.Modeling.Validation;
using Microsoft.VisualStudio.Uml.Classes;
下面為替代方法:
若要在類別庫專案中建立個別的驗證條件約束
建立類別庫專案 (不論是透過將其加入至現有的 VSIX 方案,或建立新方案)。
在 [檔案] 功能表上,選擇 [新增, 專案]。
在 [已安裝的範本] 底下,展開 [Visual C#] 或 [Visual Basic],然後在中間的欄位中選擇 [類別庫]。
建立 VSIX 專案 (若您的方案中已有則不需建立):
在 [方案總管]中的捷徑功能表中,選取 [新增], [新增專案]。
在 [已安裝的範本] 底下,展開 [Visual C#] 或 [Visual Basic],然後選擇 [擴充性]。在中間的欄位中,按一下 [VSIX 專案]。
將 VSIX 專案設定為方案的啟始專案。
- 在 [方案總管] 裡的 VSIX 專案的捷徑功能表中選取 [設定為啟始專案]。
在 source.extension.vsixmanifest 的 [內容] 底下,加入類別庫專案當做 MEF 元件:
在 [中繼資料] 索引標籤中將名稱設為 VSIX。
在 [安裝目標] 選項上將設定 Visual Studio Ultimate 和 Premium 做為目標。
在 [資產] 索引標籤上,選取 [新增]然後在對話方塊中設定:
類型 = MEF 元件
來源 = 在目前方案中的專案
專案 = 您的類別庫專案
若要定義驗證類別
如果您已經從驗證專案範本建立驗證類別和它自己的 VSIX,則不需要進行此程序。
在驗證類別專案中,將參考加入至下列 .NET 組件:
Microsoft.VisualStudio.Modeling.Sdk.11.0
Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml
Microsoft.VisualStudio.Uml.Interfaces
System.ComponentModel.Composition
將檔案加入至其程式碼類似下列範例的類別庫專案。
每個驗證條件約束都包含在以特定屬性標記的方法內。此類方法會接受模型項目型別的參數。在叫用驗證時,驗證架構會將每個驗證方法套用至每個符合參數型別的模型項目。
您可以將這些方法放在任何類別和命名空間中。根據您的偏好變更它們。
using System.Collections.Generic; using System.ComponentModel.Composition; using System.Linq; using Microsoft.VisualStudio.Modeling.Validation; using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml; using Microsoft.VisualStudio.Uml.Classes; // You might also need the other Microsoft.VisualStudio.Uml namespaces. namespace Validation { public class MyValidationExtensions { // SAMPLE VALIDATION METHOD. // All validation methods have the following attributes. [Export(typeof(System.Action<ValidationContext, object>))] [ValidationMethod( ValidationCategories.Save | ValidationCategories.Open | ValidationCategories.Menu)] public void ValidateClassNames (ValidationContext context, // This type determines what elements // will be validated by this method: IClass elementToValidate) { // A validation method should not change the model. List<string> attributeNames = new List<string>(); foreach (IProperty attribute in elementToValidate.OwnedAttributes) { string name = attribute.Name; if (!string.IsNullOrEmpty(name) && attributeNames.Contains(name)) { context.LogError( string.Format("Duplicate attribute name '{0}' in class {1}", name, elementToValidate.Name), "001", elementToValidate); } attributeNames.Add(name); } } // Add more validation methods for different element types. } }
執行驗證條件約束
為進行測試,請在偵錯模式中執行您的驗證方法。
若要測試驗證條件約束
按下 F5或是在Debug選單上選擇 開始除錯。
Visual Studio 的實驗執行個體隨即啟動。
疑難排解:如果新 Visual Studio 未啟動:
如果您具有多個專案,請確定已設定 VSIX 專案做為方案的啟始專案。
在 [方案總管] 捷徑功能表中的[啟動或唯一專案],選取 [屬性]。在專案屬性編輯器中,選取 [偵錯] 索引標籤。請確定 [啟動外部程式] 欄位中的字串為 Visual Studio 的完整路徑名稱,通常如下所示:
C:\Program Files\Microsoft Visual Studio 11.0\Common7\IDE\devenv.exe
在實驗性質的 Visual Studio 中,開啟或建立模型專案,然後開啟或建立模型圖表。
若要為上一節指定的範例條件約束設定測試:
開啟類別圖表。
建立一個類別,並加入兩個同名屬性。
在捷徑功能表上的任何位置的圖表上,選取 [驗證]。
模型中的任何錯誤都會報告在錯誤視窗中。
按兩下錯誤報告。報表中所提到的項目如果顯示在畫面上,將會反白顯示。
疑難排解:如果 [驗證] 命令未出現在功能表上,請確定:
驗證專案被列為MEF 元件,其為資產選項中的source.extensions.manifest的VSIX專案。
已將正確的 Export 和 ValidationMethod 屬性附加至驗證方法。
ValidationCategories.Menu 會包含在 ValidationMethod 屬性的引數中,並且使用邏輯 OR (|) 與其他值一起寫出。
所有 Import 和 Export 屬性的參數皆有效。
評估條件約束
驗證方法應判斷您要套用的驗證條件約束是 true 或 false。若為 true,則不應執行任何動作。若為 false,則應使用 ValidationContext 參數提供的方法報告錯誤。
注意事項 |
---|
驗證方法不應該變更模型。無法保證何時或以什麽順序執行條件約束。如果在驗證執行期間,您必須在驗證方法的後續執行之間傳遞資訊,則可以使用協調多個驗證下說明的內容快取。 |
例如,如果您想確定每個型別 (類別、介面或列舉程式) 的名稱長度是否至少都有三個字元,您可以使用下列方法:
public void ValidateTypeName(ValidationContext context, IType type)
{
if (!string.IsNullOrEmpty(type.Name) && type.Name.Length < 3)
{
context.LogError(
string.Format("Type name {0} is too short", type.Name),
"001", type);
}
}
如需可用以巡覽及讀取模型之方法和型別的詳細資訊,請參閱使用 UML API 進行程式設計。
關於驗證條件約束方法
方法會以下列形式定義每個驗證條件約束:
[Export(typeof(System.Action<ValidationContext, object>))]
[ValidationMethod(ValidationCategories.Save
| ValidationCategories.Menu
| ValidationCategories.Open)]
public void ValidateSomething
(ValidationContext context, IClassifier elementToValidate)
{...}
每個驗證方法的屬性和參數如下所示:
[Export(typeof(System.Action <ValidationContext, object>))] |
使用 Managed Extensibility Framework (MEF) 將方法定義為驗證條件約束。 |
[ValidationMethod (ValidationCategories.Menu)] |
指定何時執行驗證。如果希望合併一個以上的選項,請使用位元 OR (|)。 Menu = 由 [驗證] 功能表叫用。 Save = 在儲存模型時叫用。 Open = 在開啟模型時叫用。Load = 在儲存模型時叫用,但會警告使用可能無法重新開啟模型。也會在載入期間於剖析模型之前呼叫。 |
public void ValidateSomething (ValidationContext context, IElement element) |
將第二個參數 IElement 替換為要套用條件約束的項目型別。條件約束方法會對指定型別中的所有項目叫用。 方法的名稱並不重要。 |
您可以在第二個參數中使用不同的型別定義您所需要的驗證方法,沒有數量上的限制。在叫用驗證時,將會為每個符合參數型別的項目呼叫每個驗證方法。
報告驗證錯誤
若要建立錯誤報告,請使用 ValidationContext 提供的方法:
context.LogError("error string", errorCode, elementsWithError);
"error string" 會出現在 Visual Studio 錯誤清單中
errorCode 是字串,這應是錯誤的唯一識別項
elementsWithError 會識別模型中的項目。使用者按兩下錯誤報告時,會選取代表此項目的圖案。
LogError(),LogWarning() 和 LogMessage() 會將訊息放置在錯誤清單的不同區段中。
驗證方法的套用方式
驗證會套用至模型中的每個項目,包括較大項目的關聯性和組件,例如類別的屬性和作業的參數等。
各個驗證方法會套用至每個與其第二參數中的型別相符的項目。舉例而言,這表示如果您將某個驗證方法的第二個參數定義為 IUseCase,並且將另一個驗證方法的超級型別定義為 IElement,則這兩個方法都會套用至模型中的每個使用案例。
模型項目型別中提供了型別階層的摘要。
您也可以依照下列關聯性來存取項目。例如,如果您對 IClass 定義了驗證方法,則您可以對其擁有的屬性執行迴圈:
public void ValidateTypeName(ValidationContext context, IClass c)
{
foreach (IProperty property in c.OwnedAttributes)
{
if (property.Name.Length < 3)
{
context.LogError(
string.Format(
"Property name {0} is too short",
property.Name),
"001", property);
}
}
}
建立模型的驗證方法
如果您想確保每個驗證執行期間只會呼叫一次驗證方法,您可以驗證 IModel:
using Microsoft.VisualStudio.Uml.AuxiliaryConstructs; ...
[Export(typeof(System.Action<ValidationContext, object>))]
[ValidationMethod(ValidationCategories.Menu)]
public void ValidateModel(ValidationContext context, IModel model)
{ foreach (IElement element in model.OwnedElements)
{ ...
驗證圖案和圖表
驗證方法並不會針對圖表和圖案等顯示項目而叫用,因為驗證方法的主要用途是為了驗證模型。但您可以使用圖表內容來存取目前的圖表。
在您的驗證類別中,將 DiagramContext 宣告為匯入的屬性:
using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Presentation;
...
[Import]
public IDiagramContext DiagramContext { get; set; }
在驗證方法中,您可以使用 DiagramContext 存取目前的焦點圖表 (如果有的話):
[Export(typeof(System.Action<ValidationContext, object>))]
[ValidationMethod(ValidationCategories.Menu)]
public void ValidateModel(ValidationContext context, IModel model)
{
IDiagram focusDiagram = DiagramContext.CurrentDiagram;
if (focusDiagram != null)
{
foreach (IShape<IUseCase> useCaseShape in
focusDiagram.GetChildShapes<IUseCase>())
{ ...
若要記錄錯誤,您必須取得圖案所代表的模型項目,因為您無法將圖案傳遞至 LogError:
IUseCase useCase = useCaseShape.Element;
context.LogError(... , usecase);
協調多個驗證
叫用驗證時 (以使用者從圖表功能表叫用為例),每個驗證方法都會套用至每個模型項目。這表示在驗證架構的單一叫用中,相同的方法可以多次套用至不同項目。
這樣會產生一個驗證問題,即需要處理項目之間的關聯性。例如,您可以撰寫驗證,如從使用案例開始,周遊 include 關聯性來驗證沒有迴圈。然而,在方法套用至模型中的每個使用案例,且該模型具有多個 include 連結時,可能會重複處理模型的相同區域。
若要避免這個情形,可使用內容快取,在驗證執行期間保留資訊。您可以使用它在驗證方法的不同執行之間傳遞資訊。例如,您可能會儲存在此驗證執行期間已經處理之項目的清單。快取會在每個驗證執行期間開始時建立,且不能用於在不同驗證執行之間傳遞資訊。
context.SetCacheValue<T> (name, value) |
儲存值。 |
context.TryGetCacheValue<T> (name, out value) |
取得值。如果成功,則傳回 true。 |
context.GetValue<T>(name) |
取得值。 |
Context.GetValue<T>() |
取得所指定型別的值。 |
安裝和解除安裝擴充功能
您可以將 Visual Studio 擴充功能安裝在自己的電腦上,也可以安裝在其他電腦上。
若要安裝擴充功能
在您的電腦上,尋找由 VSIX 專案建置的 .vsix 檔案。
在 [方案總管]裡的VSIX 專案的捷徑功能表中,選取 [開啟 Windows 檔案總管中的資料夾]。
尋找 bin\*\YourProject.vsix 檔案
將 .vsix 檔案複製到要安裝擴充功能的目標電腦上。該電腦可以是您自己的電腦或其他電腦。
- 目標電腦必須具有在 source.extension.vsixmanifest 中指定的其中一個 Visual Studio 範本。
在目標電腦上開啟 .vsix 檔案。
[Visual Studio 擴充功能安裝程式] 隨即開啟,並安裝擴充功能。
啟動或重新啟動 Visual Studio。
若要解除安裝擴充功能
選擇 [工具] 功能表上的 [擴充管理員…]。
展開 [已安裝擴充功能]。
選取相應的擴充功能,然後選擇 [解除安裝]。
很少會發生因擴充功能故障而無法載入的情況,在這種情況下,錯誤視窗中會產生報告,但不會出現在 [擴充管理員] 中。在這種情況下,您可以到下列位置刪除檔案藉此移除擴充功能,其中 %LocalAppData% 通常是 DriveName:\Users\UserName\AppData\Local:
%LocalAppData%\Microsoft\VisualStudio\11.0\Extensions
範例
這個範例在項目之間的相依性關係中尋找迴圈。
它會驗證儲存和驗證功能表命令。
/// <summary>
/// Verify that there are no loops in the dependency relationsips.
/// In our project, no element should be a dependent of itself.
/// </summary>
/// <param name="context">Validation context for logs.</param>
/// <param name="element">Element to start validation from.</param>
[Export(typeof(System.Action<ValidationContext, object>))]
[ValidationMethod(ValidationCategories.Menu
| ValidationCategories.Save | ValidationCategories.Open)]
public void NoDependencyLoops(ValidationContext context, INamedElement element)
{
// The validation framework will call this method
// for every element in the model. But when we follow
// the dependencies from one element, we will validate others.
// So we keep a list of the elements that we don't need to validate again.
// The list is kept in the context cache so that it is passed
// from one execution of this method to another.
List<INamedElement> alreadySeen = null;
if (!context.TryGetCacheValue("No dependency loops", out alreadySeen))
{
alreadySeen = new List<INamedElement>();
context.SetCacheValue("No dependency loops", alreadySeen);
}
NoDependencyLoops(context, element,
new INamedElement[0], alreadySeen);
}
/// <summary>
/// Log an error if there is any loop in the dependency relationship.
/// </summary>
/// <param name="context">Validation context for logs.</param>
/// <param name="element">The element to be validated.</param>
/// <param name="dependants">Elements we've followed in this recursion.</param>
/// <param name="alreadySeen">Elements that have already been validated.</param>
/// <returns>true if no error was detected</returns>
private bool NoDependencyLoops(ValidationContext context,
INamedElement element, INamedElement[] dependants,
List<INamedElement> alreadySeen)
{
if (dependants.Contains(element))
{
context.LogError(string.Format("{0} should not depend on itself", element.Name),
"Fabrikam.UML.NoGenLoops", // unique code for this error
dependants.SkipWhile(e => e != element).ToArray());
// highlight elements that are in the loop
return false;
}
INamedElement[] dependantsPlusElement =
new INamedElement[dependants.Length + 1];
dependants.CopyTo(dependantsPlusElement, 0);
dependantsPlusElement[dependantsPlusElement.Length - 1] = element;
if (alreadySeen.Contains(element))
{
// We have already validated this when we started
// from another element during this validation run.
return true;
}
alreadySeen.Add(element);
foreach (INamedElement supplier in element.GetDependencySuppliers())
{
if (!NoDependencyLoops(context, supplier,
dependantsPlusElement, alreadySeen))
return false;
}
return true;
}