Compartilhar via


Demonstra Passo a passo: Gerando código usando os modelos de texto

Geração de código permite produzir código de programa que possui rigidez de tipos e ainda pode ser facilmente alterado quando o modelo de origem é alterado. Compare isso com a técnica alternativa de escrever um programa completamente genérico que aceita um arquivo de configuração, o que é mais flexível, mas resulta em código que não é tão fácil de ler e alterar, nem tem como um bom desempenho. Esta explicação passo a passo demonstra esse benefício.

Código digitado para a leitura de XML

O namespace System. XML fornece ferramentas abrangentes para carregar um documento XML e navegar livremente na memória-lo. Infelizmente, todos os nós têm o mesmo tipo XmlNode. Portanto, é muito fácil cometer erros de programação como, por exemplo, esperando o tipo de nó filho ou os atributos incorretos errado.

Neste projeto de exemplo, um modelo lê um arquivo XML de exemplo e gera classes que correspondem a cada tipo de nó. No código escritas à mão, você pode usar essas classes para navegar no arquivo XML. Você também pode executar seu aplicativo em todos os arquivos que usam os mesmos tipos de nó. A finalidade do arquivo XML de exemplo é fornecer exemplos de todos os tipos de nó que você deseja lidar com seu aplicativo.

ObservaçãoObservação

O aplicativo xsd. exe, que está incluído no Visual Studio, que pode gerar classes com rigidez de tipos a partir de todos os arquivos XML. O modelo mostrado aqui é fornecido como um exemplo.

Aqui está o arquivo de exemplo:

<?xml version="1.0" encoding="utf-8" ?>
<catalog>
  <artist id ="Mike%20Nash" name="Mike Nash Quartet">
    <song id ="MikeNashJazzBeforeTeatime">Jazz Before Teatime</song>
    <song id ="MikeNashJazzAfterBreakfast">Jazz After Breakfast</song>
  </artist>
  <artist id ="Euan%20Garden" name="Euan Garden">
    <song id ="GardenScottishCountry">Scottish Country Garden</song>
  </artist>
</catalog>

No projeto que constrói a este passo a passo, você pode escrever código, como a seguir e IntelliSense solicita a você os nomes de atributo e o filho corretos ao digitar:

Catalog catalog = new Catalog(xmlDocument);
foreach (Artist artist in catalog.Artist)
{
  Console.WriteLine(artist.name);
  foreach (Song song in artist.Song)
  {
    Console.WriteLine("   " + song.Text);
  }
}

Compare isso com o código não tipado que você pode escrever sem o modelo:

XmlNode catalog = xmlDocument.SelectSingleNode("catalog");
foreach (XmlNode artist in catalog.SelectNodes("artist"))
{
    Console.WriteLine(artist.Attributes["name"].Value);
    foreach (XmlNode song in artist.SelectNodes("song"))
    {
         Console.WriteLine("   " + song.InnerText);
     }
}

Na versão com rigidez de tipos, uma alteração no esquema XML resultará em alterações para as classes. O compilador irá realçar partes do código do aplicativo que deve ser alterado. Na versão tipada que usa código XML genérico, não existe tal suporte.

Neste projeto, um arquivo de modelo único é usado para gerar as classes que possibilitam a versão digitada.

Definindo o projeto

Crie ou abra um projeto C#

Você pode aplicar essa técnica para qualquer projeto de código. Esta explicação passo a passo usa um projeto C# e para fins de teste, usamos um aplicativo de console.

Para criar o projeto

  1. Sobre o arquivo menu clique novo e, em seguida, clique em projeto.

  2. Clique o C# Visual nó e depois no modelos painel, clique em aplicativo de Console.

Adicionar um arquivo XML de protótipo ao projeto

A finalidade desse arquivo é fornecer exemplos dos tipos de nó XML que você deseja que o aplicativo seja capaz de ler. Pode ser um arquivo que será usado para testar seu aplicativo. O modelo produzirá uma classe C# para cada tipo de nó neste arquivo.

O arquivo deve ser parte do projeto para que o modelo pode lê-lo, mas não será compilado no aplicativo compilado.

Para adicionar um arquivo XML

  1. Em Solution Explorer, o botão direito do mouse no projeto, clique em Add e, em seguida, clique em Novo Item.

  2. No Add New Item caixa de diálogo, selecione Arquivo XML da modelos de painel.

  3. Adicione o conteúdo de amostra para o arquivo.

  4. Para esta explicação, nomeie o arquivo exampleXml.xml. Defina o conteúdo do arquivo a ser o XML mostrado na seção anterior.

..

Adicionar um arquivo de código de teste

Adicionar um arquivo C# ao seu projeto e escrever em uma amostra do código que você deseja ser capaz de gravar. Por exemplo:

