定義鎖定原則來建立唯讀區段

Visual Studio Visualization and Modeling SDK 的不變性 API 可讓程式鎖定部分或所有特定領域語言 (DSL) 模型,以便讀取但不會變更。 例如,您可以使用這個唯讀選項,讓使用者可以要求同事標註和檢閱 DSL 模型,但可以不允許他們變更原始版本。

此外,身為 DSL 的作者,您可以定義鎖定原則。 鎖定原則會定義哪些鎖定是允許、不允許或強制。 例如,當您發佈 DSL 時,您可以鼓勵第三方開發人員使用新的命令加以擴充。 但是您也可以使用鎖定原則來防止他們改變模型指定部分的唯讀狀態。

注意

您可以使用反映來規避鎖定原則。 為第三方開發人員提供明確的界限,但是不提供強大的安全性。

詳細資訊和範例可於 Visual Studio Visualization and Modeling SDK 取得。

注意

文字範本轉換元件會作為 Visual Studio 延伸模組開發工作負載的一部分自動安裝。 您也可以從 Visual Studio 安裝程式的 [個別元件] 索引標籤加以安裝,其位於 [SDK、程式庫和架構] 底下。 從 [個別元件] 索引標籤安裝 [模型化 SDK] 元件。

設定和取得鎖定

您可以在存放區、分割區或個別元素上設定鎖定。 例如,這個陳述式可防止模型元素遭到刪除,也可防止其屬性變更:

using Microsoft.VisualStudio.Modeling.Immutability; ...
element.SetLocks(Locks.Delete | Locks.Property);

其他鎖定值可用來防止關聯性的變更、元素建立、分割區之間的移動,以及角色中的重新排序連結。

鎖定同時適用於使用者動作和程式碼。 如果程式碼嘗試進行變更,則會擲回 InvalidOperationException。 復原或重做作業會忽略鎖定。

您可以使用 IsLocked(Locks) 來探索元素是否具有指定集合中的鎖定,而且您可以使用 GetLocks() 來取得元素目前的鎖定集。

您可以在不使用交易的情況下設定鎖定。 鎖定資料庫不屬於存放區。 如果您設定鎖定以回應存放區中值的變更,例如在 OnValueChanged 中,您應該允許屬於復原作業的變更。

這些方法是定義於 Microsoft.VisualStudio.Modeling.Immutability 命名空間中的擴充方法。

分割區和存放區的鎖定

鎖定也可以套用至分割區和存放區。 分割區上設定的鎖定會套用至分割區中的所有元素。 因此舉例來說,下列陳述式可防止分割區中的所有元素遭到刪除,而不論其本身鎖定的狀態為何。 不過,例如 Locks.Property 的其他鎖定仍可在個別元素上設定:

partition.SetLocks(Locks.Delete);

在存放區上設定的鎖定會套用至其所有元素,不論該鎖定在分割區和元素上的設定為何。

使用鎖定

您可以使用鎖定來實作配置,例如下列範例:

  • 不允許變更所有元素和關聯性,但是代表註解的項目除外。 此方法可讓使用者標註模型,而不需變更模型。

  • 不允許預設分割區中的變更,但是允許圖表分割區中的變更。 使用者可以重新排列圖表,但是無法改變基礎模型。

  • 不允許變更存放區,但是在個別資料庫中註冊的使用者群組除外。 對於其他使用者,圖表和模型是唯讀的。

  • 如果圖表的布林值屬性設定為 true,則不允許變更模型。 提供功能表命令來變更該屬性。 此方法可協助確保使用者不會不小心進行變更。

  • 不允許新增和刪除特定類別的元素和關聯性,但是允許屬性變更。 此方法為使用者提供可在其中填入屬性的固定表單。

鎖定值

您可以在存放區、分割區或個別 ModelElement 上設定鎖定。 鎖定是 Flags 列舉:您可以使用 '|' 來合併其值。

  • ModelElement 的鎖定一律包含其分割區的鎖定。

  • 分割區的鎖定一律包含存放區的鎖定。

    您無法在分割區或存放區上設定鎖定,同時停用個別元素的鎖定。

