1

I am trying to Create a File in an existing Project in a Solution Folder in a multi-output file, T4 template, based on a tangible T4 Editor tutorial.

I have the following structure in my solution:

/T4Templates
   - T4TemplateProject **<--- my T4 Project**
/Common
   -Project.NameSpace.Common **<--- Want to create file in here**
/Services
   -Project.NameSpace.Services **<--- And create file in here**

My starting point is the following excerpt from the ttincludefile:

/// <summary>
/// Marks the end of the last file if there was one, and starts a new
/// and marks this point in generation as a new file.
/// </summary>
/// <param name="name">Filename</param>
/// <param name="projectName">Name of the target project for the new file.</param>
/// <param name="folderName">Name of the target folder for the new file.</param>
/// <param name="fileProperties">File property settings in vs for the new File</param>
public void StartNewFile(string name, string projectName = "",
    string folderName = "", FileProperties fileProperties = null)
{
    if (String.IsNullOrWhiteSpace(name) == true)
    {
        throw new ArgumentException("name");
    }

    CurrentBlock = new Block 
    { 
        Name = name, 
        ProjectName = projectName, 
        FolderName = folderName,
        FileProperties = fileProperties ?? new FileProperties()
    };
}

/// <summary>
/// Produce the template output files.
/// </summary>
public virtual IEnumerable<OutputFile> Process(bool split = true)
{
    var list = new List<OutputFile>();

    if (split)
    {
        EndBlock();

        var headerText = _generationEnvironment.ToString(header.Start, header.Length);
        var footerText = _generationEnvironment.ToString(footer.Start, footer.Length);
        files.Reverse();

        foreach (var block in files)
        {
            var outputPath = VSHelper.GetOutputPath(dte, block,
                Path.GetDirectoryName(_textTransformation.Host.TemplateFile)); //<-- exception bubbled up here
            var fileName = Path.Combine(outputPath, block.Name);
            var content = this.ReplaceParameter(headerText, block) + 
                _generationEnvironment.ToString(block.Start, block.Length) + 
                footerText;

            var file = new OutputFile 
            { 
                FileName = fileName, 
                ProjectName = block.ProjectName, 
                FolderName = block.FolderName,
                FileProperties = block.FileProperties,
                Content = content
            };

            CreateFile(file);
            _generationEnvironment.Remove(block.Start, block.Length);

            list.Add(file);     
        }
    }

    projectSyncAction.EndInvoke(projectSyncAction.BeginInvoke(list, null, null));
    this.CleanUpTemplatePlaceholders();
    var items = VSHelper.GetOutputFilesAsProjectItems(this.dte, list);
    this.WriteVsProperties(items, list);

    if (this.IsAutoIndentEnabled == true && split == true)
    {
        this.FormatProjectItems(items);
    }

    this.WriteLog(list);

    return list;
}

private void FormatProjectItems(IEnumerable<EnvDTE.ProjectItem> items)
{
    foreach (var item in items)
    {
        this._textTransformation.WriteLine(
        VSHelper.ExecuteVsCommand(this.dte, item, "Edit.FormatDocument"));
        this._textTransformation.WriteLine("//-> " + item.Name);
    }
}

private void WriteVsProperties(IEnumerable<EnvDTE.ProjectItem> items, 
    IEnumerable<OutputFile> outputFiles)
{
    foreach (var file in outputFiles)
    {
        var item = items
            .Where(p => p.Name == Path.GetFileName(file.FileName))
            .FirstOrDefault();
        if (item == null) continue; 

        if (String.IsNullOrEmpty(file.FileProperties.CustomTool) == false)
        {
            VSHelper.SetPropertyValue(
                item, "CustomTool", file.FileProperties.CustomTool);
        }

        if (String.IsNullOrEmpty(file.FileProperties.BuildActionString) == false)
        {
            VSHelper.SetPropertyValue(
                item, "ItemType", file.FileProperties.BuildActionString);
        }
    }
}

private string ReplaceParameter(string text, Block block)
{
    if (String.IsNullOrEmpty(text) == false)
    {
        text = text.Replace("$filename$", block.Name);
    }

    foreach (var item in block.FileProperties.TemplateParameter.AsEnumerable())
    {
        text = text.Replace(item.Key, item.Value);
    }

    return text;
}

