22

I'm working on my first T4 code generation tool to add some Stored Procedure helper code to my project. I've created custom types (e.g. StoredProcedure and StoredProcedureParameter to help with my code generation and have included the assembly and namespace references in my code:

<#@ template debug="false" hostspecific="false" language="VB" #>
<#@ output extension=".generated.vb" #>
<#@ assembly name="$(TargetPath)" #>
<#@ import namespace="StoredProcCodeGenerator" #>

This allows me to use my custom types in my T4 template code. However, because my custom types exist in the same project as the T4 template code, I can't recompile my project once I run the template code without restarting Visual Studio. This isn't very much fun.

I read a great article that addresses this exact issue by using the T4 Toolbox, but it's not working. Either I'm implementing the VolatileAssembly directive wrong or the T4 toolbox simply didn't get installed. I'm not sure that the toolbox got installed correctly (I'm using VS 2010 on Win XP).

What are some ways that I might be able to fix this problem?

Ben McCormack
  • 32,086
  • 48
  • 148
  • 223
  • I don't understand. In VS2010 I use T4 templates all the time, including using types that are in the same project as the template, and it works great and re-runs the template whenever I save -- just like I'd expect. – Kirk Woll Aug 04 '10 at 18:53
  • @Kirk I didn't realize I had to remove `<#@ assembly name="$(TargetPath)" #>` before adding `<#@ VolatileAssembly ...`. I've added an answer to explain it. – Ben McCormack Aug 04 '10 at 19:21
  • 1
    can someone edit the title to say template instead of tempate near the beginning? – Maslow Nov 10 '10 at 16:48
  • @Maslow Ha! Great catch. I've probably started at the title 50 times and never noticed that. Thanks! – Ben McCormack Nov 10 '10 at 17:56

3 Answers3

10

You need to remove the previous assembly reference and then add the VolatileAssembly reference. If you don't remove the regular assembly reference first, you'll get an error that it has already been added when you add the VolatileAssembly reference.

<#@ template debug="false" hostspecific="false" language="VB" #>
<#@ output extension=".generated.vb" #>

<#@ assembly name="$(TargetPath)" #>

<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" 
    name="$(TargetPath)" #>
<#@ import namespace="StoredProcCodeGenerator" #>  

Now you can continue to build your project and use types defined in that project within your T4 templates.

Ben McCormack
  • 32,086
  • 48
  • 148
  • 223
  • What happens when you have neither? (I have neither "assembly" nor "VolatileAssembly" and classes from the project that contains the .tt file are always accessible for me.) – Kirk Woll Aug 04 '10 at 20:46
  • @Kirk That's interesting. If I don't include an assembly reference, I'll get an error that it can't find my custom types. For example, I have a `StoreProcedure` type that I consume in my .tt file. If I don't reference my assembly, it won't be able to find the `StoredProcedure` type. – Ben McCormack Aug 04 '10 at 23:28
  • 2
    Kirk, are you using VS2008 perhaps? In 2008, we brought in the local types from the project references as stadard searches too. We removed this in 2010 so that your T4 world was isolated from what you were actually building. We did this to support targeting .Net 2.0 or 3.5 projects while using .Net 4.0 in templates. – GarethJ Nov 10 '10 at 19:25
1

Hopefully this is helpful, it shows a use case of consuming the volatileAssembly, I'm not having any luck getting this t4 template to work at all, but I think it may be helpful:

// <autogenerated/>
// Last generated <#= DateTime.Now #>
<#@ template language="C#" hostspecific="true"#>

<#@ assembly name="System" #>

<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="bin\debug\FrameworkWpf.dll" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="bin\debug\FrameworkTestToolkit.dll" #>
<#@ VolatileAssembly processor="T4Toolbox.VolatileAssemblyProcessor" name="bin\debug\WpfAppTemplate.exe" #>

<#@ output extension=".cs" #>

<#@ import namespace="System" #>
<#@ import namespace="FrameworkTestToolkit" #>

namespace WpfAppTemplateTest {
 using System;
 using System.Reflection;
<# 
    // Add new types into the below array:
    Type[] types = new Type[] { 
 typeof(FrameworkWpf.SafeEvent),
 typeof(FrameworkWpf.Mvvm.ControllerBase),
 typeof(FrameworkTestToolkit.PrivateAccessorGeneratorTestClass),
 typeof(WpfAppTemplate.PostController),
 typeof(WpfAppTemplate.ShellController),
 };


 // Do not modify this code
 foreach (Type type in types) {
 PrivateAccessorGenerator builder = new PrivateAccessorGenerator(type, WriteLine, Error, Warning);
 builder.Generate();
 }
#>
}

from http://blog.rees.biz/Home/unit-testing-and-private-accessors2

Maslow
  • 18,464
  • 20
  • 106
  • 193
1

You can also walk the code using EnvDte:

        <#@ template language="C#" hostspecific="True" debug="True" #>
    <#@ output extension="cs" #>
    <#@ assembly name="System.Core" #>
    <#@ assembly name="System.Xml" #>
    <#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #>
    <#@ assembly name="EnvDTE" #>
    <#@ assembly name="EnvDTE80" #>
    <#@ assembly name="VSLangProj" #>
    <#@ import namespace="System.Collections.Generic" #>
    <#@ import namespace="System.IO" #>
    <#@ import namespace="System.Linq" #>
    <#@ import namespace="System.Text" #>
    <#@ import namespace="System.Text.RegularExpressions" #>
    <#@ import namespace="System.Xml" #>
    <#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
    <#@ import namespace="EnvDTE" #>
    <#@ import namespace="EnvDTE80" #>
    <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #><#
    var serviceProvider = Host as IServiceProvider;
        if (serviceProvider != null) {
            Dte = serviceProvider.GetService(typeof(SDTE)) as DTE;
        }

        // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe
        if (Dte == null) {
            throw new Exception("T4Generator can only execute through the Visual Studio host");
        }

        Project = GetProjectContainingT4File(Dte);

        if (Project == null) {
            Error("Could not find the VS Project containing the T4 file.");
            return"XX";
        }

        AppRoot = Path.GetDirectoryName(Project.FullName) + '\\';
        RootNamespace = Project.Properties.Item("RootNamespace").Value.ToString();

        Console.WriteLine("Starting processing");
        ProcessFileCodeModel(Project);
    #>

I've posted even more code using this as a basis at http://imaginarydevelopment.blogspot.com/2010/11/static-reflection-or-t4-with-envdte.html

Maslow
  • 18,464
  • 20
  • 106
  • 193