表示如果 IsLocked(Value) 為 true
無限制。
屬性 無法變更元素的領域屬性。 這個值不適用於關聯性中領域類別角色所產生的屬性。
無法在分割區或存放區中建立新的元素和連結。 不適用於 ModelElement
移動 如果 element.IsLocked(Move) 為 true,或如果 targetPartition.IsLocked(Move) 為 true,則無法在分割區之間移動元素。
刪除 如果此鎖定是在元素本身上設定,或是在會傳播刪除的任何元素上設定,例如內嵌元素和圖形,則無法刪除元素。 您可以使用 element.CanDelete() 來探索是否可以刪除元素。
重新排序 無法變更角色扮演者的連結順序。
RolePlayer 無法變更此元素來源的連結集。 例如,新元素無法內嵌在此元素之下。 這個值不會影響這個元素為目標的連結。 如果這個元素是連結,則其來源和目標不會受到影響。
全部 其他值的位元 OR。

鎖定原則

身為 DSL 的作者,您可以定義鎖定原則。 鎖定原則會仲裁 SetLocks() 的作業,讓您可以防止設定特定鎖定,或強制必須設定特定鎖定。 一般而言,您會使用鎖定原則來阻止使用者或開發人員不小心違反 DSL 的預期用法,方式與宣告變數 private 相同。

您也可以使用鎖定原則來設定相依於元素類型之所有元素的鎖定。 此行為是因為第一次從檔案建立或還原序列化元素時,一律會呼叫 SetLocks(Locks.None)

不過,您無法使用原則在其生命週期期間改變元素的鎖定。 若要達到該效果,您應該使用對 SetLocks() 的呼叫。

若要定義鎖定原則:

  • 建立會實作 ILockingPolicy 的類別。

  • 將此類別新增至可透過 DSL 的 DocData 取得的服務。

定義鎖定原則

ILockingPolicy 有下列定義:

public interface ILockingPolicy
{
  Locks RefineLocks(ModelElement element, Locks proposedLocks);
  Locks RefineLocks(Partition partition, Locks proposedLocks);
  Locks RefineLocks(Store store, Locks proposedLocks);
}

對存放區、分割區或 ModelElement 上的 SetLocks() 進行呼叫時,會呼叫這些方法。 在每個方法中,您都會提供一組建議的鎖定。 您可以傳回建議的集合,也可以新增和減去鎖定。

例如:

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Immutability;
namespace Company.YourDsl.DslPackage // Change
{
  public class MyLockingPolicy : ILockingPolicy
  {
    /// <summary>
    /// Moderate SetLocks(this ModelElement target, Locks locks)
    /// </summary>
    /// <param name="element">target</param>
    /// <param name="proposedLocks">locks</param>
    /// <returns></returns>
    public Locks RefineLocks(ModelElement element, Locks proposedLocks)
    {
      // In my policy, users can never delete an element,
      // and other developers cannot easily change that:
      return proposedLocks | Locks.Delete);
    }
    public Locks RefineLocks(Store store, Locks proposedLocks)
    {
      // Only one user can change this model:
      return Environment.UserName == "aUser"
           ? proposedLocks : Locks.All;
    }

若要確保即使其他程式碼呼叫 SetLocks(Lock.Delete):,使用者一律可以刪除元素

return proposedLocks & (Locks.All ^ Locks.Delete);

若要不允許變更 MyClass 中每個元素的所有屬性:

return element is MyClass ? (proposedLocks | Locks.Property) : proposedLocks;

讓您的原則以服務的形式提供

在您的 DslPackage 專案中新增新檔案,其中包含類似下列範例的程式碼:

using Microsoft.VisualStudio.Modeling;
using Microsoft.VisualStudio.Modeling.Immutability;
namespace Company.YourDsl.DslPackage // Change
{
  // Override the DocData GetService() for this DSL.
  internal partial class YourDslDocData // Change
  {
    /// <summary>
    /// Custom locking policy cache.
    /// </summary>
    private ILockingPolicy myLockingPolicy = null;

    /// <summary>
    /// Called when a service is requested.
    /// </summary>
    /// <param name="serviceType">Service requested</param>
    /// <returns>Service implementation</returns>
    public override object GetService(System.Type serviceType)
    {
      if (serviceType == typeof(SLockingPolicy)
       || serviceType == typeof(ILockingPolicy))
      {
        if (myLockingPolicy == null)
        {
          myLockingPolicy = new MyLockingPolicy();
        }
        return myLockingPolicy;
      }
      // Request is for some other service.
      return base.GetService(serviceType);
    }
}