Can't call tt from within Visual Studio 2012 Addin extension using latest beta

Feb 4, 2013 at 7:37 PM
In VS2010 I was able to invoke my T4 tt's from within a Visual Studio Addin using the following technique:
        TextReader tr = new StreamReader(ttFile);
        string content = tr.ReadToEnd();
        IServiceProvider serviceProvider = new ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)m_applicationObject.DTE);
        ITextTemplating tt = serviceProvider.GetService(typeof(STextTemplating)) as ITextTemplating;
        tt.ProcessTemplate(ttFile, content, (this as ITextTemplatingCallback), null);
I've moved to VS2012 and upgraded to the T4 3.0.8 beta, but calling ProcessTemplate now throws the Exception below. Any ideas? Thanks

System.InvalidCastException: Unable to cast object of type 'TMS.Tools.EntityModelGenerationAddIn.Connect' to type 'Microsoft.VisualStudio.TextTemplating.VSHost.TextTemplatingCallback'.

Server stack trace:
at T4Toolbox.VisualStudio.OutputFileManager.ValidateOutputEncoding(OutputFile output)
at T4Toolbox.VisualStudio.OutputFileManager.Validate()
at T4Toolbox.VisualStudio.TransformationContextProvider.T4Toolbox.ITransformationContextProvider.UpdateOutputFiles(String inputFile, OutputFile[] outputFiles)
at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg)

Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at T4Toolbox.ITransformationContextProvider.UpdateOutputFiles(String inputFile, OutputFile[] outputFiles)
at T4Toolbox.TransformationContext.Dispose(Boolean disposing)
at T4Toolbox.TransformationContext.Dispose()
at T4Toolbox.TransformationContext.Cleanup()
at Microsoft.VisualStudio.TextTemplatingBBC5941AFF35CB607B57D4FBA03DCD978AADFDDA89E562E9DF49429CDF6B7C038A1171C9E0AC04F7C7B0652CFCA988A67A0F06124756B10A734707EBF798AC79.GeneratedTextTransformation.Dispose(Boolean disposing)
at Microsoft.VisualStudio.TextTemplating.TextTransformation.Dispose()
at Microsoft.VisualStudio.TextTemplating.TransformationRunner.PerformTransformation()
at System.Runtime.Remoting.Messaging.StackBuilderSink._PrivateProcessMessage(IntPtr md, Object[] args, Object server, Object[]& outArgs)
at System.Runtime.Remoting.Messaging.StackBuilderSink.SyncProcessMessage(IMessage msg)

Exception rethrown at [1]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at Microsoft.VisualStudio.TextTemplating.IDebugTransformationRun.PerformTransformation()
at Microsoft.VisualStudio.TextTemplating.Engine.ProcessTemplateImplementation(TemplateProcessingSession session, String content, ITextTemplatingEngineHost host, String className, String classNamespace)
Coordinator
Feb 6, 2013 at 1:55 AM
Hmm... I haven't thought of this scenario. I will change it in the next update to skip output encoding verification if ITextTemplatingComponents.Callback cannot be downcast to TextTemplatingCallback. Try passing an instance of Microsoft.VisualStudio.TextTemplating.VSHost.TextTemplatingCallback to the ITextTemplating.ProcessTemplate. This object is also used to determine extension of the default output file of the transformation and trigger saving of additional output files. Do you expect this "nested" template to save multiple output files?
Feb 6, 2013 at 3:15 AM
Yes, my tt does save multiple output files. The extension is specified in the tt by <#@ output extension="generated.cs"#> and the output file names are specified in the tt by calling RenderToFile(entity.Name + ".generated.cs");

I'll try creating a TextTemplatingCallback and passing that in, but not sure how it will help as all I'll be able to do is implement the interface ITextTemplatingCallback on it right? Which is in essense what I'm doing by implementing that interface on my Addin... Do you call any other methods/properties (outside of ITextTemplatingCallback) on the TextTemplatingCallback or by doing this we'll just avoid the cast problem and everything else will work as expected?

