共用方式為


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

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

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

注意事項注意事項

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

需求

套用驗證條件約束

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

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

如需套用驗證的詳細資訊,請參閱 驗證 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],然後在中間的欄位中選擇 [類別庫]。

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

    1. 在 [方案總管]中的捷徑功能表中,選取 [新增], [新增專案]。

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

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

    • 在 [方案總管] 裡的 VSIX 專案的捷徑功能表中選取 [設定為啟始專案]。
  4. source.extension.vsixmanifest 的 [內容] 底下,加入類別庫專案當做 MEF 元件:

    1. 在 [中繼資料] 索引標籤中將名稱設為 VSIX。

    2. 在 [安裝目標] 選項上將設定 Visual Studio Ultimate 和 Premium 做為目標。

    3. 在 [資產] 索引標籤上,選取 [新增]然後在對話方塊中設定:

      類型 = MEF 元件

      來源 = 在目前方案中的專案

      專案 = 您的類別庫專案

若要定義驗證類別

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

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

    Microsoft.VisualStudio.Modeling.Sdk.11.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或是在Debug選單上選擇 開始除錯

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

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

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

    • 在 [方案總管] 捷徑功能表中的[啟動或唯一專案],選取 [屬性]。在專案屬性編輯器中,選取 [偵錯] 索引標籤。請確定 [啟動外部程式] 欄位中的字串為 Visual Studio 的完整路徑名稱,通常如下所示:

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

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

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

    1. 開啟類別圖表。

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

  4. 在捷徑功能表上的任何位置的圖表上,選取 [驗證]。

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

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

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

    • 驗證專案被列為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 進行程式設計

Ee329482.collapse_all(zh-tw,VS.110).gif關於驗證條件約束方法

方法會以下列形式定義每個驗證條件約束:

[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 替換為要套用條件約束的項目型別。條件約束方法會對指定型別中的所有項目叫用。

方法的名稱並不重要。

您可以在第二個參數中使用不同的型別定義您所需要的驗證方法,沒有數量上的限制。在叫用驗證時,將會為每個符合參數型別的項目呼叫每個驗證方法。

Ee329482.collapse_all(zh-tw,VS.110).gif報告驗證錯誤

若要建立錯誤報告,請使用 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);
        }
   }
}

Ee329482.collapse_all(zh-tw,VS.110).gif建立模型的驗證方法

如果您想確保每個驗證執行期間只會呼叫一次驗證方法,您可以驗證 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)
   { ...

Ee329482.collapse_all(zh-tw,VS.110).gif驗證圖案和圖表

驗證方法並不會針對圖表和圖案等顯示項目而叫用,因為驗證方法的主要用途是為了驗證模型。但您可以使用圖表內容來存取目前的圖表。

在您的驗證類別中,將 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);

Ee329482.collapse_all(zh-tw,VS.110).gif協調多個驗證

叫用驗證時 (以使用者從圖表功能表叫用為例),每個驗證方法都會套用至每個模型項目。這表示在驗證架構的單一叫用中,相同的方法可以多次套用至不同項目。

這樣會產生一個驗證問題,即需要處理項目之間的關聯性。例如,您可以撰寫驗證,如從使用案例開始,周遊 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\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;
}

請參閱

概念

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

使用 UML API 進行程式設計