Definieren einer Sperrrichtlinie zum Erstellen von schreibgeschützten Segmenten

Mit der Unveränderlichkeits-API des Visual Studio Visualization and Modeling SDK kann ein Programm einen Teil oder das gesamte Domain-Specific Language(DSL)-Modell sperren, sodass es gelesen, aber nicht geändert werden kann. Mit diesem Schreibschutz kann beispielsweise ein*e Benutzer*in Kolleg*innen bitten, ein DSL-Modell zu kommentieren und zu überprüfen, ohne dass diese das Original ändern können.

Darüber hinaus können Sie als Autor einer DSL eine Sperrrichtlinie definieren. Eine Sperrrichtlinie definiert, welche Sperren zulässig, nicht zulässig oder obligatorisch sind. Wenn Sie eine DSL veröffentlichen, können Sie beispielsweise Drittanbieterentwickler ermutigen, sie mit neuen Befehlen zu erweitern. Sie können jedoch auch eine Sperrrichtlinie verwenden, um zu verhindern, dass sie den Schreibschutzstatus der angegebenen Teile des Modells ändern.

Hinweis

Eine Sperrrichtlinie kann mithilfe von Reflektion umgangen werden. Es stellt eine klare Grenze für Drittanbieterentwickler dar, bietet aber keine starke Sicherheit.

Weitere Informationen und Beispiele finden Sie unter Visual Studio SDK für Visualisierung und Modellierung.

Hinweis

Die Komponente Textvorlagentransformation wird automatisch als Teil der Workload Visual Studio-Erweiterungsentwicklung installiert. Sie können die Installation auch über die Registerkarte Einzelne Komponenten des Visual Studio-Installers unter der Kategorie SDKs, Bibliotheken und Frameworks durchführen. Installieren Sie die Komponente Modellierungs-SDK auf der Registerkarte Einzelne Komponenten.

Festlegen und Abrufen von Sperren

Sie können Sperren für den Store, für eine Partition oder für ein einzelnes Element festlegen. Diese Anweisung verhindert z. B. das Löschen eines Modellelements und verhindert, dass seine Eigenschaften geändert werden:

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

Andere Sperrwerte können verwendet werden, um Änderungen an Beziehungen, der Elementerstellung, dem Verschieben zwischen Partitionen und dem Neuanordnen von Links in einer Rolle zu verhindern.

Die Sperren gelten sowohl für Benutzeraktionen als auch für Programmcode. Wenn der Programmcode versucht, eine Änderung vorzunehmen, wird eine InvalidOperationException ausgelöst. Sperren werden beim Rückgängigmachen (Undo) oder Wiederholen (Redo) ignoriert.

Sie können ermitteln, ob ein bestimmter Satz eines Elements gesperrt ist, indem Sie IsLocked(Locks) verwenden, und Sie können den aktuellen Satz von Sperren für ein Element mithilfe von GetLocks() abrufen.

Sie können eine Sperre festlegen, ohne eine Transaktion zu verwenden. Die Sperrdatenbank ist nicht Teil des Stores. Wenn Sie eine Sperre als Reaktion auf eine Änderung eines Werts im Store festlegen, z. B. in OnValueChanged, sollten Sie Änderungen zulassen, die Teil eines Rückgängig-Vorgangs sind.

Bei diesen Methoden handelt es sich um Erweiterungsmethoden, die im Microsoft.VisualStudio.Modeling.Immutability-Namespace definiert sind.

Sperren für Partitionen und Stores

Sperren können auch auf Partitionen und den Store angewendet werden. Eine Sperre, die für eine Partition festgelegt ist, gilt für alle Elemente in der Partition. Daher verhindert beispielsweise die folgende Anweisung, dass alle Elemente in einer Partition gelöscht werden, unabhängig vom Status ihrer eigenen Sperren. Dennoch könnten noch andere Sperren, wie z. B. Locks.Property, für einzelne Elemente festgelegt werden:

partition.SetLocks(Locks.Delete);

Eine Sperre, die für den Store festgelegt ist, gilt für alle zugehörigen Elemente, unabhängig von den Einstellungen dieser Sperre für die Partitionen und die Elemente.

Verwenden von Sperren

Sie können Sperren verwenden, um Schemas wie die folgenden Beispiele zu implementieren:

  • Verbieten von Änderungen an allen Elementen und Beziehungen mit Ausnahme solcher, die Kommentare darstellen. Mit diesem Ansatz können Benutzer ein Modell kommentieren, ohne es zu ändern.

  • Verbieten von Änderungen an der Standardpartition, aber Zulassen von Änderungen in der Diagrammpartition. Benutzer*innen können kann das Diagramm neu anordnen, aber das zugrunde liegende Modell nicht ändern.

  • Keine Änderungen am Store zulassen, mit Ausnahme einer Gruppe von Benutzer*innen, die in einer separaten Datenbank registriert sind. Für andere Benutzer sind das Diagramm und das Modell schreibgeschützt.

  • Verbieten von Änderungen am Modell, wenn eine boolesche Eigenschaft des Diagramms auf „true“ gesetzt ist. Sie würden einen Menübefehl angeben, um diese Eigenschaft zu ändern. Dieser Ansatz trägt dazu bei, dass Benutzer*innen keine versehentlichen Änderungen vornehmen.

  • Verbieten des Hinzufügens und Löschens von Elementen und Beziehungen bestimmter Klassen, aber Zulassen von Eigenschaftsänderungen. Dieser Ansatz bietet Benutzern ein festes Formular, in dem sie die Eigenschaften ausfüllen können.

