Definición de una directiva de bloqueo para crear segmentos de solo lectura

La API de inmutabilidad del SDK de modelado y visualización de Visual Studio permite que un programa bloquee parte o la totalidad de un modelo de lenguaje específico de dominio (DSL) para que se pueda leer pero no modificar. Esta opción de solo lectura se puede usar, por ejemplo, para que un usuario pueda pedir a los compañeros que anoten y revisen un modelo DSL, pero puede impedirles cambiar el original.

Además, como autor de un DSL, puede definir una directiva de bloqueo. Una directiva de bloqueo define los bloqueos permitidos, no permitidos u obligatorios. Por ejemplo, al publicar un DSL, puede animar a los desarrolladores de terceros a ampliarlo con nuevos comandos. Sin embargo, también puede usar una directiva de bloqueo para evitar que modifiquen el estado de solo lectura de las partes especificadas del modelo.

Nota

Una directiva de bloqueo se puede eludir mediante la reflexión. Proporciona un límite claro para los desarrolladores de terceros, pero no proporciona una seguridad fuerte.

Hay más información y ejemplos disponibles en el SDK de modelado y visualización de Visual Studio.

Nota

El componente Transformación de plantilla de texto se instala de forma automática como parte de la carga de trabajo Desarrollo de extensiones de Visual Studio. También lo puede instalar desde la pestaña Componentes individuales del Instalador de Visual Studio, en la categoría SDK, bibliotecas y marcos. Instale el componente SDK de modelado desde la pestaña Componentes individuales.

Establecimiento y obtención de bloqueos

Puede establecer bloqueos en el almacén, en una partición o en un elemento individual. Por ejemplo, esta instrucción impide que se elimine un elemento de modelo y también que se cambien sus propiedades:

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

Se pueden usar otros valores de bloqueo para evitar cambios en las relaciones, la creación de elementos, el movimiento entre particiones y la reordenación de vínculos en un rol.

Los bloqueos se aplican tanto a las acciones del usuario como al código de programa. Si el código de programa intenta realizar un cambio, se produce una excepción InvalidOperationException. Los bloqueos se omiten en una operación Deshacer o Rehacer.

Puede detectar si un elemento tiene un bloqueo en un conjunto determinado mediante IsLocked(Locks) y puede obtener el conjunto actual de bloqueos en un elemento mediante GetLocks().

Puede establecer un bloqueo sin usar una transacción. La base de datos de bloqueo no forma parte del almacén. Si establece un bloqueo en respuesta a un cambio de un valor en el almacén, por ejemplo, en OnValueChanged, debe permitir los cambios que forman parte de una operación Deshacer.

Estos métodos son métodos de extensión que se definen en el espacio de nombres Microsoft.VisualStudio.Modeling.Immutability.

Bloqueos en particiones y almacenes

También se pueden aplicar bloqueos a particiones y al almacén. Un bloqueo que se establece en una partición se aplica a todos los elementos de esta. Por lo tanto, por ejemplo, la siguiente instrucción impide que se eliminen todos los elementos de una partición, independientemente de los estados de sus propios bloqueos. Sin embargo, otros bloqueos, como Locks.Property, podrían seguir estando establecidos en elementos individuales:

partition.SetLocks(Locks.Delete);

Un bloqueo que se establece en el almacén se aplica a todos sus elementos, independientemente de la configuración de ese bloqueo en las particiones y los elementos.

Uso de bloqueos

Puede usar bloqueos para implementar esquemas como estos:

  • No permitir cambios en todos los elementos y relaciones, excepto en los que representan comentarios. Este enfoque permite a los usuarios anotar un modelo sin cambiarlo.

  • No permitir cambios en la partición predeterminada, pero permitirlos en la partición de diagrama. El usuario puede reorganizar el diagrama, pero no puede modificar el modelo subyacente.

  • No permitir cambios en el almacén, excepto en un grupo de usuarios registrados en una base de datos distinta. Para otros usuarios, el diagrama y el modelo son de solo lectura.

  • No permitir cambios en el modelo si una propiedad booleana del diagrama está establecida en true. Proporcione un comando de menú para cambiar esa propiedad. Este enfoque ayuda a garantizar que los usuarios no realicen cambios por accidente.

  • No permitir la adición y eliminación de elementos y relaciones de clases concretas, pero permitir cambios de propiedad. Este enfoque proporciona a los usuarios un formulario fijo en el que pueden rellenar las propiedades.

