Compartilhar via


Personalizar o armazenamento de arquivos e a serialização XML

Quando o usuário salva uma instância ou modelo de uma DSL (linguagem específica de domínio) no Visual Studio, um arquivo XML é criado ou atualizado. O arquivo pode ser recarregado para recriar o modelo no repositório.

Você pode personalizar o esquema de serialização ajustando as configurações em Comportamento de Serialização XML no Gerenciador de DSL. Há um nó em Comportamento de Serialização Xml para cada classe de domínio, propriedade e relação. Os relacionamentos estão localizados nas respectivas classes de origem. Também há nós correspondentes às classes de forma, conector e diagrama.

Você também pode escrever o código do programa para uma personalização mais avançada.

Observação

Se você quiser salvar o modelo em um formato específico, mas não precisar recarregá-lo desse formulário, considere usar modelos de texto para gerar saída do modelo, em vez de um esquema de serialização personalizado. Para obter mais informações, confira Gerar código de uma linguagem específica de domínio.

Arquivos de modelo e de diagrama

Cada modelo é salvo em dois arquivos:

  • O arquivo de modelo tem um nome como Model1.mydsl. Ele armazena os elementos e os relacionamentos do modelo e as respectivas propriedades. A extensão do arquivo, como .mydsl é determinada pela propriedade FileExtension do nó Editor na Definição de DSL.

  • O arquivo de diagrama tem um nome como Model1.mydsl.diagram. Ele armazena as formas, os conectores e as respectivas posições, as cores, as espessuras de linha e outros detalhes da aparência do diagrama. Se o usuário excluir um .diagram arquivo, as informações essenciais no modelo não serão perdidas. Somente o layout do diagrama será perdido. Quando o arquivo de modelo é aberto, um conjunto padrão de formas e conectores é criado.

Para alterar a extensão de arquivo de uma DSL

  1. Abra a Definição da DSL. No Gerenciador de DSL, clique no nó Editor.

  2. Na janela Propriedades, edite a propriedade FileExtension. Não inclua a inicial . da extensão do nome do arquivo.

  3. No Gerenciador de Soluções, altere o nome dos dois arquivos de modelo de item em DslPackage\ProjectItemTemplates. Esses arquivos têm nomes que seguem este formato:

    myDsl.diagram

    myDsl.myDsl

O esquema de serialização padrão

Para criar um exemplo para este tópico, a definição de DSL a seguir foi usada.

Diagrama de Definição de DSL – modelo de árvore genealógica

Essa DSL foi usada para criar um modelo que tem a aparência a seguir na tela.

Gerenciador, caixa de ferramentas e diagrama de árvore genealógica

Este modelo foi salvo e reaberto no editor de texto XML:

<?xml version="1.0" encoding="utf-8"?>
<familyTreeModel xmlns:dm0="http://schemas.microsoft.com/VisualStudio/2008/DslTools/Core" dslVersion="1.0.0.0" Id="f817b728-e920-458e-bb99-98edc469d78f" xmlns="http://schemas.microsoft.com/dsltools/FamilyTree">
  <people>
    <person name="Henry VIII" birthYear="1491" deathYear="1547" age="519">
      <children>
        <personMoniker name="/f817b728-e920-458e-bb99-98edc469d78f/Elizabeth I" />
        <personMoniker name="/f817b728-e920-458e-bb99-98edc469d78f/Mary" />
      </children>
    </person>
    <person name="Elizabeth I" birthYear="1533" deathYear="1603" age="477" />
    <person name="Mary" birthYear="1515" deathYear="1558" age="495" />
  </people>
</familyTreeModel>