/// <summary>
/// Write log to the default output file.
/// </summary>
/// <param name="list"></param>
private void WriteLog(IEnumerable<OutputFile> list)
{
    this._textTransformation.WriteLine("// Generated helper templates");
    foreach (var item in templatePlaceholderList)
    {
        this._textTransformation.WriteLine(
            "// " + this.GetDirectorySolutionRelative(item));        
    }

    this._textTransformation.WriteLine("// Generated items");
    foreach (var item in list)
    {
        this._textTransformation.WriteLine(
            "// " + this.GetDirectorySolutionRelative(item.FileName)); 
    }
}

/// <summary>
/// Removes old template placeholders from the solution.
/// </summary>
private void CleanUpTemplatePlaceholders()      
{
    string[] activeTemplateFullNames = this.templatePlaceholderList.ToArray();
    string[] allHelperTemplateFullNames = VSHelper.GetAllSolutionItems(this.dte)
        .Where(p =>
            p.Name == VSHelper.GetTemplatePlaceholderName(this.templateProjectItem))
        .Select(p => VSHelper.GetProjectItemFullPath(p))
        .ToArray();

    var delta = allHelperTemplateFullNames.Except(activeTemplateFullNames).ToArray();

    var dirtyHelperTemplates = VSHelper.GetAllSolutionItems(this.dte)
        .Where(p => delta.Contains(VSHelper.GetProjectItemFullPath(p)));

    foreach (ProjectItem item in dirtyHelperTemplates)
    {
        if (item.ProjectItems != null)
        {
            foreach (ProjectItem subItem in item.ProjectItems)
            {
                subItem.Remove(); 
            }
        }

        item.Remove();
    }
}

protected virtual void CreateFile(OutputFile file)
{
    if (this.CanOverrideExistingFile == false && File.Exists(file.FileName) == true)
    {
        return;
    }

    if (IsFileContentDifferent(file))
    {
        CheckoutFileIfRequired(file.FileName);
        File.WriteAllText(file.FileName, file.Content, this.Encoding);
    }
}

protected bool IsFileContentDifferent(OutputFile file)
{
    return !(File.Exists(file.FileName) && File.ReadAllText(file.FileName) == file.Content);
}

private void CheckoutFileIfRequired(string fileName)
{
    if (dte.SourceControl == null
        || !dte.SourceControl.IsItemUnderSCC(fileName)
            || dte.SourceControl.IsItemCheckedOut(fileName))
    {
        return;
    }

    // run on worker thread to prevent T4 calling back into VS
    checkOutAction.EndInvoke(checkOutAction.BeginInvoke(fileName, null, null));
}   

Part 2 of the excerpt: VSHelper class. I have indicated where the exception occurs and bubbles up. You can search for the string <-- exception

public class VSHelper
{
    /// <summary>
    /// Execute Visual Studio commands against the project item.
    /// </summary>
    /// <param name="item">The current project item.</param>
    /// <param name="command">The vs command as string.</param>
    /// <returns>An error message if the command fails.</returns>
    public static string ExecuteVsCommand(EnvDTE.DTE dte, EnvDTE.ProjectItem item, params string[] command)
    {
        if (item == null)
        {
            throw new ArgumentNullException("item");
        }

        string error = String.Empty;

        try
        {
            EnvDTE.Window window = item.Open();
            window.Activate();

            foreach (var cmd in command)
            {
                if (String.IsNullOrWhiteSpace(cmd) == true)
                {
                    continue;
                }

                EnvDTE80.DTE2 dte2 = dte as EnvDTE80.DTE2;
                dte2.ExecuteCommand(cmd, String.Empty);     
            }

            item.Save();
            window.Visible = false;
            // window.Close(); // Ends VS, but not the tab :(
        }
        catch (Exception ex)
        {
            error = String.Format("Error processing file {0} {1}", item.Name, ex.Message);
        }

        return error;
    }

    /// <summary>
    /// Sets a property value for the vs project item.
    /// </summary>
    public static void SetPropertyValue(EnvDTE.ProjectItem item, string propertyName, object value)
    {
        EnvDTE.Property property = item.Properties.Item(propertyName);
        if (property == null)
        {
            throw new ArgumentException(String.Format("The property {0} was not found.", propertyName));
        }
        else
        {
            property.Value = value;
        }
    }

    public static IEnumerable<ProjectItem> GetOutputFilesAsProjectItems(EnvDTE.DTE dte, IEnumerable<OutputFile> outputFiles)
    {
        var fileNames = (from o in outputFiles
                        select Path.GetFileName(o.FileName)).ToArray();

        return VSHelper.GetAllSolutionItems(dte).Where(f => fileNames.Contains(f.Name));
    }

