mgphall mgphall - 20 days ago 6
C# Question

VisualStudio Multi project Templates

I am creating a Multi project Template

The problem is that when I run the template each project creates a directory folder that matches the project name.

I don't want each of the projects creating folders by default, like this:


  • solutionfolder\Libraries\BL\projectname\
    my files - and csproj file

  • solutionfolder\Libraries\BL\interfaces\projectname\
    my files - and csproj file



What I want is:


  • solutionfolder\Libraries\BL\
    my files and csproj file

  • solutionfolder\Libraries\BL\interfaces\
    my files and csproj file



I have tried
<CreateNewFolder> false </CreateNewFolder>
but this does not work

Q: How can I create a project template without creating a project folder?

<ProjectCollection>
<SolutionFolder Name="Libraries">
<SolutionFolder Name="BL">
<SolutionFolder Name="Interfaces">
<ProjectTemplateLink ProjectName="BL_$safeprojectname$_Interfaces">Libraries\BL\Interfaces\MyTemplate.vstemplate</ProjectTemplateLink>
</SolutionFolder>
<ProjectTemplateLink ProjectName="BL_$safeprojectname$">Libraries\BL\MyTemplate.vstemplate</ProjectTemplateLink>
</SolutionFolder>
</ProjectCollection>

Answer

You can move around folders and project items by creating a Wizard Template. Just put the logic in the ProjectFinishedGenerating() method.

I used this file as a reference for my own template, specifically the MoveProjectTo() method.

WizardImplementation.cs (original link, cached)

using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TemplateWizard;
using VSLangProj;
using EnvDTE;
using EnvDTE80;
using System.IO;
using System.Windows.Forms;
using System.Threading;
using System.Xml;

namespace SharpArchApplicationWizard
{
    // Class that implements the IWizard() interface
    internal class WizardImplementation : IWizard
    {
        /// <summary>
        /// Provide a means for sub-projects to have access to the solution name
        /// </summary>
        private static string solutionName;
        private static string guidAssignedToCore = "{00000000-0000-0000-0000-000000000000}";
        private static string guidAssignedToData = "{00000000-0000-0000-0000-000000000000}";
        private static string guidAssignedToApplicationServices = "{00000000-0000-0000-0000-000000000000}";
        private static string guidAssignedToControllers = "{00000000-0000-0000-0000-000000000000}";

        private const int MIN_TIME_FOR_PROJECT_TO_RELEASE_FILE_LOCK = 700;
        private EnvDTE._DTE dte;
        private WizardRunKind runKind;
        private Dictionary<string, string> replacementsDictionary;

        // RunStarted() method gets called before the template wizard creates the project.
        void IWizard.RunStarted(object application, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams) {
            this.dte = application as EnvDTE._DTE;
            this.runKind = runKind;
            this.replacementsDictionary = replacementsDictionary;

            // Store the solution name locally while processing the solution template
            if (runKind == WizardRunKind.AsMultiProject) {
                solutionName = replacementsDictionary["$safeprojectname$"];
            }

            replacementsDictionary.Add("$solutionname$", solutionName);

            if (runKind == WizardRunKind.AsNewProject) {
                // Make the solution root path available for all the projects
                replacementsDictionary.Add("$solutionrootpath$", GetSolutionRootPath() + solutionName + "\\");

                AddProjectGuidsTo(replacementsDictionary);
            }
        }

        /// <summary>
        /// Makes the project GUIDs, which are collected during the project creation process, 
        /// available to subsequent projects
        /// </summary>
        private static void AddProjectGuidsTo(Dictionary<string, string> replacementsDictionary) {
            replacementsDictionary.Add("$guidAssignedToCore$", guidAssignedToCore);
            replacementsDictionary.Add("$guidAssignedToData$", guidAssignedToData);
            replacementsDictionary.Add("$guidAssignedToApplicationServices$", guidAssignedToApplicationServices);
            replacementsDictionary.Add("$guidAssignedToControllers$", guidAssignedToControllers);
        }