Observe os seguintes pontos sobre o modelo serializado:

  • Cada nó XML tem um nome que é o mesmo que um nome de classe de domínio, exceto que a letra inicial é minúscula. Por exemplo, familyTreeModel e person.

  • Propriedades de domínio, como Name e BirthYear, são serializadas como atributos nos nós XML. Novamente, o caractere inicial do nome da propriedade é convertido em minúsculas.

  • Cada relacionamento é serializado como um nó XML aninhado dentro da extremidade de origem do relacionamento. O nó tem o mesmo nome que a propriedade de função de origem, mas com um caractere inicial minúsculo.

    Por exemplo, na Definição de DSL, uma função chamada People é originada na classe FamilyTree. No XML, a função Pessoas é representada com um nó chamado people aninhado dentro do familyTreeModel nó.

  • O destino final de cada relacionamento de inserção é serializado como um nó aninhado no relacionamento. Por exemplo, o nó people contém vários nós person.

  • O final de destino de cada relacionamento de referência é serializado como um moniker, que codifica uma referência ao elemento de destino.

    Por exemplo, em um nó person, pode haver um relacionamento children. Esse nó contém monikers como:

    <personMoniker name="/f817b728-e920-458e-bb99-98edc469d78f/Elizabeth I" />
    

Noções básicas sobre monikers

Monikers são usados para representar referências cruzadas entre diferentes partes dos arquivos de modelo e de diagrama. Eles também são usados no .diagram arquivo para se referir a nós no arquivo de modelo. Há duas formas de moniker:

  • Os monikers de ID citam o GUID do elemento de destino. Por exemplo:

    <personShapeMoniker Id="f79734c0-3da1-4d72-9514-848fa9e75157" />
    
  • Os monikers de chave qualificados identificam o elemento de destino pelo valor de uma propriedade de domínio designada chamada chave moniker. O moniker do elemento de destino é prefixado com o moniker de seu elemento pai na árvore de relações de inserção.

    Os exemplos a seguir são obtidos de uma DSL na qual há uma classe de domínio chamada Album, que tem uma relação de inserção com uma classe de domínio chamada Song:

    <albumMoniker title="/My Favorites/Jazz after Teatime" />
    <songMoniker title="/My Favorites/Jazz after Teatime/Hot tea" />
    

    Os monikers de chave qualificados serão usados se a classe de destino tiver uma propriedade de domínio para a qual a opção É Chave de Moniker está definida em true Comportamento de serialização Xml. No exemplo, essa opção é definida para propriedades de domínio chamadas "Title" nas classes de domínio "Album" e "Song".

Monikers de chave qualificados são mais fáceis de ler do que monikers de ID. Se você pretende que o XML de seus arquivos de modelo seja legível, considere o uso de monikers de chave qualificados. No entanto, é possível que o usuário defina mais de um elemento para ter a mesma chave de moniker. Chaves duplicadas podem fazer com que o arquivo não recarregue corretamente. Portanto, se você definir uma classe de domínio referenciada usando monikers de chave qualificados, considere maneiras de impedir que o usuário salve um arquivo que tenha monikers duplicados.

Para definir uma classe de domínio a ser referenciada por monikers de ID

  1. Verifique se É Chave Moniker é false para cada propriedade de domínio na classe e nas respectivas classes base.

    1. No Gerenciador de DSL, expanda Comportamento de Serialização XML\Dados de Classe\<a classe de domínio>\Dados do Elemento.

    2. Verifique se É Chave Moniker é false para cada propriedade de domínio.

    3. Se a classe de domínio tiver uma classe base, repita o procedimento nessa classe.

  2. Defina Serializar ID = true para a classe de domínio.

    Essa propriedade pode ser encontrada em Comportamento de Serialização XML.

Para definir uma classe de domínio a ser referenciada por monikers de chave qualificados

  • Defina É Chave Moniker para uma propriedade de domínio de uma classe de domínio existente. O tipo da propriedade precisa ser string.

    1. No Gerenciador de DSL, expanda Comportamento de Serialização XML\Dados de Classe\<a classe de domínio>\Dados do Elemento e selecione a propriedade de domínio.

    2. No janela Propriedades, defina É Chave Moniker como true.

  • - ou -

    Crie uma classe de domínio usando a ferramenta Classe de Domínio Nomeada.

    Essa ferramenta cria uma classe que tem uma propriedade de domínio chamada Nome. As propriedades É Nome de Elemento e É Chave Moniker dessa propriedade de domínio são inicializadas para true.

  • - ou -

    Crie um relacionamento de herança da classe de domínio para outra classe que tenha uma propriedade de chave moniker.