Sperren von Werten

Sperren können für einen Store, eine Partition oder ein einzelnes ModelElement festgelegt werden. Sperren ist eine Flags-Enumeration: Sie können ihre Werte mit „|“ kombinieren.

  • Sperren eines ModelElement umfassen immer die Sperren seiner Partition.

  • Sperren einer Partition umfassen immer die Sperren des Store.

    Sie können keine Sperre für eine Partition oder einen Store festlegen und diese Sperre für ein einzelnes Element deaktivieren.

Wert Bedeutung, wenn IsLocked(Value) true ist
Keine Keine Einschränkung.
Eigenschaft Domäneneigenschaften von Elementen können nicht geändert werden. Dieser Wert gilt nicht für Eigenschaften, die von der Rolle einer Domänenklasse in einer Beziehung generiert werden.
Hinzufügen Neue Elemente und Links können nicht in einer Partition oder einem Store erstellt werden. Nicht zutreffend für ModelElement.
Move Das Element kann nicht zwischen Partitionen verschoben werden, wenn element.IsLocked(Move) oder targetPartition.IsLocked(Move) true ist.
Löschen Ein Element kann nicht gelöscht werden, wenn diese Sperre für das Element selbst oder für eines der Elemente festgelegt ist, die in Folge gelöscht würden, z. B. eingebettete Elemente und Formen. Sie können element.CanDelete() verwenden, um zu ermitteln, ob ein Element gelöscht werden kann.
Neu anordnen Die Reihenfolge von Links auf einem Rolleninhaber kann nicht geändert werden.
RolePlayer Der Satz von Links, die von diesem Element stammen, kann nicht geändert werden. Beispielsweise können neue Elemente nicht unter diesem Element eingebettet werden. Dieser Wert wirkt sich nicht auf Links aus, für die dieses Element das Ziel ist. Wenn es sich bei diesem Element um einen Link handelt, sind seine Quelle und das Ziel nicht betroffen.
Alle Bitweises OR der anderen Werte.

Sperrrichtlinien

Als Autor einer DSL können Sie eine Sperrrichtlinie definieren. Eine Sperrrichtlinie moderiert den Vorgang von SetLocks(), sodass Sie verhindern können, dass bestimmte Sperren festgelegt werden oder vorgeben können, dass bestimmte Sperren festgelegt werden müssen. In der Regel würden Sie eine Sperrrichtlinie verwenden, um Benutzer oder Entwickler davon abzuhalten, versehentlich gegen die beabsichtigte Verwendung einer DSL zu verstoßen – auf ähnliche Weise, wie wenn Sie eine Variable als private deklarieren.

Sie können auch eine Sperrrichtlinie verwenden, um Sperren für alle Elemente festzulegen, die vom Typ eines Elements abhängig sind. Dies geschieht, weil SetLocks(Locks.None) immer aufgerufen wird, wenn ein Element zuerst aus einer Datei erstellt oder deserialisiert wird.

Sie können jedoch keine Richtlinie verwenden, um die Sperren für ein Element während seiner Lebensdauer zu variieren. Um diesen Effekt zu erzielen, sollten Sie Aufrufe von SetLocks() verwenden.

So definieren Sie eine Sperrrichtlinie:

  • Erstellen Sie eine Klasse, die das ILockingPolicy implementiert.

  • Fügen Sie diese Klasse den Diensten hinzu, die über die DocData Ihrer DSL verfügbar sind.

Definieren einer Sperrrichtlinie

ILockingPolicy hat die folgende Definition:

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

Diese Methoden werden aufgerufen, wenn ein Aufruf von SetLocks() für einen Store, eine Partition oder ein ModelElement erfolgt. In jeder Methode erhalten Sie einen vorgeschlagenen Satz von Sperren. Sie können den vorgeschlagenen Satz zurückgeben oder Sperren hinzufügen und subtrahieren.

Zum Beispiel:

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

Sicherstellen, dass Benutzer*innen Elemente jederzeit löschen können, auch wenn anderer Code SetLocks(Lock.Delete): aufruft

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

So können Sie Änderungen an allen Eigenschaften jedes Elements von MyClass verbieten:

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

Bereitstellen Ihrer Richtlinie als Dienst

Fügen Sie in Ihrem DslPackage-Projekt eine neue Datei hinzu, die Code enthält, der dem folgenden Beispiel ähnelt:

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);
    }
}