Valores de bloqueo

Se pueden establecer bloqueos sobre un almacén, una partición o un elemento ModelElement individual. Los bloqueos son una enumeración Flags: puede combinar sus valores mediante "|".

  • Los bloqueos de un elemento ModelElement siempre incluyen los bloqueos de su partición.

  • Los bloqueos de una partición siempre incluyen los bloqueos del almacén.

    No se puede establecer un bloqueo sobre una partición o almacén y, al mismo tiempo, deshabilitar el bloqueo sobre un elemento individual.

Value Lo que significa si IsLocked(Value) es true
None Sin restricción.
Propiedad No se pueden cambiar las propiedades de dominio de los elementos. Este valor no se aplica a las propiedades generadas por el rol de una clase de dominio en una relación.
Sumar Los nuevos elementos y vínculos no se pueden crear en una partición o almacén. No es aplicable a ModelElement.
Move El elemento no se puede mover entre particiones si element.IsLocked(Move) es true o si targetPartition.IsLocked(Move) es true.
Eliminar No se puede eliminar un elemento si este bloqueo está establecido sobre el propio elemento o sobre cualquiera de los elementos a los que se propagaría la eliminación, como elementos y formas insertados. Puede usar element.CanDelete() para detectar si se puede eliminar un elemento.
Reordenar No se puede cambiar el orden de los vínculos en un encargado de rol.
RolePlayer No se puede cambiar el conjunto de vínculos de origen en este elemento. Por ejemplo, los nuevos elementos no se pueden insertar en este elemento. Este valor no afecta a los vínculos en los que este elemento es el destino. Si este elemento es un vínculo, su origen y destino no se ven afectados.
Todo OR bit a bit de los demás valores.

Directivas de bloqueo

Como autor de un DSL, puede definir una directiva de bloqueo. Una directiva de bloqueo modera el funcionamiento de SetLocks(), de modo que puede evitar que se establezcan bloqueos específicos u ordenar que lo hagan. Normalmente, se emplearía una directiva de bloqueo para disuadir a los usuarios o desarrolladores de contravenir accidentalmente el uso previsto de una DSL, de la misma manera que se puede declarar una variable private.

También puede usar una directiva de bloqueo para establecer bloqueos sobre todos los elementos dependientes del tipo del elemento. Este comportamiento se debe a que SetLocks(Locks.None) siempre se llama cuando se crea o deserializa un elemento por primera vez desde el archivo.

Sin embargo, no se puede usar una directiva para variar los bloqueos que tiene un elemento durante su vida útil. Para lograr ese efecto, debe usar llamadas a SetLocks().

Para definir una directiva de bloqueo:

  • Cree una clase que implemente ILockingPolicy.

  • Agregue esta clase a los servicios que están disponibles mediante el elemento DocData de su DSL.

Definición de una directiva de bloqueo

ILockingPolicy tiene la siguiente definición:

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

Se llama a estos métodos cuando se realiza una llamada a SetLocks() en un almacén, una partición o un elemento ModelElement. En cada método, se proporciona un conjunto propuesto de bloqueos. Puede devolver el conjunto propuesto o puede sumar y restar bloqueos.

Por ejemplo:

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

Para asegurarse de que los usuarios siempre puedan eliminar elementos, incluso si otro código llama a SetLocks(Lock.Delete):.

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

Para no permitir cambios en todas las propiedades de todos los elementos de MyClass:

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

Permitir que la directiva esté disponible como servicio

En el proyecto DslPackage, agregue un nuevo archivo que contenga código similar al del ejemplo siguiente:

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