Thanks
Feb 6, 2013 at 4:15 AM
LightningStrikes wrote:
Yes, my tt does save multiple output files. The extension is specified in the tt by <#@ output extension="generated.cs"#> and the output file names are specified in the tt by calling RenderToFile(entity.Name + ".generated.cs");

I'll try creating a TextTemplatingCallback and passing that in, but not sure how it will help as all I'll be able to do is implement the interface ITextTemplatingCallback on it right? Which is in essense what I'm doing by implementing that interface on my Addin... Do you call any other methods/properties (outside of ITextTemplatingCallback) on the TextTemplatingCallback or by doing this we'll just avoid the cast problem and everything else will work as expected?

Thanks
I tried passing in a TextTemplateCallback object. I no longer get the cast exception, but get no output... In fact I don't even see the SetFileExtension or SetOutputEncoding getting called (which I did see when passing in the interface). Not sure if I'm missing something but I simply implemented as below:
public class MyTextTemplatingCallback : TextTemplatingCallback
{
    private string m_extension;
    private Encoding m_encoding;

    public string Extension 
    {
        get { return m_extension; }
        set { m_extension = value; }
    }

    public Encoding OutputEncoding 
    {
        get { return m_encoding; }
        set { m_encoding = value; } 
    }

    public bool Errors { get; set; } // TODO

    public void ErrorCallback(bool warning, string message, int line, int column)
    {
        // TODO
    }

    public void SetFileExtension(string extension)
    {
        m_extension = extension;
    }

    public void SetOutputEncoding(Encoding encoding, bool fromOutputDirective)
    {
        m_encoding = encoding;
    }
}

        TextReader tr = new StreamReader(ttGenerationFile);
        string content = tr.ReadToEnd();
        IServiceProvider serviceProvider = new ServiceProvider((Microsoft.VisualStudio.OLE.Interop.IServiceProvider)m_applicationObject.DTE);
        ITextTemplating tt = serviceProvider.GetService(typeof(STextTemplating)) as ITextTemplating;
        //tt.ProcessTemplate(ttGenerationFile, content, (this as ITextTemplatingCallback), null);
        MyTextTemplatingCallback myTextTemplatingCallback = new MyTextTemplatingCallback();
        tt.ProcessTemplate(ttGenerationFile, content, myTextTemplatingCallback, null);     
Feb 6, 2013 at 9:19 PM
Hi Oleg,

I updated to 11.2 and tried using the interface technique again. I no longer get the cast exception (which is good), but still not quite there... The exception returned now is...

Errors were generated when initializing the transformation object. The transformation will not be run. The following Exception was thrown:
System.NullReferenceException: Object reference not set to an instance of an object.
at T4Toolbox.TransformationContext.InitializeParameters()
at T4Toolbox.TransformationContext..ctor(TextTransformation transformation, StringBuilder generationEnvironment)
at T4Toolbox.TransformationContext.Initialize(TextTransformation transformation, StringBuilder generationEnvironment)
at Microsoft.VisualStudio.TextTemplating74B999907D82DDBE31F80E57C80D176D6377DA080B8B5FF406CD04D90A06C4BE1F865671A93EE84B71F09944C3C260A5C29F4D1CD73A1E7ECA877E21CB6720BF.GeneratedTextTransformation.Initialize()
at CallSite.Target(Closure , CallSite , Object )
at System.Dynamic.UpdateDelegates.UpdateAndExecuteVoid1[T0](CallSite site, T0 arg0)
at Microsoft.VisualStudio.TextTemplating.TransformationRunner.PerformTransformation()
Coordinator
Feb 7, 2013 at 3:50 AM
How about this?
var textTemplating = (ITextTemplating)serviceProvider.GetService(typeof(STextTemplating));
var templatingComponents = (ITextTemplatingComponents)textTemplating;
string templateFileName = "TextTemplate2.tt";
string templateContent = File.ReadAllText(templateFileName);
textTemplating.ProcessTemplate(templateFileName, templateContent, templatingComponents.Callback, templatingComponents.Hierarchy);
I'm sorry I keep throwing things to try at you. As I mentioned earlier, I haven't had a chance to think through this scenario yet.
Feb 7, 2013 at 3:41 PM
Gave it a try. That doesn't work either. Looking in the debugger, after casting to ITextTemplatingComponents, both templatingComponents.Callback and templatingComponents.Hierarchy are null...

