Passo a passo: Geração de código usando 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 tal 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 o arquivo XML.Você também pode executar o 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 que o seu aplicativo para lidar com.
Observação |
---|
O aplicativo xsd. exe, que está incluído no Visual Studio, pode gerar classes com rigidez de tipos de 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 sem tipo 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 as partes do código do aplicativo que deve ser alterado.Na versão sem tipo 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
Sobre o arquivo menu do botão New e, em seguida, clique em projeto.
Clique no Visual C# nó e depois no modelos de 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 ele não será compilado no aplicativo compilado.
Para adicionar um arquivo XML
Em Solution Explorer, o botão direito do mouse no projeto, clique em Add e, em seguida, clique em Novo Item.
No Add New Item caixa de diálogo, selecione Arquivo XML da modelos de painel.
Adicione o conteúdo de amostra para o arquivo.
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);
} } } } }
Nesse 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 o conteúdo conhecido do arquivo XML de exemplo.Mas nesta explicação, ficaremos satisfeitos quando o método de teste é compilado.
Adicionar um arquivo de modelo de texto
Adicione 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
Em Solution Explorer, o botão direito do mouse no projeto, clique em Adde, em seguida, clique em Novo Item.
No Add New Item select da caixa de diálogo Modelo de texto da modelos de painel.
Observação Certifique-se de que você adiciona um modelo de texto e não um pré-processado modelo de texto.
No arquivo, na diretriz de modelo, alterar o hostspecific para o atributo true.
Esta alteração permitirá que o código do modelo acessar o Visual Studio services.
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ê altere-a "VB".
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 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ê possa vê-los lado a lado.Isso lhe permite ver imediatamente a saída do seu modelo.Você também observará 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ê execute 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 pequeno fragmento do código no arquivo gerado, onde IntelliSense é em operação, e, 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 desaparecem 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 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 <#...#>.Um fragmento do código do programa que gera o texto de colchetes desses delimitadores.Os delimitadores de bloco de expressão <#=...#> uma expressão que pode ser avaliada como uma seqüência de caracteres de colchetes.
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 dentro os delimitadores de bloco de código é executado 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 se comporta como uma referência, disponibilizando o assembly 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 funciona como um using instrução, permitindo que você use os nomes curtos de classes no namespace importado.
Infelizmente, embora este modelo gera código, ele produz uma declaração de classe de todos os nós no arquivo XML de exemplo, para que se houver várias instâncias da <song> nó, diversas declarações da música classe aparecerá.
Ler o arquivo de modelo e, 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, em seguida, gerar as declarações de classe.Outro <#@import#> é necessária para que podemos usarDictionary<>:
<#@ 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 no 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. 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 do 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);
}
#>
Executar 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 com rigidez de tipos, usando as classes geradas em vez de usar o código XML genérico.
Quando o esquema XML for alterado, novas classes podem ser gerados com facilidade.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 podem ser alterados durante a vida útil do aplicativo.
Tipagem forte é uma das vantagens de geração de código.Enquanto o modelo representa 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ê escreve um novo código 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, na verdade, é gerado a partir de uma instância do modelo, um exemplo representativo dos arquivos XML que processará o aplicativo.Em 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 a multiplicidade de um relacionamento.
O modelo de texto de solução de problemas
Se você já viu os erros de compilação ou transformação de 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