    public static string GetOutputPath(EnvDTE.DTE dte, Block block, string defaultPath)
    {
        if (String.IsNullOrEmpty(block.ProjectName) == true && String.IsNullOrEmpty(block.FolderName) == true)
        {
            return defaultPath;
        }

        EnvDTE.Project prj = null;
        EnvDTE.ProjectItem item = null;

        if (String.IsNullOrEmpty(block.ProjectName) == false)
        {
            prj = GetProject(dte, block.ProjectName); //<-- exception bubbled up here           
        }

        if (String.IsNullOrEmpty(block.FolderName) == true && prj != null)
        {
            return Path.GetDirectoryName(prj.FullName);
        }
        else if (prj != null && String.IsNullOrEmpty(block.FolderName) == false)
        {
            item = GetAllProjectItemsRecursive(prj.ProjectItems).Where(i=>i.Name == block.FolderName).First();
        }
        else if (String.IsNullOrEmpty(block.FolderName) == false)
        {
            item = GetAllProjectItemsRecursive(
                dte.ActiveDocument.ProjectItem.ContainingProject.ProjectItems).
                Where(i=>i.Name == block.FolderName).First();
        }

        if (item != null)
        {
            return GetProjectItemFullPath(item);
        }

        return defaultPath;
    }
    public static string GetTemplatePlaceholderName(EnvDTE.ProjectItem item)
    {
        return String.Format("{0}.txt4", Path.GetFileNameWithoutExtension(item.Name));
    }

    public static EnvDTE.ProjectItem GetTemplateProjectItem(EnvDTE.DTE dte, OutputFile file, EnvDTE.ProjectItem defaultItem)
    {
        if (String.IsNullOrEmpty(file.ProjectName) == true && String.IsNullOrEmpty(file.FolderName) == true)
        {
            return defaultItem;
        }

        string templatePlaceholder = GetTemplatePlaceholderName(defaultItem);
        string itemPath = Path.GetDirectoryName(file.FileName); 
        string fullName = Path.Combine(itemPath, templatePlaceholder);
        EnvDTE.Project prj = null;
        EnvDTE.ProjectItem item = null;

        if (String.IsNullOrEmpty(file.ProjectName) == false)
        {
            prj = GetProject(dte, file.ProjectName);            
        }

        if (String.IsNullOrEmpty(file.FolderName) == true && prj != null)
        {
            return FindProjectItem(prj.ProjectItems, fullName, true);
        }
        else if (prj != null && String.IsNullOrEmpty(file.FolderName) == false)
        {
            item = GetAllProjectItemsRecursive(prj.ProjectItems).Where(i=>i.Name == file.FolderName).First();
        }
        else if (String.IsNullOrEmpty(file.FolderName) == false)
        {
            item = GetAllProjectItemsRecursive(
                dte.ActiveDocument.ProjectItem.ContainingProject.ProjectItems).
                Where(i=>i.Name == file.FolderName).First();
        }

        if (item != null)
        {
            return FindProjectItem(item.ProjectItems, fullName, true);
        }

        return defaultItem;
    }

    private static EnvDTE.ProjectItem FindProjectItem(EnvDTE.ProjectItems items, string fullName, bool canCreateIfNotExists)
    {
        EnvDTE.ProjectItem item = (from i in items.Cast<EnvDTE.ProjectItem>()
                                  where i.Name == Path.GetFileName(fullName)
                                  select i).FirstOrDefault();
        if (item == null)
        {
            File.CreateText(fullName);
            item = items.AddFromFile(fullName);
        }

        return item;
    }

    public static EnvDTE.Project GetProject(EnvDTE.DTE dte, string projectName) 
    {
        return GetAllProjects(dte).Where(p=>p.Name == projectName).First(); // <-- exception ORIGINATES HERE
    }

