VS2022 17.2.0 Preview 2.0: T4 template serialization exception when accessing projects, etc - t4

Using VS2022 17.2.0 Preview 2.0 to generate data layer using T4 templates.
Part of the T4 uses VS interop / DTE to access projects in solution.
The following T4 is a test:
<## template debug="false" hostspecific="true" language="C#" #>
<## assembly name="Microsoft.VisualStudio.Shell.Interop"#>
<## import namespace="Microsoft.VisualStudio.Shell"#>
<## import namespace="Microsoft.VisualStudio.Shell.Interop"#>
<## import namespace="EnvDTE" #>
<## import namespace="EnvDTE80" #>
<## output extension=".txt" #>
<#
var hostServiceProvider = Host as IServiceProvider;
var dte = hostServiceProvider.GetService(typeof(DTE)) as DTE2;
foreach (Project project in dte.Solution)
{
#><#= project.Name #>
<#
}
#>
This produces following exception:
Error Running transformation: System.Runtime.Serialization.SerializationException: Type 'Microsoft.VisualStudio.CommonIDE.Solutions.CMiscProject' in Assembly 'Microsoft.VisualStudio.CommonIDE, Version=17.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' is not marked as serializable.
Issue did not exist in Preview 1.0 or in VS2019.
I have had a look around and pulled in nuget package for Microsoft.VisualStudio.Interop, version 17.1.32210.191, but problem persists when accessing anything through the EnvDTE.DTE(2).
I know I'm jumping the gun on this as it is a preview version, but has anyone had this issue and solved it? Is there a different approach needed to access projects in the solution from the T4 template?
The error does not occur when debugging the T4 template.

I had a bit of a play (and a lot of a Google) and found the following solved it for me under VS 2022:
Ensure you have the following assemblies and namespaces
<## assembly name="Microsoft.VisualStudio.Interop" #>
<## import namespace="EnvDTE" #>
<## import namespace="EnvDTE80" #>
<## import namespace="Microsoft.VisualStudio.TextTemplating" #>
and then swap out the IServiceProvider's GetService for GetCOMService
//var dte = hostServiceProvider.GetService(typeof(DTE)) as DTE2;
var dte = hostServiceProvider.GetCOMService(typeof(DTE)) as DTE2;
Mostly from answer here: https://stackoverflow.com/a/53346767/2797450

Related

Cannot debug T4 template in VS2017