        /// <summary>
        /// Runs custom wizard logic when a project has finished generating
        /// </summary>
        void IWizard.ProjectFinishedGenerating(EnvDTE.Project project) {
            if (project != null) {
                if (project.Name == "SolutionItemsContainer") {
                    PerformSolutionInitialization(project);
                    MoveSolutionItemsToLib(project);
                }
                else if (project.Name == "ToolsSolutionItemsContainer") {
                    MoveSolutionItemsToToolsLib(project);
                }
                else if (project.Name == "CrudScaffolding") {
                    Project movedProject = MoveProjectTo("\\tools\\", project, "Code Generation");
                    ExcludeProjectFromBuildProcess(movedProject);
                }
                else if (project.Name == GetSolutionName() + ".Tests") {
                    MoveProjectTo("\\tests\\", project);
                }
                else if (project.Name == GetSolutionName() + ".Web.Controllers" ||
                    project.Name == GetSolutionName() + ".ApplicationServices" ||
                    project.Name == GetSolutionName() + ".Core" ||
                    project.Name == GetSolutionName() + ".Data" ||
                    project.Name == GetSolutionName() + ".Web") {
                    Project movedProject = MoveProjectTo("\\app\\", project);

                    // Give the solution time to release the lock on the project file
                    System.Threading.Thread.Sleep(MIN_TIME_FOR_PROJECT_TO_RELEASE_FILE_LOCK);

                    CaptureProjectGuidOf(movedProject);
                }
            }
        }

        private void CaptureProjectGuidOf(Project project) {
            if (IsProjectReferredByOtherProjects(project)) {
                string projectPath = GetSolutionRootPath() + GetSolutionName() + "\\app\\" + project.Name + "\\" + project.Name + ".csproj";

                Log("CaptureProjectGuidOf: Does " + projectPath + " exist? " + File.Exists(projectPath).ToString());
                Log("CaptureProjectGuidOf: About to open " + projectPath);

                XmlDocument xmlDocument = new XmlDocument();
                xmlDocument.Load(projectPath);
                XmlNodeList projectGuidNodes = xmlDocument.GetElementsByTagName("ProjectGuid");

                if (projectGuidNodes == null || projectGuidNodes.Count == 0)
                    throw new ApplicationException("Couldn't find a matching node in the project file for ProjectGuid");

                StoreCapturedGuidForLaterUse(project, projectGuidNodes);

                Log("CaptureProjectGuidOf: Captured the GUID " + projectGuidNodes[0].InnerText + " for " + project.Name);
            }
        }

        private void StoreCapturedGuidForLaterUse(Project project, XmlNodeList projectGuidNodes) {
            if (project.Name == GetSolutionName() + ".ApplicationServices") {
                guidAssignedToApplicationServices = projectGuidNodes[0].InnerText;
            }
            else if (project.Name == GetSolutionName() + ".Core") {
                guidAssignedToCore = projectGuidNodes[0].InnerText;
            }
            else if (project.Name == GetSolutionName() + ".Web.Controllers") {
                guidAssignedToControllers = projectGuidNodes[0].InnerText;
            }
            else if (project.Name == GetSolutionName() + ".Data") {
                guidAssignedToData = projectGuidNodes[0].InnerText;
            }
        }

        private bool IsProjectReferredByOtherProjects(Project project) {
            return project.Name == GetSolutionName() + ".ApplicationServices" ||
                project.Name == GetSolutionName() + ".Core" ||
                project.Name == GetSolutionName() + ".Web.Controllers" ||
                project.Name == GetSolutionName() + ".Data";
        }

        /// <summary>
        /// Sets up the solution structure and performs a number of related initialization steps
        /// </summary>
        private void PerformSolutionInitialization(EnvDTE.Project project) {
            CreateSolutionDirectoryStructure();
            MoveCommonAssemblyInfoToRoot(project);
        }

        /// <summary>
        /// Runs custom wizard logic when the wizard has completed all tasks
        /// </summary>
        void IWizard.RunFinished() {
            // Only copy the solution items once, right after processing the solution template
            if (runKind == WizardRunKind.AsMultiProject) {
                DeleteSuoFile();

                // Operations after this must take into account that the solution path has changed
                MoveSolutionFileToProjectsDirectory();
            }
        }

        private void ExcludeProjectFromBuildProcess(EnvDTE.Project project) {
            Solution2 solution = dte.Solution as Solution2;
            SolutionBuild2 solutionBuild = (SolutionBuild2)solution.SolutionBuild;

            foreach (SolutionConfiguration solutionConfiguration in solutionBuild.SolutionConfigurations) {
                foreach (SolutionContext solutionContext in solutionConfiguration.SolutionContexts) {
                    if (solutionContext.ProjectName.IndexOf(project.Name) > -1) {
                        Log("ExcludeProjectFromBuildProcess: Setting build to false for project " + solutionContext.ProjectName +
                            " within the " + solutionConfiguration.Name + " configuration");
                        solutionContext.ShouldBuild = false;
                    }
                }
            }
        }

