Acessar modelos por meio de modelos de texto
Usando modelos de texto, você pode criar arquivos de relatório, arquivos de código-fonte e outros arquivos de texto baseados em modelos de linguagem específica de domínio. Para obter informações básicas sobre modelos de texto, confira Geração de código e modelos de texto T4. Os modelos de texto funcionarão no modo experimental quando você estiver depurando sua DSL e também funcionarão em um computador no qual você implantou a DSL.
Observação
Quando você cria uma solução DSL, os arquivos *.tt de modelo de texto de exemplo são gerados no projeto de depuração. Quando você alterar os nomes das classes de domínio, esses modelos deixarão de funcionarão. No entanto, eles incluem as diretivas básicas necessárias e fornecem exemplos que você pode atualizar para corresponder a sua DSL.
Para acessar um modelo de um modelo de texto:
Defina a propriedade inherit da diretiva de modelo como Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation. Isso fornece acesso à Loja.
Especifique processadores de diretiva para a DSL que você deseja acessar. Isso carrega os assemblies da sua DSL para que você possa usar suas classes de domínio, propriedades e relações no código do modelo de texto. Também carrega o arquivo de modelo que você especificar.
Um arquivo
.tt
semelhante ao exemplo a seguir é criado no projeto de Depuração quando você cria uma nova solução do Visual Studio com base no modelo linguagem mínima do DSL.
<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
<#@ output extension=".txt" #>
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1'" #>
This text will be output directly.
This is the name of the model: <#= this.ModelRoot.Name #>
Here is a list of elements in the model:
<#
// When you change the DSL Definition, some of the code below may not work.
foreach (ExampleElement element in this.ExampleModel.Elements)
{#>
<#= element.Name #>
<#
}
#>
Observe os seguintes pontos sobre esse modelo:
O modelo pode usar as classes de domínio, as propriedades e as relações que você definiu na Definição de DSL.
O modelo carrega o arquivo de modelo especificado na propriedade
requires
.Uma propriedade em
this
contém o elemento raiz. A partir daí, seu código pode navegar até outros elementos do modelo. O nome da propriedade costuma ser o mesmo da classe de domínio raiz do DSL. Neste exemplo, éthis.ExampleModel
.Embora a linguagem com a qual os fragmentos de código foram escritos seja C#, você poderá gerar texto de qualquer tipo. Como alternativa, escreva o código em Visual Basic adicionando a propriedade
language="VB"
à diretivatemplate
.Para depurar o modelo, adicione
debug="true"
à diretivatemplate
. Se ocorrer uma exceção, o modelo será aberto em outra instância do Visual Studio. Se você quiser entrar no depurador em um ponto específico do código, insira a instruçãoSystem.Diagnostics.Debugger.Break();
Para saber mais, confira Como depurar um modelo de texto T4.
Sobre o processador de diretiva de DSL
O modelo pode usar as classes de domínio definidas em sua Definição de DSL. Isso ocorre graças a uma diretiva que geralmente aparece perto do início do modelo. No exemplo anterior, será a seguinte.
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1'" #>
O nome da diretiva (MyLanguage
, neste exemplo) é derivado do nome do DSL. Ela invoca um processador de diretiva que é gerado como parte do DSL. Você pode encontrar seu código-fonte em Dsl\GeneratedCode\DirectiveProcessor.cs.
O processador de diretiva de DSL executa duas tarefas principais:
Insere efetivamente diretivas de assembly e de importação no modelo que faz referência ao DSL. Assim, você pode usar suas classes de domínio no código do modelo.
Carrega o arquivo especificado no parâmetro
requires
e define uma propriedade emthis
, que se refere ao elemento raiz do modelo carregado.
Validação do modelo antes de executar o modelo
Você pode fazer com que o modelo seja validado antes da execução do modelo.
<#@ MyLanguage processor="MyLanguageDirectiveProcessor" requires="fileName='Sample.myDsl1';validation='open|load|save|menu'" #>
Observe que:
Os parâmetros
filename
evalidation
são separados por ";" e não deve haver outros separadores ou espaços.A lista de categorias de validação determina quais métodos de validação serão executados. As categorias devem ser separadas com "|" e não deve haver outros separadores ou espaços.
Se um erro for encontrado, ele será relatado na janela de erros e o arquivo de resultado conterá uma mensagem de erro.
Acesso a vários modelos de um modelo de texto
Observação
Esse método permite que você leia vários modelos no mesmo modelo, mas não dá suporte a referências de ModelBus. Para ler modelos interligados por Referências de ModelBus, confira Como usar o Visual Studio ModelBus em um modelo de texto.
Se você quiser acessar mais de um modelo do mesmo modelo de texto, deverá chamar o processador de diretiva gerado uma vez para cada modelo. Você deve especificar o nome de arquivo de cada modelo no parâmetro requires
. Você deve especificar os nomes que deseja usar para a classe de domínio raiz no parâmetro provides
. Você deve especificar valores diferentes para os parâmetros provides
em cada uma das chamadas da diretiva. Por exemplo, vamos supor que você tenha três arquivos de modelo chamados Library.xyz, School.xyz e Work.xyz. Para acessá-las do mesmo modelo de texto, você deve escrever três chamadas de diretiva semelhantes às seguintes.
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='Library.xyz'" provides="ExampleModel=LibraryModel" #>
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='School.xyz'" provides="ExampleModel=SchoolModel" #>
<#@ ExampleModel processor="<YourLanguageName>DirectiveProcessor" requires="fileName='Work.xyz'" provides="ExampleModel=WorkModel" #>
Observação
Esse exemplo de código é para um idioma baseado no modelo de solução linguagem mínima.
Para acessar os modelos em seu modelo de texto, agora você pode escrever código semelhante ao código no exemplo a seguir.
<#
foreach (ExampleElement element in this.LibraryModel.Elements)
...
foreach (ExampleElement element in this.SchoolModel.Elements)
...
foreach (ExampleElement element in this.WorkModel.Elements)
...
#>
Carregamento dinâmico de modelos
Se você quiser determinar, em tempo de execução, quais modelos carregar, carregue um arquivo de modelo dinamicamente no código do programa, em vez de usar a diretiva específica ao DSL.
No entanto, uma das funções da diretiva específica ao DSL é importar o namespace de DSL, para que o código de modelo possa usar as classes de domínio definidas nesse DSL. Como você não está usando a diretiva, deve adicionar diretivas de <assembly> e <importação> para todos os modelos que você possa carregar. Isso será fácil se os diferentes modelos que você pode carregar forem todas as instâncias do mesmo DSL.
Para carregar o arquivo, o método mais eficaz é o uso do Visual Studio ModelBus. Em um cenário comum, seu modelo de texto usará uma diretiva específica ao DSL para carregar o primeiro modelo da maneira usual. Esse modelo conteria Referências de ModelBus para outro modelo. Você pode usar o ModelBus para abrir o modelo referenciado e acessar um elemento específico. Para saber mais, confira Como usar o Visual Studio ModelBus em um modelo de texto.
Em um cenário menos comum, convém abrir um arquivo de modelo para o qual você tem apenas um nome de arquivo e que pode não estar no projeto atual do Visual Studio. Nesse caso, você pode abrir o arquivo usando a técnica descrita em Como abrir um modelo do arquivo no código do programa.
Geração de vários arquivos a partir de um modelo
Se você quiser gerar vários arquivos, por exemplo, para gerar um arquivo separado para cada elemento em um modelo, contará com várias abordagens possíveis. Por padrão, apenas um arquivo é produzido de cada arquivo de modelo.
Divisão de um arquivo longo
Nesse método, você usa um modelo para gerar um único arquivo, separado por um delimitador. Em seguida, você divide o arquivo em suas partes. Há dois modelos, um para gerar o único arquivo e o outro para dividi-lo.
LoopTemplate.t4 gera o arquivo único longo. Observe que a extensão de arquivo é ".t4", pois não deve ser processada diretamente quando você clica em Transformar Todos os Modelos. Esse modelo usa um parâmetro, que especifica a cadeia delimitadora que separa os segmentos:
<#@ template inherits="Microsoft.VisualStudio.TextTemplating.VSHost.ModelingTextTransformation" #>
<#@ parameter name="delimiter" type="System.String" #>
<#@ output extension=".txt" #>
<#@ MyDSL processor="MyDSLDirectiveProcessor" requires="fileName='SampleModel.mydsl1';validation='open|load|save|menu'" #>
<#
// Create a file segment for each element:
foreach (ExampleElement element in this.ExampleModel.Elements)
{
// First item is the delimiter:
#>
<#= string.Format(delimiter, element.Id) #>
Element: <#= element.Name #>
<#
// Here you generate more content derived from the element.
}
#>
LoopSplitter.tt
invoca LoopTemplate.t4
e divide o arquivo resultante em segmentos. Observe que esse modelo não precisa ser um modelo de modelagem, pois não lê o modelo.
<#@ template hostspecific="true" language="C#" #>
<#@ output extension=".txt" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="System.Runtime.Remoting.Messaging" #>
<#@ import namespace="System.IO" #>
<#
// Get the local path:
string itemTemplatePath = this.Host.ResolvePath("LoopTemplate.t4");
string dir = Path.GetDirectoryName(itemTemplatePath);
// Get the template for generating each file:
string loopTemplate = File.ReadAllText(itemTemplatePath);
Engine engine = new Engine();
// Pass parameter to new template:
string delimiterGuid = Guid.NewGuid().ToString();
string delimiter = "::::" + delimiterGuid + ":::";
CallContext.LogicalSetData("delimiter", delimiter + "{0}:::");
string joinedFiles = engine.ProcessTemplate(loopTemplate, this.Host);
string [] separateFiles = joinedFiles.Split(new string [] {delimiter}, StringSplitOptions.None);
foreach (string nameAndFile in separateFiles)
{
if (string.IsNullOrWhiteSpace(nameAndFile)) continue;
string[] parts = nameAndFile.Split(new string[]{":::"}, 2, StringSplitOptions.None);
if (parts.Length < 2) continue;
#>
Generate: [<#= dir #>] [<#= parts[0] #>]
<#
// Generate a file from this item:
File.WriteAllText(Path.Combine(dir, parts[0] + ".txt"), parts[1]);
}
#>