using System;
namespace MyProject
{
  class CodeGeneratorTest
  {
    public void TestMethod()
    {
      Catalog catalog = new Catalog(@"..\..\exampleXml.xml");
      foreach (Artist artist in catalog.Artist)
      {
        Console.WriteLine(artist.name);
        foreach (Song song in artist.Song)
        {
          Console.WriteLine("   " + song.Text);
} } } } }

Neste estágio, esse código não será compilado. Enquanto você escreve o modelo, você irá gerar classes que permitem que ela tenha êxito.

Um teste mais abrangente pôde verificar a saída desta função de teste contra conteúdo conhecido do arquivo XML de exemplo. Mas nesta explicação, estaremos satisfeitos quando o método de teste compila.

Adicionar um arquivo de modelo de texto

Adicionar um arquivo de modelo de texto e definir a extensão de saída para "CS".

Para adicionar um arquivo de modelo de texto ao seu projeto.

  1. Em Solution Explorer, o botão direito do mouse no projeto, clique em Adde em seguida, clique em Novo Item.

  2. No Add New Item selecione caixa de diálogo Modelo de texto da modelos de painel.

    ObservaçãoObservação

    Certifique-se de que você adicionar um modelo de texto e não um pré-processado modelo de texto.

  3. No arquivo, na diretiva de modelo, alterar o hostspecific atributo para true.

    Esta alteração permitirá que o modelo de código para acessar o Visual Studio services.

  4. Na diretiva de saída, altere o atributo de extensão "CS", para que o modelo gera um arquivo C#. Em um projeto de Visual Basic, você teria mude para "vb".

  5. Salve o arquivo. Neste estágio, o arquivo de modelo de texto deve conter essas linhas:

    <#@ template debug="false" hostspecific="true" language="C#" #>
    <#@ output extension=".cs" #>
    

.

Observe que aparece um arquivo. cs no Solution Explorer como uma subsidiária do arquivo de modelo. Você pode vê-lo clicando em [+] ao lado do nome do arquivo de modelo. Este arquivo é gerado a partir do arquivo de modelo sempre que você salva ou move o foco para fora do arquivo de modelo. O arquivo gerado será ser compilado como parte do seu projeto.

Para sua conveniência, ao mesmo tempo em que você desenvolver o arquivo de modelo, organize as janelas de arquivo de modelo e o arquivo gerado, para que você pode vê-los lado a lado. Isso permite que você veja imediatamente a saída do seu modelo. Você notará que, quando o modelo gera C# código inválido, erros serão exibidos na janela da mensagem de erro.

Qualquer edição que você executar diretamente no arquivo gerado serão perdidas sempre que você salvar o arquivo de modelo. Você deve, portanto, evite editar o arquivo gerado ou editá-lo somente para experimentos curtos. Às vezes é útil tentar um curto fragmento de código no arquivo gerado, onde IntelliSense é em operação, em seguida, copie-o para o arquivo de modelo.

Desenvolvendo o modelo de texto

Seguindo o melhor conselho no desenvolvimento ágil, desenvolveremos o modelo em pequenas etapas, limpando a alguns dos erros a cada incremento, até que o código de teste compila e executa corretamente.

O código a ser gerado do protótipo

O código de teste requer uma classe para cada nó no arquivo. Portanto, alguns dos erros de compilação desaparecerá se você acrescentar essas linhas para o modelo e, em seguida, salvá-lo:

  class Catalog {} 
  class Artist {}
  class Song {}

Isso ajuda você a ver o que é necessário, mas as declarações devem ser geradas a partir de tipos de nós no arquivo XML de exemplo. Exclua essas linhas experimentais do modelo.

Gerar código de aplicativo a partir do arquivo XML de modelo

Para ler o arquivo XML e gerar as declarações de classe, substitua o modelo de conteúdo com o seguinte código de modelo:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml"#>
<#@ import namespace="System.Xml" #>
<#
 XmlDocument doc = new XmlDocument();
 // Replace this file path with yours:
 doc.Load(@"C:\MySolution\MyProject\exampleXml.xml");
 foreach (XmlNode node in doc.SelectNodes("//*"))
 {
#>
  public partial class <#= node.Name #> {}
<#
 }
#>

Substitua o caminho do arquivo com o caminho correto para seu projeto.

Observe os delimitadores de bloco de código <#...#>. Esses delimitadores colchete um fragmento do código do programa que gera o texto. Os delimitadores de bloco de expressão <#=...#> colchete uma expressão que pode ser avaliada em uma seqüência de caracteres.

Quando você estiver escrevendo um modelo que gera o código-fonte para seu aplicativo, você está lidando com dois textos de programa separado. O programa dos delimitadores de bloco de código executa toda vez que você salva o modelo ou move o foco para outra janela. O texto que ele gera, que aparece fora os delimitadores, é copiado para o arquivo gerado e se torna parte do código do seu aplicativo.