    public static IEnumerable<EnvDTE.Project> GetAllProjects(EnvDTE.DTE dte)
    {
        List<EnvDTE.Project> projectList = new List<EnvDTE.Project>();

        var folders = dte.Solution.Projects.Cast<EnvDTE.Project>().Where(p=>p.Kind == EnvDTE80.ProjectKinds.vsProjectKindSolutionFolder);

        foreach (EnvDTE.Project folder in folders)
        {
            if (folder.ProjectItems == null) continue;

            foreach (EnvDTE.ProjectItem item in folder.ProjectItems)
            {
                if (item.Object is EnvDTE.Project)
                    projectList.Add(item.Object as EnvDTE.Project);
            }
        }

        var projects = dte.Solution.Projects.Cast<EnvDTE.Project>().Where(p=>p.Kind != EnvDTE80.ProjectKinds.vsProjectKindSolutionFolder);

        if (projects.Count() > 0)
            projectList.AddRange(projects);

        return projectList;
    }

    public static EnvDTE.ProjectItem GetProjectItemWithName(EnvDTE.ProjectItems items, string itemName)
    {
        return GetAllProjectItemsRecursive(items).Cast<ProjectItem>().Where(i=>i.Name == itemName).First();
    }

    public static string GetProjectItemFullPath(EnvDTE.ProjectItem item)
    {
        return item.Properties.Item("FullPath").Value.ToString();
    }

    public static IEnumerable<EnvDTE.ProjectItem> GetAllSolutionItems(EnvDTE.DTE dte)
    {
        List<EnvDTE.ProjectItem> itemList = new List<EnvDTE.ProjectItem>();

        foreach (Project item in GetAllProjects(dte))
        {
            if (item == null || item.ProjectItems == null) continue;

            itemList.AddRange(GetAllProjectItemsRecursive(item.ProjectItems));   
        }

        return itemList;
    }

    public static IEnumerable<EnvDTE.ProjectItem> GetAllProjectItemsRecursive(EnvDTE.ProjectItems projectItems) 
    {
        foreach (EnvDTE.ProjectItem projectItem in projectItems) 
        {
            if (projectItem.ProjectItems == null) continue;

            foreach (EnvDTE.ProjectItem subItem in GetAllProjectItemsRecursive(projectItem.ProjectItems))
            {
                yield return subItem;
            }


            yield return projectItem;
        }
    }
}

My template:

<#@ template  debug="true" hostSpecific="true" #>
<#@ output extension=".cs" #>
<#@ Assembly Name="System.Core" #>
<#@ Assembly Name="System.Windows.Forms" #>
<#@ import namespace="System" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections" #>
<#@ import namespace="System.Collections.Generic" #> 

<#@ include file="TemplateFileManagerV2.1.ttinclude" #>
<#
var manager = TemplateFileManager.Create(this);
#>

<#
//This correctly generates a file in the local T4TemplateProject
manager.StartNewFile("MyObject.cs");
#>

<#
//this fails
manager.StartNewFile("MyObjectDto.cs","Project.NameSpace.Common","Common");
#>

<#    
manager.Process();
#>

I get the following error:

Severity Code Description Project File Line Suppression State Error Running transformation: System.InvalidOperationException: Sequence contains no elements

I have read the following:

  1. http://t4-editor.tangible-engineering.com/blog/how-to-generate-multiple-output-files-from-a-single-t4-template.html
  2. How to create multiple output files from a single T4 template using Tangible Editor?

The T4 example is based on the following solution structure: enter image description here

I would like to do the same where there are Solution Folders: enter image description here

tinonetic
  • 7,751
  • 11
  • 54
  • 79

1 Answers1

1

I have reduced the relevant parts of your code down to this:

var projectList = dte.Solution.Projects
    .Cast<EnvDTE.Project>()
    .Where(p => p.Kind == EnvDTE80.ProjectKinds.vsProjectKindSolutionFolder)
    .Where(folder => folder.ProjectItems != null)
    .SelectMany(folder => folder.ProjectItems)
    .Where(item => item.Object is EnvDTE.Project)
    .Select(item => item.Object as EnvDTE.Project)

var projects = dte.Solution.Projects.Cast<EnvDTE.Project>()
    .Where(p => p.Kind != EnvDTE80.ProjectKinds.vsProjectKindSolutionFolder);

projectList.Concat(projects)
    .Where(p=>p.Name == projectName)
    .First(); // <-- exception ORIGINATES HERE

So it looks like either p.Kind or p.Name is not what you are expecting.

To debug this, loop over dte.Solution.Projects and all its child ProjectItems recursivly. Output the Kind and Name properties.

Buh Buh
  • 7,443
  • 1
  • 34
  • 61
  • Will try it out in a moment. Either way, thanks for the effort of going through all that code, asking and figuring it out! – tinonetic Sep 01 '17 at 14:04