        private Project MoveProjectTo(string targetSubFolder, EnvDTE.Project project) {
            return MoveProjectTo(targetSubFolder, project, null);
        }

        private Project MoveProjectTo(string targetSubFolder, EnvDTE.Project project, string solutionFolderName) {
            string projectName = project.Name;
            string originalLocation = GetSolutionRootPath() + GetSolutionName() + "\\" + projectName;

            if (Directory.Exists(originalLocation)) {
                Solution2 solution = dte.Solution as Solution2;

                Log("MoveProjectTo: Removing " + projectName + " from solution");
                solution.Remove(project);

                // Give the solution time to release the lock on the project file
                System.Threading.Thread.Sleep(MIN_TIME_FOR_PROJECT_TO_RELEASE_FILE_LOCK);

                PerformManualProjectReplacementsTo(originalLocation + "\\" + projectName + ".csproj");

                string targetLocation = GetSolutionRootPath() + GetSolutionName() + targetSubFolder + projectName;

                Log("MoveProjectTo: Moving " + projectName + " from " + originalLocation + " to target location at " + targetLocation);
                Directory.Move(originalLocation, targetLocation);

                if (!string.IsNullOrEmpty(solutionFolderName)) {
                    SolutionFolder solutionFolder = (SolutionFolder)solution.AddSolutionFolder(solutionFolderName).Object;
                    Log("MoveProjectTo: Adding " + projectName + " to solution folder " + targetLocation);
                    return solutionFolder.AddFromFile(targetLocation + "\\" + projectName + ".csproj");
                }
                else {
                    Log("MoveProjectTo: Adding " + projectName + " to solution");
                    return solution.AddFromFile(targetLocation + "\\" + projectName + ".csproj", false);
                }
            }
            else {
                throw new ApplicationException("Couldn't find " + originalLocation + " to move");
            }
        }

        /// <summary>
        /// This does any manual value replacement on project files when it can't be handled 
        /// (or is being handled incorrectly by the VS templating process.
        /// </summary>
        private void PerformManualProjectReplacementsTo(string projectFilePath) {
            if (File.Exists(projectFilePath)) {
                Log("PerformManualProjectReplacementsTo: Going to PerformManualProjectReplacementsTo on " + projectFilePath);

                // Open a file for reading
                StreamReader streamReader;
                streamReader = File.OpenText(projectFilePath);

                // Now, read the entire file into a string
                string contents = streamReader.ReadToEnd();
                streamReader.Close();

                // Write the modification into the same fil
                StreamWriter streamWriter = File.CreateText(projectFilePath);
                streamWriter.Write(contents.Replace("PLACE_HOLDER_COMMON_ASSEMLY_INFO_LOCATION", "..\\..\\CommonAssemblyInfo.cs"));
                streamWriter.Close();
            }
            else {
                throw new ApplicationException("Couldn't find " + projectFilePath + " to PerformManualProjectReplacementsTo");
            }
        }

        private void MoveCommonAssemblyInfoToRoot(EnvDTE.Project solutionItemsContainerProject) {
            string originalFileLocation = GetSolutionRootPath() + GetSolutionName() + "\\SolutionItemsContainer\\CommonAssemblyInfo.cs";

            if (File.Exists(originalFileLocation)) {
                string targetFileLocation = GetSolutionRootPath() + GetSolutionName() + "\\CommonAssemblyInfo.cs";

                Log("MoveCommonAssemblyInfoToRoot: Moving CommonAssemblyInfo.cs from " + originalFileLocation + " to root at " + targetFileLocation);
                File.Move(originalFileLocation, targetFileLocation);
            }
            else {
                throw new ApplicationException("Couldn't find CommonAssemblyInfo.cs to move");
            }
        }

