Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

T4 template and run-time parameters

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.

like image 736
Raffaeu Avatar asked Dec 09 '10 19:12

Raffaeu


4 Answers

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 ...

like image 64
Raffaeu Avatar answered Sep 24 '22 09:09

Raffaeu


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.

like image 25
GarethJ Avatar answered Sep 20 '22 09:09

GarethJ


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;
            }
        }
like image 24
Daniel Pamich Avatar answered Sep 24 '22 09:09

Daniel Pamich


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.

like image 41
JWP Avatar answered Sep 23 '22 09:09

JWP