O <#@assembly#> diretiva comporta-se como uma referência, tornando o assembly disponível para o código do modelo. A lista de assemblies visto pelo modelo é separada da lista de referências do projeto de aplicativo.

O <#@import#> diretiva age como um using a instrução, permitindo que você use os nomes curtos de classes no namespace importado.

Infelizmente, embora este modelo gera o código, ela produz uma declaração de classe para cada nó do arquivo XML de exemplo, para que se houver várias instâncias da <song> o nó, várias declarações da música classe aparecerá.

Ler o arquivo de modelo, em seguida, gerar o código

Muitos modelos de texto seguem um padrão no qual a primeira parte do modelo lê o arquivo de origem e a segunda parte gera o modelo. Precisamos ler todo o arquivo de exemplo para resumir os tipos de nós que ele contém e gere as declarações de classe. Outro <#@import#> é necessária para que podemos usar Dictionary<>:

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml"#>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Collections.Generic" #>
<#
 // Read the model file
 XmlDocument doc = new XmlDocument();
 doc.Load(@"C:\MySolution\MyProject\exampleXml.xml");
 Dictionary <string, string> nodeTypes = 
        new Dictionary<string, string>();
 foreach (XmlNode node in doc.SelectNodes("//*"))
 {
   nodeTypes[node.Name] = "";
 }
 // Generate the code
 foreach (string nodeName in nodeTypes.Keys)
 {
#>
  public partial class <#= nodeName #> {}
<#
 }
#>

Adicionar um método auxiliar

Um bloco de controle de recurso de classe é um bloco no qual você pode definir métodos auxiliares. O bloco é delimitado por <#+...#> e ele deve aparecer como o último bloco do arquivo.

Se preferir que os nomes de classe para começar com uma letra maiúscula, você pode substituir a última parte do modelo com o seguinte código de modelo:

// Generate the code
 foreach (string nodeName in nodeTypes.Keys)
 {
#>
  public partial class <#= UpperInitial(nodeName) #> {}
<#
 }
#>
<#+
 private string UpperInitial(string name)
 { return name[0].ToString().ToUpperInvariant() + name.Substring(1); }
#>

Neste estágio, o arquivo de CS gerado contém as seguintes declarações:

  public partial class Catalog {}
  public partial class Artist {}
  public partial class Song {}

Mais detalhes como, por exemplo, propriedades para os nós filho, atributos e texto interno podem ser adicionados usando a mesma abordagem.

Acessando a API de Visual Studio

Definindo a hostspecific atributo da <#@template#> diretiva permite que o modelo obter acesso ao Visual Studio API. O modelo pode usar isso para obter a localização dos arquivos de projeto, para evitar o uso de um caminho absoluto no código de modelo.

<#@ template debug="false" hostspecific="true" language="C#" #>
...
<#@ assembly name="EnvDTE" #>
...
EnvDTE.DTE dte = (EnvDTE.DTE) ((IServiceProvider) this.Host)
                       .GetService(typeof(EnvDTE.DTE));
// Open the prototype document.
XmlDocument doc = new XmlDocument();
doc.Load(System.IO.Path.Combine(dte.ActiveDocument.Path, "exampleXml.xml"));

Concluindo o modelo de texto

Conteúdo do modelo a seguir gera um código que permite que o código de teste compilar e executar.

<#@ template debug="false" hostspecific="true" language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="EnvDTE" #>
<#@ import namespace="System.Xml" #>
<#@ import namespace="System.Collections.Generic" #>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
namespace MyProject
{
<#
 // Map node name --> child name --> child node type
 Dictionary<string, Dictionary<string, XmlNodeType>> nodeTypes = new Dictionary<string, Dictionary<string, XmlNodeType>>();