Evitar monikers duplicados

Se você usar monikers de chave qualificados, é possível que dois elementos no modelo de um usuário tenham o mesmo valor na propriedade key. Por exemplo, se a DSL tiver uma classe Person que tenha um nome de propriedade, o usuário poderá definir os Nomes de dois elementos como os mesmos. Embora o modelo pudesse ser salvo em um arquivo, ele não seria recarregado corretamente.

Há vários métodos que ajudam a evitar essa situação:

  • Defina É Nome de Elemento = true para a propriedade de domínio de chave. Selecione a propriedade de domínio no diagrama Definição de DSL e defina o valor na janela Propriedades.

    Quando o usuário cria uma instância da classe, esse valor faz com que a propriedade de domínio seja atribuída automaticamente a um valor diferente. O comportamento padrão adiciona um número ao final do nome da classe. Isso não impede que o usuário altere o nome para uma duplicata, mas ajuda no caso em que o usuário não define o valor antes de salvar o modelo.

  • Habilite a validação para a DSL. No Gerenciador de DSL, selecione Editor\Validação e defina as propriedades Usa... como true.

    Há um método de validação gerado automaticamente que verifica se há ambiguidades. O método está na categoria de validação Load. Isso garante que o usuário seja avisado de que talvez não seja possível reabrir o arquivo.

    Para obter mais informações, confira Validação em uma linguagem específica de domínio.

Caminhos e qualificadores de moniker

Um moniker de chave qualificado termina com a chave de moniker e é prefixado com o moniker do próprio pai na árvore de inserção. Por exemplo, se o moniker de um Álbum for:

<albumMoniker title="/My Favorites/Jazz after Teatime" />

Então uma das Músicas desse Álbum poderia ser:

<songMoniker title="/My Favorites/Jazz after Teatime/Hot tea" />

No entanto, se os Álbuns forem referenciados pela ID, os monikers serão os seguintes:

<albumMoniker Id="77472c3a-9bf9-4085-976a-d97a4745237c" />
<songMoniker title="/77472c3a-9bf9-4085-976a-d97a4745237c/Hot tea" />

Observe que, como um GUID é exclusivo, ele nunca é prefixado pelo apelido de seu pai.

Se você souber que uma propriedade de domínio específica sempre terá um valor exclusivo dentro de um modelo, defina É Qualificador de Moniker como true para essa propriedade. Isso faz com que ele seja usado como um qualificador, sem usar o apelido do pai. Por exemplo, se você definir Is Moniker Qualifier e Is Moniker Key para a propriedade de domínio Title da classe Album, o nome ou identificador do modelo não será usado em monikers para Album e seus filhos inseridos:

<albumMoniker name="Jazz after Teatime" />
<songMoniker title="/Jazz after Teatime/Hot tea" />

Personalizar a estrutura do XML