No worries about throwing things at me to try...I'm anxious to get this working so we can upgrade to VS2012 this month. Maybe we can attack it from another angle. This same Addin works in VS 2010, with the previous version of T4 Toolbox. Is there additional information your expecting now that wasn't in the previous version? Are there now calls being made off of TextTemplatingComponents (I could implement that interface in addition to ITextTemplating)? Other ideas?

Thanks
Coordinator
Feb 8, 2013 at 2:30 AM
In VS2012, T4Toolbox relies on the ITextTemplatingComponents interface implemented by the STextTemplating service to implement additional validation and new functionality (initialization of template parameters from project item metadata). In particular, it uses the ITextTemplatingComponents.Hierarchy property to get a IVsHierarchy object representing the project to which the input/template file belongs. It also uses the ITextTemplatingComponents.Callback property to get a TextTemplatingComponents object, from which it needs the Extension property to determine the name of the default output file generated by the TextTemplatingFileGenerator custom tool. However, the biggest challenge for your add-in, is that in VS2012 T4Toolbox assumes that transformation is done by a custom tool and not by an extension. It generates additional output files only after the default transformation output file is saved to disk by the custom tool.

Can you tell me more about your add-on? Why is the TextTemplatingFileGenerator custom tool insufficient for your scenario? Does the text template you transform belong to a project? Do you expect it to generate additional outputs under this template? What do you do with the default output returned by ITextTemplating.ProcessTemplate?
Feb 8, 2013 at 2:03 PM
We have approximately 8 entity models as part of our project (using Entity Framework). In the project we have separate tt's that generate the entity models from the database, the ObjectContext's, and the POCO objects, so 24 individual tt's that perform the code generation. Instead of developers needing to right click on each tt to perform the code generation and worry about keepinbg them in sync, I developed an AddIn which simply prompts for what models need to be generated, and the AddIn simply executes running the correct tt's. This greatly simplifies our code generation process, limits development mistakes, and allows it to be interactive for the developer while they are writing code and testing.

So yes the text templates are part of the project, the code that they generate is in files under the template, and we ignore the output returned from the ProcessTemplate call, as the tt's generate the output in the project (with the appropriate TFS checkins and adds).

Does this make sense or do I need more details?
Coordinator
Feb 10, 2013 at 5:49 PM
For this specific scenario, I think the best way to upgrade your add-in is to call VSProjectItem.RunCustomTool method. This will transform your templates through the built-in implementation of the TextTemplatingFileGenerator and we won't have to figure out how to make the low-level ITextTemplating.ProcessTemplate work. You can obtain a VSProjectItem object from ProjectItem.Object property. Let me know if you need additional information.
Feb 10, 2013 at 6:59 PM
Thanks Oleg this works. It does have a side effect though, where an empty output file is created for each tt run. Is there a way to suppress that?
Coordinator
Feb 10, 2013 at 8:58 PM
Glad to hear. Unfortunately, there is no clean way to suppress the default transformation output. You could use a workaround described at the end of this thread: http://stackoverflow.com/questions/2601202/how-not-to-output-default-t4-generated-file/13878689#comment19263302_13878689. My recommendation would be to find some useful content for the default output file, such as the ObjectContext descendant. With T4 Toolbox, Template will append its output to the default transformation output if you leave the Template.Output.File property empty.