 // The Visual Studio host, to get the local file path.
 EnvDTE.DTE dte = (EnvDTE.DTE) ((IServiceProvider) this.Host)
                       .GetService(typeof(EnvDTE.DTE));
 // Open the prototype document.
 XmlDocument doc = new XmlDocument();
 doc.Load(System.IO.Path.Combine(dte.ActiveDocument.Path, "exampleXml.xml"));
 // Inspect all the nodes in the document.
 // The example might contain many nodes of the same type, 
 // so make a dictionary of node types and their children.
 foreach (XmlNode node in doc.SelectNodes("//*"))
 {
   Dictionary<string, XmlNodeType> subs = null;
   if (!nodeTypes.TryGetValue(node.Name, out subs))
   {
     subs = new Dictionary<string, XmlNodeType>();
     nodeTypes.Add(node.Name, subs);
   }
   foreach (XmlNode child in node.ChildNodes)
   {
     subs[child.Name] = child.NodeType;
   } 
   foreach (XmlNode child in node.Attributes)
   {
     subs[child.Name] = child.NodeType;
   }
 }
 // Generate a class for each node type.
 foreach (string className in nodeTypes.Keys)
 {
    // Capitalize the first character of the name.
#>
    partial class <#= UpperInitial(className) #>
    {
      private XmlNode thisNode;
      public <#= UpperInitial(className) #>(XmlNode node) 
      { thisNode = node; }

<#
    // Generate a property for each child.
    foreach (string childName in nodeTypes[className].Keys)
    {
      // Allow for different types of child.
      switch (nodeTypes[className][childName])
      {
         // Child nodes:
         case XmlNodeType.Element:
#>
      public IEnumerable<<#=UpperInitial(childName)#>><#=UpperInitial(childName) #>
      { 
        get 
        { 
           foreach (XmlNode node in
                thisNode.SelectNodes("<#=childName#>")) 
             yield return new <#=UpperInitial(childName)#>(node); 
      } }
<#
         break;
         // Child attributes:
         case XmlNodeType.Attribute:
#>
      public string <#=childName #>
      { get { return thisNode.Attributes["<#=childName#>"].Value; } }
<#
         break;
         // Plain text:
         case XmlNodeType.Text:
#>
      public string Text  { get { return thisNode.InnerText; } }
<#
         break;
       } // switch
     } // foreach class child
  // End of the generated class:
#>
   } 
<#
 } // foreach class

   // Add a constructor for the root class 
   // that accepts an XML filename.
   string rootClassName = doc.SelectSingleNode("*").Name;
#>
   partial class <#= UpperInitial(rootClassName) #>
   {
      public <#= UpperInitial(rootClassName) #>(string fileName) 
      {
        XmlDocument doc = new XmlDocument();
        doc.Load(fileName);
        thisNode = doc.SelectSingleNode("<#=rootClassName#>");
      }
   }
}
<#+
   private string UpperInitial(string name)
   {
      return name[0].ToString().ToUpperInvariant() + name.Substring(1);
   }
#>

Executando o programa de teste

Principal do aplicativo de console, as seguintes linhas executará o método de teste. Pressione F5 para executar o programa no modo de depuração:

using System;
namespace MyProject
{ class Program
  { static void Main(string[] args)
    { new CodeGeneratorTest().TestMethod();
      // Allow user to see the output:
      Console.ReadLine();
} } }

Escrevendo e atualizando o aplicativo.

O aplicativo agora pode ser gravado no estilo de rigidez de tipos, usando as classes geradas em vez de usar o código XML genérico.

Quando o esquema XML é alterado, novas classes facilmente podem ser gerados. O compilador irá informar o desenvolvedor onde o código do aplicativo deve ser atualizado.

Para regenerar as classes quando o arquivo XML de exemplo é alterado, clique em Transformar todos os modelos de na barra de ferramentas do Solution Explorer.

Conclusão

Esta explicação passo a passo demonstra várias técnicas e os benefícios de geração de código:

  • Geração de código é a criação de parte do código fonte do seu aplicativo a partir de um modelo. O modelo contém informações de forma adequada para o domínio do aplicativo e pode mudar ao longo do tempo de vida do aplicativo.

  • Tipagem forte é um benefício de geração de código. Enquanto o modelo representa as informações de forma mais adequada para o usuário, o código gerado, permite que outras partes do aplicativo para lidar com as informações usando um conjunto de tipos.

  • IntelliSense e o compilador ajudam você a criar código que segue o esquema do modelo, quando você escrever código novo e quando o esquema é atualizado.

  • A adição de um arquivo de modelo único de pouco complicadas para um projeto pode fornecer esses benefícios.

  • Um modelo de texto pode ser desenvolvido e testado rapidamente e de forma incremental.

Esta explicação passo a passo, o código de programa realmente é gerado a partir de uma instância do modelo, um exemplo representativo dos arquivos XML que processará o aplicativo. Uma abordagem mais formal, o esquema XML seria a entrada para o modelo, na forma de um arquivo. xsd ou uma definição de linguagem específica de domínio. Essa abordagem tornaria mais fácil para o modelo determinar as características como, por exemplo, a multiplicidade de um relacionamento.

O modelo de texto de solução de problemas

Se você já viu os erros de compilação ou de transformação do modelo no Error List, ou se o arquivo de saída não foi gerado corretamente, você pode solucionar o modelo de texto com as técnicas descritas em Gerando arquivos com o utilitário TextTransform.

Consulte também

Conceitos

Geração de código de tempo de design usando modelos de texto T4

Escrever um modelo de texto T4