共用方式為


HOW TO:定義 UML 模型的驗證條件約束

在 Visual Studio Ultimate 中,可以定義驗證條件約束,用於測試模型是否符合指定的條件。 例如,您可以定義條件約束,確保使用者無法建立繼承關聯性的迴圈。 條件約束會在使用者嘗試開啟或儲存模型時叫用,但也可以由使用者手動叫用。 如果條件約束失敗,則定義的錯誤訊息會加入至錯誤視窗。 您可以將這些條件約束封裝到 Visual Studio Integration Extension (VSIX) 中,並將其散發給其他 Visual Studio Ultimate 使用者。

您也可以定義可根據外部資源 (如資料庫) 來驗證模型的條件約束。

注意事項注意事項

如果您要根據圖層圖表驗證程式碼,請參閱在圖層圖表中加入自訂架構驗證

需求

套用驗證條件約束

驗證條件約束適用於下列三種情況:當您儲存模型時、當您開啟模型時,以及當您按一下 [架構] 功能表上的 [驗證 UML 模型] 時。 雖然您通常會定義讓一個條件約束套用於多種情況,但在前述每一種情況下,將只會套用針對該情況所定義的條件約束。

Visual Studio 錯誤視窗會報告驗證錯誤,您可以按兩下錯誤,以選取發生錯誤的模型項目。

如需套用驗證的詳細資訊,請參閱 HOW TO:驗證 UML 模型

定義驗證擴充功能

若要建立 UML 設計工具的驗證擴充功能,您必須建立會定義驗證條件約束的類別並內嵌在 Visual Studio Integration Extension (VSIX) 中。 VSIX 可做為能安裝條件約束的容器。 定義驗證擴充功能的替代方法有二種:

  • **使用專案範本在它自己的 VSIX 中建立驗證擴充功能。**這是較快速的方法。 如果您不要將您的驗證擴充功能與其他類型的擴充功能 (例如,功能表命令、自訂工具箱項目或筆勢處理常式) 結合在一起,請使用此方法。 您可以一個類別中定義數個條件約束。

  • **建立個別的驗證擴充功能和 VSIX 專案。**如果您要將數個擴充功能類型結合成相同的 VSIX,請使用此方法。 例如,如果您的功能表命令預期模型會遵守特定的約束條件,則可以將它內嵌在相同的 VSIX 做為驗證方法。

若要在它自己的 VSIX 中建立驗證擴充功能

  1. 在 [新增專案] 對話方塊的 [模型專案] 底下,選取 [驗證擴充功能]。

  2. 開啟新專案中的 .cs 檔,並修改類別以實作您的驗證條件約束。

    如需詳細資訊,請參閱實作驗證條件約束。

    重要事項重要事項

    確定 .cs 檔案包含下列 using 陳述式:

    using Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml;

  3. 您可以藉由定義新方法來加入其他條件約束。 若要將方法識別為驗證方法,必須使用和初始驗證方法一樣的方式使用屬性標記。

  4. 按 F5 鍵測試您的條件約束。 如需詳細資訊,請參閱執行驗證。

  5. 複製您專案建置的檔案 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;

若要在類別庫專案中建立個別的驗證條件約束

  1. 建立類別庫專案 (不論是透過將其加入至現有的 VSIX 方案,或建立新方案)。

    1. 在 [檔案] 功能表上,指向 [新增],然後按一下 [專案]。

    2. 在 [已安裝的範本] 底下,按一下 [Visual C#] 或 [Visual Basic],然後在中間的欄位中,按一下 [類別庫]。

    3. 設定 [方案],指出您想要建立新的方案,還是想要將元件加入至您已經開啟的 VSIX 方案。

    4. 設定專案名稱和位置,然後按一下 [確定]。

  2. 建立 VSIX 專案 (若您的方案中已有則不需建立)。

    1. 在 [方案總管] 中,以滑鼠右鍵按一下方案、指向 [加入],再按一下 [新增專案]。

    2. 在 [已安裝的範本] 底下,展開 [Visual C#] 或 [Visual Basic],然後按一下 [擴充性]。 在中間的欄位中,按一下 [VSIX 專案]。

  3. 將 VSIX 專案設定為方案的啟始專案。

    • 在 [方案總管] 中,以滑鼠右鍵按一下 VSIX 專案,然後按一下 [設定為啟始專案]。
  4. source.extension.vsixmanifest 的 [內容] 底下,加入類別庫專案當做 MEF 元件。

    1. 開啟 source.extension.vsixmanifest

    2. 按一下 [加入內容]。

    3. 在 [選取內容類型] 中,選取 [MEF 元件]。

    4. 在 [選取來源] 中,按一下 [專案],然後選取類別庫專案的名稱。

  5. 按一下 [選取版本],然後選取您想要用來執行擴充功能的 Visual Studio 版本。

  6. 設定 VSIX 的名稱和描述性欄位。 儲存檔案。

若要定義驗證類別

  1. 如果您已經從驗證專案範本建立驗證類別和它自己的 VSIX,則不需要進行此程序。

  2. 在驗證類別專案中,將參考加入至下列 .NET 組件:

    Microsoft.VisualStudio.Modeling.Sdk.10.0

    Microsoft.VisualStudio.ArchitectureTools.Extensibility.Uml

    Microsoft.VisualStudio.Uml.Interfaces

    System.ComponentModel.Composition

  3. 將檔案加入至其程式碼類似下列範例的類別庫專案。

    • 每個驗證條件約束都包含在以特定屬性標記的方法內。 此類方法會接受模型項目型別的參數。 在叫用驗證時,驗證架構會將每個驗證方法套用至每個符合參數型別的模型項目。

    • 您可以將這些方法放在任何類別和命名空間中。 根據您的偏好變更它們。

    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.
      }
    }
    

