I'm trying to automate my builds from the command line. Unfortunately, my project was not intended to be automated from the beginning and now I'm struggling with the T4 templates.
After finding several bugs (this, and this) when trying to transform T4 templates using MSBuild I decided to find all the *.tt files from the root of my project and apply TextTransform to them, one by one.
But... some of them are a bit problematic. When running TextTransform on those I receive the following message:
C:\Specifications.tt(0,0) : error : Running transformation: System.InvalidCastException: Cannot convert 'Microsoft.VisualStudio.TextTemplating.CommandLine.CommandLineHost' into 'System.IServiceProvider'. en Microsoft.VisualStudio.TextTemplating7ea01e60423f49d2871739be33c0c810.GeneratedTextTransformation.GetProject() in Microsoft.VisualStudio.TextTemplating7ea01e60423f49d2871739be33c0c810.GeneratedTextTransformation.FindClasses(String nameSpace, String className, String baseClassName) in Microsoft.VisualStudio.TextTemplating7ea01e60423f49d2871739be33c0c810.GeneratedTextTransformation.TransformText()
Opening the .tt and looking for IServiceProvider or CommandLineHost, I've found these two methods:
private Project GetProject()
{
IServiceProvider serviceProvider = (IServiceProvider)this.Host;
DTE dte = serviceProvider.GetService(typeof(DTE)) as DTE;
// Get DTE
//var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
// Get ProjectItem representing the template file
ProjectItem projectItem = dte.Solution.FindProjectItem(this.Host.TemplateFile);
// Get the Project of the template file
Project project = projectItem.ContainingProject;
return project;
}
private string GetDefaultNamespace()
{
IServiceProvider serviceProvider = (IServiceProvider)this.Host;
DTE dte = serviceProvider.GetService(typeof(DTE)) as DTE;
// Get DTE
//var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
// Get ProjectItem representing the template file
ProjectItem projectItem = dte.Solution.FindProjectItem(this.Host.TemplateFile);
// Get the Project of the template file
Project project = projectItem.ContainingProject;
var vsSolution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution));
IVsHierarchy vsHierarchy;
ErrorHandler.ThrowOnFailure(vsSolution.GetProjectOfUniqueName(project.FullName, out vsHierarchy));
uint projectItemId;
ErrorHandler.ThrowOnFailure(vsHierarchy.ParseCanonicalName(projectItem.FileNames[1], out projectItemId));
object defaultNamespace;
ErrorHandler.ThrowOnFailure(vsHierarchy.GetProperty(projectItemId, (int)VsHierarchyPropID.DefaultNamespace, out defaultNamespace));
return ((string)defaultNamespace);
}
Is it possible to workaround this problem somehow? Changing the .tt code, using an alternative approach...
I just want to automate my builds from the command line!
UPDATE The following is the entire content of the template:
<#@ template language="C#" debug="false" hostspecific="true"#>
<#@ assembly name="EnvDTE" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.11.0" #>
<#@ assembly name="Microsoft.VisualStudio.Shell.Interop" #>
<#@ import namespace="EnvDTE" #>
<#@ import namespace="Microsoft.VisualStudio.Shell" #>
<#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #>
<#@ include file="EF.Utility.CS.ttinclude"#>
<#@ output extension="/"#>
<#
CodeGenerationTools code = new CodeGenerationTools(this);
MetadataLoader loader = new MetadataLoader(this);
CodeRegion region = new CodeRegion(this, 1);
MetadataTools ef = new MetadataTools(this);
string namespaceName = code.VsNamespaceSuggestion();// @"ArcNet.Piloto.Domain.Repositories";
string filenameSuffix = "Repository.cs";
// include additional using statements here
List<string> usingList = new List<string>(){
"IDB.MW.Domain.Entities",
"EverNext.Domain.Contracts.Repositories",
"System.ServiceModel",
"System.CodeDom.Compiler",
"EverNext.Domain.Contracts.Model"
// ,"other namespace"
};
////////////////////////////////////////////////////////////////////////////// Don't edit from here
EntityFrameworkTemplateFileManager fileManager = EntityFrameworkTemplateFileManager.Create(this);
// Write out support code to primary template output file
WriteHeader(fileManager, usingList.ToArray());
BeginNamespace(namespaceName, code);
EndNamespace(namespaceName);
// Emit Entity Types
foreach (var classes in FindClasses("IDB.MW.Domain.Entities", "", "BaseEntity").Where(c=>c.ImplementedInterfaces.OfType<CodeInterface>()
.Any(d=>d.Name.Equals("IAggregateRoot"))))
{
fileManager.StartNewFile(classes.Name + filenameSuffix);
BeginNamespace(namespaceName, code);
#>
[ServiceContract]
<#="public"#> <#=code.SpaceAfter(classes.IsAbstract ? "abstract" : string.Empty)#>partial interface I<#=classes.Name#>Repository : IEntityRepository<<#=classes.Name#>>,IAggregateRoot
{
}
<#
EndNamespace(namespaceName);
}
//
//if (!VerifyTypesAreCaseInsensitiveUnique(ItemCollection))
//{
//return "";
//}
fileManager.Process();
#>
<#+
void WriteHeader(EntityFrameworkTemplateFileManager fileManager, params string[] extraUsings)
{
fileManager.StartHeader();
#>
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated from a template.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Linq;
<#=String.Join(String.Empty, extraUsings.Select(u => "using " + u + ";" + Environment.NewLine).ToArray())#>
<#+
fileManager.EndBlock();
}
void BeginNamespace(string namespaceName, CodeGenerationTools code)
{
CodeRegion region = new CodeRegion(this);
if (!String.IsNullOrEmpty(namespaceName))
{
#>
namespace <#=code.EscapeNamespace(namespaceName)#>
{
<#+
PushIndent(CodeRegion.GetIndent(1));
}
}
void EndNamespace(string namespaceName)
{
if (!String.IsNullOrEmpty(namespaceName))
{
PopIndent();
#>
}
<#+
}
}
bool VerifyTypesAreCaseInsensitiveUnique(EdmItemCollection itemCollection)
{
Dictionary<string, bool> alreadySeen = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
foreach(StructuralType type in itemCollection.GetItems<StructuralType>())
{
if (!(type is EntityType || type is ComplexType))
{
continue;
}
if (alreadySeen.ContainsKey(type.FullName))
{
Error(String.Format(CultureInfo.CurrentCulture, "This template does not support types that differ only by case, the types {0} are not supported", type.FullName));
return false;
}
else
{
alreadySeen.Add(type.FullName, true);
}
}
return true;
}
private System.Collections.Generic.List<CodeClass> FindClasses(string nameSpace, string className, string baseClassName)
{
System.Collections.Generic.List<CodeClass> result = new System.Collections.Generic.List<CodeClass>();
FindClasses(GetProject().CodeModel.CodeElements, className, baseClassName, nameSpace, result, false);
return result;
}
private void FindClasses(CodeElements elements, string className, string baseClassName, string searchNamespace, List<CodeClass> result, bool isNamespaceOk)
{
if (elements == null) return;
foreach (CodeElement element in elements)
{
if (element is EnvDTE.CodeNamespace)
{
EnvDTE.CodeNamespace ns = element as EnvDTE.CodeNamespace;
if (ns != null)
{
if (ns.FullName == searchNamespace)
FindClasses(ns.Members, className, baseClassName, searchNamespace, result, true);
else
FindClasses(ns.Members, className, baseClassName, searchNamespace, result, false);
}
}
else if (element is CodeClass && isNamespaceOk)
{
CodeClass c = element as CodeClass;
if (c != null)
{
if (c.FullName.Contains(className) && (baseClassName == null || (HasIt(c.Bases, baseClassName) && c.Name != baseClassName)))
result.Add(c);
FindClasses(c.Members, className, baseClassName, searchNamespace, result, true);
}
}
}
}
private bool HasIt(CodeElements elements, string name)
{
foreach (CodeElement element in elements)
{
CodeClass c = element as CodeClass;
if (c != null && c.Bases != null)
{
if (HasIt(c.Bases, name))
{
return true;
}
}
if (element.Name == name)
return true;
}
return false;
}
private Project GetProject()
{
IServiceProvider serviceProvider = (IServiceProvider)this.Host;
DTE dte = serviceProvider.GetService(typeof(DTE)) as DTE;
// Get DTE
//var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
// Get ProjectItem representing the template file
ProjectItem projectItem = dte.Solution.FindProjectItem(this.Host.TemplateFile);
// Get the Project of the template file
Project project = projectItem.ContainingProject;
return project;
}
private string GetDefaultNamespace()
{
IServiceProvider serviceProvider = (IServiceProvider)this.Host;
DTE dte = serviceProvider.GetService(typeof(DTE)) as DTE;
// Get DTE
//var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE));
// Get ProjectItem representing the template file
ProjectItem projectItem = dte.Solution.FindProjectItem(this.Host.TemplateFile);
// Get the Project of the template file
Project project = projectItem.ContainingProject;
var vsSolution = (IVsSolution)serviceProvider.GetService(typeof(SVsSolution));
IVsHierarchy vsHierarchy;
ErrorHandler.ThrowOnFailure(vsSolution.GetProjectOfUniqueName(project.FullName, out vsHierarchy));
uint projectItemId;
ErrorHandler.ThrowOnFailure(vsHierarchy.ParseCanonicalName(projectItem.FileNames[1], out projectItemId));
object defaultNamespace;
ErrorHandler.ThrowOnFailure(vsHierarchy.GetProperty(projectItemId, (int)VsHierarchyPropID.DefaultNamespace, out defaultNamespace));
return ((string)defaultNamespace);
}
#>