Tikhon
Tikhon

Reputation: 1024

Pass an Object to a T4 Text Template

I have a T4 Template that I am trying to pass object values to at runtime.

Basically what we're trying to to is:

  1. From a Windows .NET form, read a file as text

  2. Set an external object property to the text value

  3. Access that object property in a T4 text template that has an output extension of .java.

I am starting very simple for now where I just have the template and the form and say an external class object:

Flow

Of course reading the text in the the form part and setting an object property like foo.foocode is fairly straightforward.

I just can't figure out how to access that object variable or property in the template and i've been looking at this for over a day..

Thanks

Upvotes: 6

Views: 9743

Answers (2)

Samuel
Samuel

Reputation: 6500

Very old post but I keep forgetting how I want to do this in a bit more elegant way, so I post my solution here. This is simply another option, it is not designed to be better or faster.

For preprocessed <Name>.tt files, the T4 engine creates an underlying <Name>.cs and inside there is a class called <Name>. So I wanted to have that flow of doing things, as I am working with simple objects and don't want to complicate things.

This is my main method

public static void Main(string[] args)
{
    var outputFile = @"index.html";

    var r = new Report();

    // Here I pass whatever my "context" is, to the template
    r.Values = new List<string> { "hello", "friendo" };

    File.WriteAllText(outputFile, r.TransformText());

}

So this is very simple and all I need is to figure out, how I can make r.Values work, because this property/field is not available per default.

Luckily the solution is also simple. Here is my Report.tt that creates a Report.cs, which contains a Report class:

<#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>


<!DOCTYPE html>
<html>
<head>
    <title>Report</title>

    </head>
    <body>
        <h1>Report</h1>
        <table>
            <tr>
                <th>Variant</th>
               
            </tr>
            <# foreach (var variant in Values) { #>
                <tr>
                    <td><#= variant #></td>
                   
                </tr>
            <# } #>
        </table>
    </body>
 </html>

 <#+
#nullable enable

    public List<string> Values { get; set; } = null!;

#nullable disable
#>

All I did was sneaking some C# code into the class generation. The important part is at the bottom, I added a property called Values.

There can be any amount of properties and any available type can be used here. At the end, it is simply C# code.

This way I was able to pass fairly complex objects into the generation and they did not have to be serializable. And since I want the transformation during compile time anyway, this allows me to do it very straightforward.

Hope that helps future me and others 😁

Upvotes: 0

Nico
Nico

Reputation: 2110

At runtime you can only transform preprocessed templates, because the templating engine is not a redistributable part of Visual Studio. You can pass objects to a preprocessed templates using the parameter directive. The object type you pass to the template must be decorated with the SerializableAttribute. Before calling the TransformText() method put the value of the parameter into the templating session.

The output extension directive is ignored when using a preprocessed template. The TransformText() method returns a string with the generated code. You can save it in whatever file type you want.

<#@ template debug="true" #>
<#@ parameter name="MyObject" type="MyNamespace.MyType" #>

<#
  // now access the passed parameter using
  this.MyObject
#>

Call the preprocessedTemplate:

var templateInstance = new MyTemplate();
templateInstance.Session = new Dictionary<string, object>();
templateInstance.Session.Add("MyObject", new MyType());
templateInstance.Initialize();

var generatedCode = templateInstance.TransformText();

System.IO.File.WriteAllText("outputfile.java", generatedCode);

Hope this helps.

Upvotes: 14

Related Questions