Compartilhar via


Definir uma política de bloqueio para criar segmentos somente leitura

A API de Imutabilidade do SDK de Visualização e Modelagem do Visual Studio permite que um programa bloqueie parte ou todo um modelo DSL (linguagem específica de domínio) para que ele possa ser lido, mas não alterado. Essa opção somente leitura pode ser usada, por exemplo, para que um usuário possa pedir aos colegas para anotar e revisar um modelo DSL, mas pode não permitir que eles alterem o original.

Além disso, como autor de um DSL, você pode definir uma política de bloqueio. Uma política de bloqueio define quais bloqueios são permitidos, não permitidos ou obrigatórios. Por exemplo, ao publicar um DSL, você pode incentivar desenvolvedores de terceiros a estendê-lo com novos comandos. Mas você também pode usar uma política de bloqueio para impedi-los de alterar o status somente leitura de partes especificadas do modelo.

Observação

Uma política de bloqueio pode ser contornada usando reflexão. Ele fornece um limite claro para desenvolvedores de terceiros, mas não fornece segurança forte.

Mais informações e exemplos estão disponíveis no SDK de Visualização e Modelagem do Visual Studio.

Observação

O componente Transformação de Modelo de Texto é instalado automaticamente como parte da carga de trabalho de Desenvolvimento de extensões do Visual Studio. Você também pode instalá-lo na guia Componentes individuais do Instalador do Visual Studio, na categoria SDKs, bibliotecas e estruturas. Instale o componente SDK de Modelagem na guia Componentes individuais.

Configuração e obtenção de bloqueios

Você pode definir bloqueios no repositório, em uma partição ou em um elemento individual. Por exemplo, essa instrução impede que um elemento de modelo seja excluído e também impede que suas propriedades sejam alteradas:

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

Outros valores de bloqueio podem ser usados para evitar alterações em relações, criação de elementos, movimento entre partições e reordenação de links em uma função.

Os bloqueios se aplicam às ações do usuário e ao código do programa. Se o código do programa tentar fazer uma alteração, um InvalidOperationException será gerado. Os bloqueios são ignorados em uma operação Desfazer ou Refazer.

Você pode descobrir se um elemento tem um bloqueio em um determinado conjunto usando IsLocked(Locks) e obter o conjunto atual de bloqueios em um elemento usando GetLocks().

Você pode definir um bloqueio sem usar uma transação. O banco de dados de bloqueio não faz parte do repositório. Se você definir um bloqueio em resposta a uma alteração de um valor no repositório, por exemplo, em OnValueChanged, permita alterações que fazem parte de uma operação Desfazer.

Esses métodos são métodos de extensão definidos no namespace Microsoft.VisualStudio.Modeling.Immutability.

Bloqueios em partições e repositórios

Os bloqueios também podem ser aplicados a partições e ao repositório. Um bloqueio definido em uma partição se aplica a todos os elementos na partição. Portanto, por exemplo, a instrução a seguir impede que todos os elementos em uma partição sejam excluídos, independentemente dos estados de seus próprios bloqueios. No entanto, outros bloqueios, como Locks.Property, ainda podem ser definidos em elementos individuais:

partition.SetLocks(Locks.Delete);

Um bloqueio definido no repositório aplica-se a todos os seus elementos, independentemente das configurações desse bloqueio nas partições e nos elementos.

Usando bloqueios

Você pode usar bloqueios para implementar esquemas como os seguintes exemplos:

  • Não permitir alterações em todos os elementos e relações, exceto os que representam comentários. Essa abordagem permite que os usuários anotem um modelo sem alterá-lo.

  • Não permitir alterações na partição padrão, mas permitir alterações na partição de diagrama. O usuário pode reorganizar o diagrama, mas não pode alterar o modelo subjacente.

  • Não permitir alterações no repositório, exceto para um grupo de usuários registrados em um banco de dados separado. Para outros usuários, o diagrama e o modelo são somente leitura.

  • Não permitir alterações no modelo se uma propriedade booliana do diagrama estiver definida como true. Forneça um comando de menu para alterar essa propriedade. Essa abordagem ajuda a garantir que os usuários não façam alterações acidentalmente.

  • Não permitir a adição e exclusão de elementos e relações de classes específicas, mas permitir alterações de propriedade. Essa abordagem fornece aos usuários um formulário fixo no qual eles podem preencher as propriedades.