執行驗證條件約束

為進行測試,請在偵錯模式中執行您的驗證方法。

若要測試驗證條件約束

  1. 按下 F5,或在 [偵錯] 功能表上,按一下 [開始偵錯]。

    Visual Studio 的實驗執行個體隨即啟動。

    疑難排解:如果新 Visual Studio 未啟動:

    • 如果您具有多個專案,請確定已設定 VSIX 專案做為方案的啟始專案。

    • 在 [方案總管] 中,以滑鼠右鍵按一下 [啟動] 或僅按一下專案,然後按一下 [屬性]。 在專案屬性編輯器中,按一下 [偵錯] 索引標籤。 請確定 [啟動外部程式] 欄位中的字串為 Visual Studio 的完整路徑名稱,通常如下所示:

      C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe

  2. 在實驗性質的 Visual Studio 中,開啟或建立模型專案,然後開啟或建立模型圖表。

  3. 若要為上一節指定的範例條件約束設定測試:

    1. 開啟類別圖表。

    2. 建立一個類別,並加入兩個同名屬性。

  4. 以滑鼠右鍵按一下圖表上的任何位置,然後按一下 [驗證]。

  5. 模型中的任何錯誤都會報告在錯誤視窗中。

  6. 按兩下錯誤報告。 報表中所提到的項目如果顯示在畫面上,將會反白顯示。

    疑難排解:如果 [驗證] 命令未出現在功能表上,請確定:

    • 驗證專案在 VSIX 專案之 source.extensions.manifest 的 [內容] 清單中,已列為 MEF 元件。

    • 已將正確的 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 擴充功能安裝在自己的電腦上,也可以安裝在其他電腦上。

若要安裝擴充功能

  1. 在您的電腦上,尋找由 VSIX 專案建置的 .vsix 檔案。

    1. 在 [方案總管] 中,以滑鼠右鍵按一下 VSIX 專案,然後按一下 [在 Windows 檔案總管中開啟資料夾]。

    2. 尋找 bin\*\YourProject.vsix 檔案

  2. .vsix 檔案複製到要安裝擴充功能的目標電腦上。 該電腦可以是您自己的電腦或其他電腦。

    • 目標電腦必須具有在 source.extension.vsixmanifest 中指定的其中一個 Visual Studio 範本。
  3. 在目標電腦上,按兩下 .vsix 檔案。

    [Visual Studio 擴充功能安裝程式] 隨即開啟,並安裝擴充功能。

  4. 啟動或重新啟動 Visual Studio。

若要解除安裝擴充功能

  1. 按一下 [工具] 功能表上的 [擴充管理員]。

  2. 展開 [已安裝擴充功能]。

  3. 選取相應的擴充功能,然後按一下 [解除安裝]。

很少會發生因擴充功能故障而無法載入的情況,在這種情況下,錯誤視窗中會產生報告,但不會出現在 [擴充管理員] 中。 在這種情況下,您可以到下列位置刪除檔案藉此移除擴充功能,其中 %LocalAppData% 通常是 DriveName:\Users\UserName\AppData\Local:

%LocalAppData%\Microsoft\VisualStudio\10.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;
}

請參閱

其他資源

HOW TO:定義與安裝模型擴充功能

使用 UML API 進行程式設計