T4 template and run-time parameters
Asked Answered
A

6

9

I am building a plug-in in VS 2010 and I get stuck at the T4 generation. Right now I have implemented (like MSDN suggests) a custom T4 host to generate my T4 results and I use it in this way:

        const string content = @"c:\Simple.tt";
        var engine = new Engine();
        var host = new MyTemplateHost();            
        var result = engine.ProcessTemplate(File.ReadAllText(content), host);
        foreach (CompilerError error in host.Errors)
        {
            Console.WriteLine(error.ErrorText);
        }

This works until I pass a parameter in the Template. As soon as I create a parameter in the .tt file, the Host freak out saying that it doesn't know how to resolve it. I saw that you can use the TemplateSession to do that but I didn't figure out how to pass it to my Host? Is there a better way of generating code from a .tt using C# and passing parameters at run-time? Maybe I am on the wrong path.

Aekerly answered 9/12, 2010 at 19:0 Comment(0)
A
13

Within Visual Studio 2010 the T4 template engine has been radically changed. Now you can run directly a template file and pass to it any parameter type you want.

        var template = Activator.CreateInstance<SimpleTemplate>();
        var session = new TextTemplatingSession();
        session["namespacename"] = "MyNamespace";
        session["classname"] = "MyClass";
        var properties = new List<CustomProperty>
        {
           new CustomProperty{ Name = "FirstProperty", ValueType = typeof(Int32) }
        };
        session["properties"] = properties;
        template.Session = session;
        template.Initialize();

This statement will process the following template:

<#@ template language="C#" debug="true"  #>
<#@ output extension=".cs" #>
<#@ assembly name="System.dll" #>
<#@ import namespace="System" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="SampleDomain.Entities" #>
<#@ parameter name="namespacename" type="System.String" #>
<#@ parameter name="classname" type="System.String" #>
<#@ parameter name="properties" type="System.Collections.Generic.List<CustomProperty>" #>

using System;
using System.Collections.Generic;
using SampleDomain.Entities;

namespace <#= this.namespacename #>
{
public class <#= this.classname #>

So honestly the host is not really needed anymore ...

Aekerly answered 10/12, 2010 at 12:36 Comment(2)
This code seems to assume using preprocesed templates (If I'm understanding your type SimpleTemplate. See my answer for how to go about this using the built-in VS host.Brey
Very helpful thanks. Also works in 2012. After calling Initialize() you could also check template.Errors.HasErrors to see if types were passed correctly :)Huffman
B
7

If you are building an add-in for VS, you probably don't need a custom host, but can instead use the built-in VS host via its service interface.

Check out ITextTemplating as the core service API, which you can get by casting your DTE object to an IServiceProvider, then calling GetService(typeof(STextTemplating))

To pass parameters, you can then sidecast the ITextTemplating object to ITextTemplatingSessionHost and set the Session property to an implementation of ITextTemplatingSession. A session is essentially just a serializable property bag. There's a trivial one provided as TextTemplatingSession.

Brey answered 13/1, 2011 at 2:52 Comment(1)
Thanks, I have to give it a shot in the office tomorrow.Aekerly
C
7

Add and implement the ITextTemplatingSessionHost to your custom host. Just implementing the ITextTemplatingEngineHost won't give you session support.

 [Serializable()]
    public class CustomCmdLineHost : ITextTemplatingEngineHost,ITextTemplatingSessionHost
    {

        ITextTemplatingSession session = new TextTemplatingSession();

        public ITextTemplatingSession CreateSession()
        {
            return session;
        }

        public ITextTemplatingSession Session
        {
            get
            {
                return session;
            }
            set
            {
                session = value;
            }
        }
Catercornered answered 11/1, 2012 at 16:37 Comment(0)
I
4

Using T4 Templates for Run Time Generation

  1. You choose this method if you need to generate code at run-time. For example you want to generate a Page Object using Selenium.

    enter image description here

  2. Create a folder in your solution, name it Templates (good name for T4 Templates).

  3. Next add a new Item, of type T4, then pick the Runtime Text Template.... We named our template MyNodeName.tt which is seen in the image above.

  4. Add your code as shown below, the top part was inserted by Visual Studio...

Add Code

You can see that we want to pass in the Namespace and ClassName (these are the Model.NameSpaceName and Model.ClassName markup seen above.

The tricky part is learning how to pass in the parameters...

Create a new CS class with the name partial in the file name.

Partial File Name

But in the class don't name it MyNodeNamePartial name it MyNodeName like this:

   public partial class MyNodeName
    {
       public MyNodeNameModel Model { get; set; }
    }

This is the same name as the TT file. (MyNodeName) which creates it's own partial class. But now notice we have a value named MODEL of this class type..

   public class MyNodeNameModel
    {
        public MyNodeNameModel()
        {
            ClassName = "Test";
        }
        public string ClassName { get; set; }
        public string NameSpaceName { get; set; }
    }

The model class holds the ClassName and NameSpaceName and anything else you want to "inject" into the template.

The key to this working as shown, is that the Runtime Text Template was used! If you use a Text Template, no matter what you do, you will see errors similar to "Model not found" or other ambiguous issues.

Debugging Tips: "The Model cannot be found" is the T4 generation code telling you that in your partial class with the variable named MODEL, that it cannot find it! Check both your partial and the model types to ensure they are in same namespace as the any other normal class namespace would be if created in that folder.

Implosive answered 12/4, 2016 at 22:6 Comment(0)
H
1

Look at MSDN Reference (Section "Passing parameters in the constructor").

To summarize:

Create a partial class with the same name of your TT File.

partial class MyWebPage
{
    private MyData m_data;
    public MyWebPage(MyData data) { this.m_data = data; }}
}

Then simply pass your parameters in the constructor of the class

MyWebPage page = new MyWebPage(data);
String pageContent = page.TransformText();
Hime answered 11/7, 2014 at 12:35 Comment(0)
G
0

I tried all the answers here to get it to work and none worked for my scenario. Eventually I added the following to my template ...

<#@ assembly name="System.CodeDom" #>

.. and then it worked

Gondi answered 4/3, 2020 at 14:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.