Valores de Bloqueio

Os bloqueios podem ser definidos em um repositório, partição ou ModelElement individual. Bloqueio é uma enumeração Flags: você pode combinar seus valores usando '|'.

  • Bloqueios de um ModelElement sempre incluem os Bloqueios de sua partição.

  • Os bloqueios de uma partição sempre incluem os Bloqueios do repositório.

    Você não pode definir um bloqueio em uma partição ou repositório e, ao mesmo tempo, desabilitar o bloqueio em um elemento individual.

Valor Ou seja, se IsLocked(Value) for true
Nenhum Sem restrição.
Propriedade As propriedades de domínio dos elementos não podem ser alteradas. Esse valor não se aplica às propriedades geradas pela função de uma classe de domínio em uma relação.
Adicionar Não é possível criar novos elementos e links em uma partição ou repositório. Não se aplica a ModelElement.
Mover O elemento não poderá ser movido entre partições se element.IsLocked(Move) ou targetPartition.IsLocked(Move) for true.
Excluir Um elemento não poderá ser excluído se esse bloqueio for definido no próprio elemento ou em qualquer um dos elementos para os quais a exclusão se propagaria, como elementos e formas inseridos. Você pode usar element.CanDelete() para descobrir se um elemento pode ser excluído.
Reordenar A ordenação de links em um representante da função não pode ser alterada.
RolePlayer O conjunto de links que são originados neste elemento não pode ser alterado. Por exemplo, novos elementos não podem ser inseridos nesse elemento. Esse valor não afeta os links para os quais esse elemento é o destino. Se esse elemento for um link, sua origem e destino não serão afetados.
Tudo OR bit a bit dos outros valores.

Políticas de bloqueio

Como autor de um DSL, você pode definir uma política de bloqueio. Uma política de bloqueio modera a operação de SetLocks(), para que você possa impedir ou exigir que bloqueios específicos sejam definidos. Normalmente, você usaria uma política de bloqueio para desencorajar usuários ou desenvolvedores de violar acidentalmente o uso pretendido de uma DSL, da mesma maneira que você pode declarar uma variável private.

Você também pode usar uma política de bloqueio para definir bloqueios em todos os elementos dependentes do tipo do elemento. Esse comportamento ocorre porque SetLocks(Locks.None) é sempre chamado quando um elemento é criado pela primeira vez ou desserializado do arquivo.

No entanto, você não pode usar uma política para variar os bloqueios em um elemento durante sua vida útil. Para obter esse efeito, você deve usar chamadas para SetLocks().

Para definir uma política de bloqueio:

  • Crie uma classe que implementa ILockingPolicy.

  • Adicione essa classe aos serviços que estão disponíveis por meio do DocData de sua DSL.

Para definir uma política de bloqueio

ILockingPolicy tem a seguinte definição:

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

Esses métodos são chamados quando uma chamada é feita para SetLocks() em um repositório, partição ou ModelElement. Em cada método, você recebe um conjunto proposto de bloqueios. Você pode retornar o conjunto proposto ou adicionar e subtrair bloqueios.

Por exemplo:

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 garantir que os usuários sempre possam excluir elementos, mesmo que outro código chame SetLocks(Lock.Delete):

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

Para não permitir a alteração em todas as propriedades de cada elemento de MyClass:

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

Para disponibilizar sua política como um serviço

Em seu projeto DslPackage, adicione um novo arquivo que contenha código semelhante ao seguinte exemplo:

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