        private void MoveSolutionItemsToLib(EnvDTE.Project solutionItemsContainerProject) {
            string originalLocation = GetSolutionRootPath() + GetSolutionName() + "\\SolutionItemsContainer\\Solution Items";

            if (Directory.Exists(originalLocation)) {
                string targetLibFolder = GetSolutionRootPath() + GetSolutionName() + "\\lib";

                Log("MoveSolutionItemsToLib: Moving solution items from " + originalLocation + " to lib at " + targetLibFolder);
                Directory.Move(originalLocation, targetLibFolder);

                Solution2 solution = dte.Solution as Solution2;
                solution.Remove(solutionItemsContainerProject);
                // Give the solution time to release the lock on the project file
                System.Threading.Thread.Sleep(500);

                Directory.Delete(GetSolutionRootPath() + GetSolutionName() + "\\SolutionItemsContainer", true);
            }
            else {
                throw new ApplicationException("Couldn't find " + originalLocation + " to move");
            }
        }

        private void MoveSolutionItemsToToolsLib(EnvDTE.Project toolsSolutionItemsContainerProject) {
            string originalLocation = GetSolutionRootPath() + GetSolutionName() + "\\ToolsSolutionItemsContainer\\Solution Items";

            if (Directory.Exists(originalLocation)) {
                string targetToolsLibFolder = GetSolutionRootPath() + GetSolutionName() + "\\tools\\lib";

                Log("MoveSolutionItemsToToolsLib: Moving tools solution items from " + originalLocation + " to tools lib at " + targetToolsLibFolder);
                Directory.Move(originalLocation, targetToolsLibFolder);

                Solution2 solution = dte.Solution as Solution2;
                solution.Remove(toolsSolutionItemsContainerProject);
                // Give the solution time to release the lock on the project file
                System.Threading.Thread.Sleep(500);

                Directory.Delete(GetSolutionRootPath() + GetSolutionName() + "\\ToolsSolutionItemsContainer", true);
            }
            else {
                throw new ApplicationException("Couldn't find " + originalLocation + " to move");
            }
        }

        /// <summary>
        /// Note that this is called BEFORE the SLN is moved to the solution folder; therefore, we have 
        /// to add the solution name after the root path.
        /// </summary>
        private void CreateSolutionDirectoryStructure() {
            Directory.CreateDirectory(GetSolutionRootPath() + GetSolutionName() + "\\app");
            Directory.CreateDirectory(GetSolutionRootPath() + GetSolutionName() + "\\build");
            Directory.CreateDirectory(GetSolutionRootPath() + GetSolutionName() + "\\db");
            Directory.CreateDirectory(GetSolutionRootPath() + GetSolutionName() + "\\docs");
            Directory.CreateDirectory(GetSolutionRootPath() + GetSolutionName() + "\\logs");
            Directory.CreateDirectory(GetSolutionRootPath() + GetSolutionName() + "\\tests");
            Directory.CreateDirectory(GetSolutionRootPath() + GetSolutionName() + "\\tools");
        }

        private void MoveSolutionFileToProjectsDirectory() {
            dte.Solution.SaveAs(
                GetSolutionRootPath() + GetSolutionName() + "\\" + GetSolutionFileName());
        }

        private void DeleteSuoFile() {
            string suoFile = GetSolutionRootPath() + GetSolutionName() + ".suo";

            if (File.Exists(suoFile)) {
                Log("DeleteSuoFile: Deleting " + suoFile);
                File.Delete(suoFile);
            }
        }

        private void Log(string message) {
            StreamWriter streamWriter = File.AppendText(GetSolutionRootPath() + GetSolutionName() + "\\logs\\" + LOG_FILE_NAME);
            streamWriter.WriteLine(DateTime.Now.ToLongTimeString() + "\t" + message);
            streamWriter.Close();
        }

        private string GetSolutionName() {
            return replacementsDictionary["$solutionname$"];
        }

        private string GetSolutionFileName() {
            return GetSolutionName() + ".sln";
        }

        private string GetSolutionFileFullName() {
            return dte.Solution.Properties.Item("Path").Value.ToString();
        }

        private string GetSolutionRootPath() {
            return GetSolutionFileFullName().Replace(GetSolutionFileName(), "");
        }

        // This method is called before opening any item which is marked for opening in the editor in the 
        // .vstemplate file using the "OpenInEditor" attribute.
        void IWizard.BeforeOpeningFile(EnvDTE.ProjectItem projectItem) {

        }

        // This method is only applicable for item templates and does not get called for project templates.
        void IWizard.ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem) {
        }

        // This method is only applicable for item templates and does not get called for project templates.
        bool IWizard.ShouldAddProjectItem(string filePath) {
            return true;
        }

        private const string LOG_FILE_NAME = "SharpArch.VSharpArchTemplate.log";
    }
}