Para fazer as personalizações a seguir, expanda o nó Comportamento de Serialização XML no Gerenciador de DSL. Em uma classe de domínio, expanda o nó Dados do Elemento para ver a lista de propriedades e relações que são originadas nessa classe. Selecione um relacionamento e ajuste as opções dele na janela Propriedades.

  • Defina Omitir Elemento como true para omitir o nó de função de origem, deixando apenas a lista de elementos de destino. Você não deve definir essa opção se houver mais de uma relação entre as classes de origem e de destino.

    <familyTreeModel ...>
      <!-- The following node is omitted by using Omit Element: -->
      <!-- <people> -->
        <person name="Henry VIII" .../>
        <person name="Elizabeth I" .../>
      <!-- </people> -->
    </familyTreeModel>
    
  • Defina Usar Formulário Completo para inserir os nós de destino em nós que representam as instâncias de relacionamento. Essa opção é definida automaticamente quando você adiciona propriedades de domínio a um relacionamento de domínio.

    <familyTreeModel ...>
      <people>
        <!-- The following node is inserted by using Use Full Form: -->
        <familyTreeModelHasPeople myRelationshipProperty="x1">
          <person name="Henry VIII" .../>
        </familyTreeModelHasPeople>
        <familyTreeModelHasPeople myRelationshipProperty="x2">
          <person name="Elizabeth I" .../>
        </familyTreeModelHasPeople>
      </people>
    </familyTreeModel>
    
  • Defina Representação = Elemento para ter uma propriedade de domínio salva como um elemento em vez de como um valor de atributo.

    <person name="Elizabeth I" birthYear="1533">
      <deathYear>1603</deathYear>
    </person>
    
  • Para alterar a ordem na qual os atributos e relações são serializados, clique com o botão direito do mouse em um item em Dados do Elemento e use os comandos de menu Mover para Cima ou Mover para Baixo.

Personalização principal usando o código do programa

Você pode substituir partes ou todos os algoritmos de serialização.

Recomendamos que você estude o código em Dsl\Generated Code\Serializer.cs e SerializationHelper.cs.

Para personalizar a serialização de uma classe específica

  1. Defina É Personalizado no nó para essa classe em Comportamento de Serialização XML.

  2. Transforme Todos os Modelos, crie a solução e investigue os erros de compilação resultantes. Comentários próximos a cada erro explicam qual código você precisa fornecer.

Para fornecer sua própria serialização para todo o modelo

  1. Substituir métodos em Dsl\GeneratedCode\SerializationHelper.cs

Observação

A partir do Visual Studio 2022 17.13, a implementação de serialização padrão não dá mais suporte à serialização ou desserialização de tipos de dados personalizados usando BinaryFormatter devido a riscos de segurança com BinaryFormatter.

Se você usar um tipo de dados personalizado para qualquer propriedade de domínio, precisará substituir os métodos de serialização na SerializationHelper classe ou implementar um TypeConverter capaz de converter cada tipo de dados personalizado de e para uma cadeia de caracteres.

Embora não seja recomendável usar BinaryFormatter por motivos de segurança, se você precisar manter a compatibilidade com versões anteriores com modelos mais antigos que usavam BinaryFormatter serialização, poderá implementar um TypeConverter que desserialize os dados binários. O snippet de código a seguir serve como um modelo para implementar essa compatibilidade:

class MyCustomDataTypeConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    {
        return destinationType == typeof(string) || base.CanConvertTo(context, destinationType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string text)
        {
            // First, try to parse the string as if it were returned by MyCustomDataType.ToString().
            if (MyCustomDataType.TryParse(text, out var custom))
                return custom;

            // Fall back to trying to deserialize the old BinaryFormatter serialization format.
            var decoded = Convert.FromBase64String(text);
            using (var memory = new MemoryStream(decoded, false))
            {
                var binaryFormatter = new BinaryFormatter();
                return binaryFormatter.Deserialize(memory) as MyCustomDataType;
            }
        }

        return base.ConvertFrom(context, culture, value);
    }

    public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
    {
        if (destinationType == typeof(string) && value is MyCustomDataType custom)
            return custom.ToString();

        return base.ConvertTo(context, culture, value, destinationType);
    }
}

// ...

[TypeConverter(MyCustomDataTypeConverter)]
class MyCustomDataType
{
    // ...
}

Opções no comportamento de serialização XML

No DSL Explorer, o nó Comportamento de Serialização Xml contém um nó filho para cada classe de domínio, relação, forma, conector e classe de diagrama. Em cada um desses nós há uma lista de propriedades e relacionamentos originados nesse elemento. Os relacionamentos são representados por si sós e sob as próprias classes de origem.