In VS2017 Community, I cannot debug T4 Templates, which works in 2015.
I have a very basic template, such as this...
<## template debug="true" hostspecific="false" language="C#" #>
<## output extension=".txt" #>
<#
var a = "Hello";
var b = "World";
#>
<#=($"{a} {b}!")#>
Run Custom Tool and Transform All T4 Templates both options work, and text file contains expected output
Hello World!
If I put breakpoint somewhere and use Debug T4 Template from the context menu of .tt, it throws this error
Unable to start transformation run creation process.
However it works fine in VS 2015, and I'm able to debug there.
What I could be missing? how to debug T4 Templates in VS 2017? Note that I don't have any Tool/ Extension installed in VS2015 to debug T4
I have had the same issue, I don't know why it doesn't work this way but I have a work around.
Set debug to true, and add the diagnostic namespace
<## template language="C#" debug="true" #>
<## import namespace="System.Diagnostics" #>
In your T4 template write
Debugger.Launch();
Then run your template (easiest way it just to save it) and it will ask if you would like to debug in a new instance of visual studio.
The easiest solution is to just add these two lines to the top of your T4 template.
<## template debug="true" hostspecific="false" language="C#" #>
<# System.Diagnostics.Debugger.Launch(); #>
Then just run the template by saving the file and visual studio will prompt you to debug in a new instance.
If you use Host in your template and you get the error The name 'Host' does not exist in the current context then set `hostspecific="true"'.

EnvDTE not found in VS2012 works in VS2010

I'm using EnvDTE to do some code generation within my T4 Templates.
I have the code working correctly in Visual Studio 2010, however I've just started using Visual Studio 2012 and now when I try to run my templates I get the following error
Compiling transformation: Metadata file 'EnvDTE.dll' could not be found
I don't actually have a reference to EnvDTE in my project as its a Silverlight class library and I wasn't able to add the DLL, however it finds the DLL somehow.
I'm not sure what is difference is between 10 and 12 to cause this.
The following are my imports and assembly definitions from the start of my ttinclude file.
<## template debug="true" hostSpecific="true" #>
<## output extension=".generated.cs" #>
<## Assembly Name="EnvDTE.dll" #>
<## Assembly Name="System.Data" #>
<## import namespace="EnvDTE" #>
<## import namespace="System.Data" #>
<## import namespace="System.Data.SqlClient" #>
<## import namespace="System.IO" #>
<## import namespace="System.Text.RegularExpressions" #>
Is there anything I have to do differently to get it working for Visual Studio 2012
It appears that VS12 can't figure out where EnvDTE is. Its odd that (as you mentioned in a comment) fusion didn't pick that up. Perhaps it did, but you weren't reading it correctly?
As an aside, when the fusion log lets you down, its time to break out Process Monitor when you can't figure out why an application can't find something that should be there.
You can give a full path for assembly references in T4 templates. In your case, it would be
<## Assembly Name="C:\Program Files (x86)\Common Files\microsoft shared\MSEnv\PublicAssemblies\envdte.dll" #>
(assuming you have EnvDTE in the correct spot). I wouldn't consider this a true solution, and would open a Connect issue with MS about this. Seems like a bug.
After stumbling about the same error i searched a little deeper and found this Microsoft Connect entry.
To fix the problem simply remove the .dll from the assembly name and it works as expected:
<## Assembly Name="EnvDTE" #>
Also ensure that the EnvDTE assembly is located within the GAC under C:\Windows\assembly. This will normally automaticaly happen when you install Visual Studio on a machine.
Example
Here is an example that should work out of the box:
<## template language="C#" debug="true" hostSpecific="true" #>
<## output extension=".txt" #>
<## Assembly Name="System.Core" #>
<## Assembly Name="System.Design" #>
<## Assembly Name="System.Drawing" #>
<## Assembly Name="System.Windows.Forms" #>
<## Assembly Name="EnvDTE" #>
<## import namespace="System" #>
<## import namespace="System.CodeDom.Compiler" #>
<## import namespace="System.Collections.Generic" #>
<## import namespace="System.Drawing" #>
<## import namespace="System.IO" #>
<## import namespace="System.Linq" #>
<## import namespace="System.Resources" #>
<## import namespace="System.Resources.Tools" #>
<## import namespace="EnvDTE" #>
<## import namespace="Microsoft.CSharp" #>
All projects currently available within this solution:
<#
//System.Diagnostics.Debugger.Launch();
EnvDTE.DTE dte = (EnvDTE.DTE)((IServiceProvider)this.Host)
.GetService(typeof(EnvDTE.DTE));
EnvDTE.Projects projects = dte.Solution.Projects;
foreach (EnvDTE.Project project in projects)
{
#>
<#= project.Name #>
<#
}
#>
This file was generated at: <#= System.DateTime.Now.ToShortDateString() #> <#= DateTime.Now.ToLongTimeString() #>
I was facing the issue related to EnvDTE80 on my visual studio 2019 while loading an application.
Error displayed the following message:
"Reference.svcmap: Could not load file or assembly "'EnvDTE," Version=8.0.0.0, Culture=neutral..."
I cleaned the solution and installed the nuget package for version 8.0.0.0. Then rebuilt the solution. In that way my visual studio was able to load the application.

t4 template linq issue

How can I use Linq in a T4 template
This is my software environment information
vs2012
.net version 4.0
This is t4 template:
<## templatedebug="true" hostSpecific="true" #>
<## output extension=".cs" #>
<## Assembly Name="System.Core.dll" #>
<## import namespace="System" #>
<## import namespace="System.Linq" #>
When I call engine.ProcessTemplate(inputTemplate, host), it returns the contents of
ErrorGeneratingOutput. Why is that?
Old question I know, but I've just found the same thing.
When you reference System.Core, don't include .dll:
<## assembly name="System.Core" #>
You need to look at the error in the errors window of visual studio to see more information.
Also you can right click the .tt file and say debug template.

Can Conditional compilation symbols be used within T4 templates

I have a T4 template that is used with the TextTemplatingFilePreprocessor to generate a class that I can then use to generate the output of the template.
At the start of the T4 template I import several namespaces. E.g.
<## import namespace="Company.ProductX.Widgets" #>
<## import namespace="Company.ProductX.Services" #>
//...
I'd like to use Preprocessor Directives to switch out these imports with another set of namespaces (which provide the same interfaces but differing functionality to ProductX). E.g.
<#
#if(ProductX)
{
#>
<## import namespace="Company.ProductX.Widgets" #>
<## import namespace="Company.ProductX.Services" #>
//...
<#
}
#endif
#>
<#
#if(ProductY)
{
#>
<## import namespace="Company.ProductY.Widgets" #>
<## import namespace="Company.ProductY.Services" #>
//...
<#
}
#endif
#>
With the above example the imports seem to create the corresponding using statements in the class regardless of the preprocessor directive. E.g.
using Company.ProductX.Widgets
using Company.ProductX.Services
using Company.ProductY.Widgets
using Company.ProductY.Services
Is there another way to use Preprocessors in T4 templates to affect the template itself rather than just the template output?
In your example the preprocessor directive is injected into the generated output. What you could potentially do is having a ProductX.tt file that imports the correct namespace and uses <## include #> to include the template code.
Something like this (ProductX.tt):
<## import namespace="Company.ProductX.Widgets" #>
<## import namespace="Company.ProductX.Services" #>
<## include file="TheTemplateCode.ttinclude" #>
(ProductY.tt):
<## import namespace="Company.ProductY.Widgets" #>
<## import namespace="Company.ProductY.Services" #>
<## include file="TheTemplateCode.ttinclude" #>
I am not sure if this helps you but to be honest I am struggling a little bit with the use-case here.
New idea for an old question.
It might be possible to use a Custom T4 Text Template Directive Processor to pass through arbitrary code to the T4 output.
The custom directive processor would need to be registered on each machine to use it.

How to open a file on relative path using T4?

I'm trying to run a T4 template that opens a XML file and uses it contents to generate a code artifact. However, I'm getting the an error message when I try to run a T4 template similar to the one below
<## template debug="false" hostspecific="false" language="C#" #>
<## assembly name="System.Xml.dll" #>
<## assembly name="System.Xml.Linq.dll" #>
<## import namespace="System.IO" #>
<## import namespace="System.Xml" #>
<## import namespace="System.Xml.Linq" #>
<## output extension=".cs" #>
namespace ConsoleApplication1
{
<# XElement fragment = XElement.Load("data.xml"); #>
...
Visual Studio 2010 error list is showing the following message
Running transformation: System.IO.FileNotFoundException: Could not find file 'C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\data.xml'.
It is trying to open the file on the path where the TextTemplateFileGenerator custom tool runs. I'd like it to open the file relative to my project path, because other developers on my team use different folder structures. Does anyone know if it is something possible to accomplish?
Change hostspecific option in template directive to "true" and call Host.ResolvePath("data.xml").
I had a similar problem but Host.ResolvePath didn't work for me because my relative path contained "..\.." in it. I worked around it by doing this:
string ttpath = this.Host.TemplateFile;
string resolvedPath = Path.GetFullPath(Path.GetDirectoryName(ttpath) + #"..\..\<Path To File>");

Resources