A tabela a seguir resume as opções que você pode definir nesta seção da Definição de DSL. Em cada caso, selecione um elemento no Gerenciador de DSL e defina as opções na janela Propriedades.

Dados da classe XML

Esses elementos são encontrados no Gerenciador de DSL em Comportamento de Serialização XML\Dados de Classe.

Propriedade Descrição
Tem Esquema de Elemento Personalizado Se é True, indica que a classe de domínio tem um esquema de elemento personalizado
É Personalizado Defina o valor como True se quiser escrever seu próprio código de serialização e desserialização para essa classe de domínio.

Crie a solução e investigue os erros para descobrir instruções detalhadas.
Classe de domínio Classe de domínio à qual esse nó de dados de classe se aplica. Somente leitura.
Nome do elemento Nome do nó XML para elementos dessa classe. O valor padrão é uma versão em letras minúsculas do nome da classe de domínio.
Nome do atributo moniker Nome do atributo usado em elementos moniker para conter a referência. Se estiver em branco, o nome da propriedade ou ID da chave será usado.

Neste exemplo, é "name": <personMoniker name="/Mike Nash"/>
Nome do elemento moniker Nome do elemento XML usado para monikers que se referem a elementos dessa classe.

O valor padrão é uma versão em minúsculas do nome da classe com o sufixo "Moniker". Por exemplo, personMoniker.
Nome do tipo moniker Nome do tipo de XSD gerado para os monikers para os elementos desta classe. O XSD está em Dsl\Generated Code\*Schema.xsd
Serializar ID Se for True, o GUID do elemento será incluído no arquivo. O valor deverá ser definido como True se não houver nenhuma propriedade marcada como Is Moniker Key e a DSL definir relações de referência para essa classe.
Nome do Tipo Nome do tipo de XML gerado no XSD com base na classe de domínio designada.
Observações Observações informais associadas a este elemento

Dados da propriedade XML

Os nós de propriedade XML são encontrados nos nós de classe.

Propriedade Descrição
Propriedade de domínio Propriedade à qual os dados de configuração de serialização XML se aplicam. Somente leitura.
É Chave de Moniker Se o valor for definido como True, a propriedade será usada como a chave para criar monikers que fazem referência a instâncias dessa classe de domínio.
É Qualificador de Moniker Se o valor for definido como True, a propriedade será usada para criar o qualificador em monikers. Se false e se SerializeId não for true para essa classe de domínio, os monikers serão qualificados pelo moniker do elemento pai na árvore de inserção.
Representação Se o valor for definido como Atributo, a propriedade será serializada como um atributo xml; se o valor for definido como Elemento, ele será serializado como um elemento; se o valor for definido como Ignorar, ele não será serializado.
Nome XML Nome usado para o atributo ou o elemento XML que representa a propriedade. Por padrão, o valor é uma versão em minúsculas do nome da propriedade de domínio.
Observações Observações informais associadas a este elemento

Dados da função XML

Os nós de dados de função são encontrados nos nós da classe de origem.

Propriedade Descrição
Tem Moniker Personalizado Defina isso como true se você quiser fornecer código próprio para gerar e resolver monikers que fazem parte desse relacionamento.

Para obter instruções detalhadas, crie a solução e clique duas vezes nas mensagens de erro.
Relacionamento de domínio Especifica o relacionamento ao qual essas opções se aplicam. Somente leitura.
Omitir Elemento Se for true, o nó XML que corresponde à função de origem será omitido do esquema.

Se houver mais de uma relação entre as classes de origem e de destino, esse nó de função distinguirá entre os links que pertencem às duas relações. Portanto, recomendamos que você não defina essa opção neste caso.
Nome do Elemento de Função Especifica o nome do elemento XML derivado da função de origem. O valor padrão é o nome da propriedade da função.
Usar Formulário Completo Se for true, cada elemento de destino ou moniker será colocado em um nó XML que representa o relacionamento. Isso deverá ser definido como true se a relação tiver as próprias propriedades de domínio.