关键词:
【中文标题】Visual Studio 多项目模板【英文标题】:VisualStudio Multi project Templates 【发布时间】:2012-04-05 05:50:26 【问题描述】:我正在创建一个多项目模板
问题是当我运行模板时,每个项目都会创建一个与项目名称匹配的目录文件夹。
我不希望每个项目都默认创建文件夹,如下所示:
solutionfolder\Libraries\BL\projectname\
我的文件 - 和 csproj 文件
solutionfolder\Libraries\BL\interfaces\projectname\
我的文件 - 和 csproj 文件
我想要的是:
solutionfolder\Libraries\BL\
我的文件和 csproj 文件
solutionfolder\Libraries\BL\interfaces\
我的文件和 csproj 文件
我试过<CreateNewFolder> false </CreateNewFolder>
但这不起作用
问:如何在不创建项目文件夹的情况下创建项目模板?
<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>
【问题讨论】:
【参考方案1】:您可以通过创建向导模板来移动文件夹和项目项。只需将逻辑放在 ProjectFinishedGenerating() 方法中即可。
我将此文件用作我自己模板的参考,特别是 MoveProjectTo() 方法。
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";
【讨论】:
【参考方案2】:$ext_safeprojectname$ 也可用于获取基于解决方案而非当前项目的命名空间。
【讨论】:
Visual Studio 多项目模板重命名命名空间和项目名称
】VisualStudio多项目模板重命名命名空间和项目名称【英文标题】:VisualStudiomulti-projecttemplatesrenamenamespacesandprojectnames【发布时间】:2011-07-1511:56:45【问题描述】:我已经成功创建了一个多项目解决方案模板。然而,我想做的是用... 查看详情
如何在 Visual Studio 2013 中创建多项目模板?
】如何在VisualStudio2013中创建多项目模板?【英文标题】:Howtocreateamultiprojecttemplateinvisualstudio2013?【发布时间】:2015-02-0704:57:22【问题描述】:我按照Howto:CreateMulti-ProjectTemplates文章创建了一个模板,该模板将生成一个包含4个项目... 查看详情
在 Visual Studio 项目模板中创建解决方案文件夹
】在VisualStudio项目模板中创建解决方案文件夹【英文标题】:CreateaSolutionFolderinaVisualStudioProjectTemplate【发布时间】:2014-06-1507:52:43【问题描述】:我按照以下说明创建了一个多项目VisualStudio模板:http://msdn.microsoft.com/en-us/library/ms... 查看详情
Visual Studio 项目模板多个项目类型
】VisualStudio项目模板多个项目类型【英文标题】:VisualStudioprojecttemplatemultipleProjectTypes【发布时间】:2009-04-2800:34:05【问题描述】:我有一个项目模板,我希望它出现在“VisualC#”及其子类型“Test”下。我可以让它出现在一个而... 查看详情
如何创建 Visual Studio 2008 C++ 项目模板?
】如何创建VisualStudio2008C++项目模板?【英文标题】:HowtocreateaVisualStudio2008C++projecttemplate?【发布时间】:2009-11-1113:34:32【问题描述】:我在C#、ASP.NET、WinForms等项目中多次使用“导出模板”功能。今天我尝试为一个C++项目做这件... 查看详情
Visual Studio 2019 和 IdentityServer 项目模板
】VisualStudio2019和IdentityServer项目模板【英文标题】:Visualstudio2019andIdentityServerprojecttemplate【发布时间】:2019-10-1505:45:34【问题描述】:尝试遵循使用VisualStudio2019的IdentityServer教程。按照说明创建一个.NetCoreWeb应用程序,然后创建... 查看详情
Visual Studio 2010 项目模板不显示
】VisualStudio2010项目模板不显示【英文标题】:VisualStudio2010ProjectTemplatedoesn\'tshowup【发布时间】:2011-04-2521:51:57【问题描述】:我尝试在从vs2010导出项目模板后对其进行编辑,方法是提取它并在编辑后再次压缩它。但之后它不会... 查看详情
使用命令行创建新的 Visual Studio 模板项目
】使用命令行创建新的VisualStudio模板项目【英文标题】:CreatingANewVisualStudioTemplateProjectUsingTheCommandLine【发布时间】:2018-01-1712:16:06【问题描述】:我正在开展一个项目,该项目需要我们在一些RaspberryPi上远程构建。我们使用的是... 查看详情
Visual Studio 2013 中的默认模板项目崩溃
】VisualStudio2013中的默认模板项目崩溃【英文标题】:DefaultTemplateProjectsCrashinginVisualStudio2013【发布时间】:2015-01-0914:32:09【问题描述】:是我做错了什么还是VisualStudio2013默认项目损坏了?创建一个新的基本MVC项目:File->New->P... 查看详情
Visual Studio 2019 MVC 项目模板无法编译
】VisualStudio2019MVC项目模板无法编译【英文标题】:VisualStudio2019MVCprojecttemplatedoesn\'tcompile【发布时间】:2019-08-1216:29:41【问题描述】:我正在使用VisualStudio2019使用.NETFramework创建一个股票MVC项目。我引入了一些具有.NETFramework依赖... 查看详情
NUnit 是不是有 Visual Studio 测试项目模板?
】NUnit是不是有VisualStudio测试项目模板?【英文标题】:IsthereaVisualStudioTestProjecttemplateforNUnit?NUnit是否有VisualStudio测试项目模板?【发布时间】:2011-01-2711:30:54【问题描述】:是否有NUnit的官方VisualStudio测试项目模板?如果不是,... 查看详情
如何创建包含链接文件的 Visual Studio 项目模板?
】如何创建包含链接文件的VisualStudio项目模板?【英文标题】:HowdoIcreateaVisualStudioprojecttemplatethatincludeslinkedfiles?【发布时间】:2011-09-0123:28:17【问题描述】:在VisualStudio2010中,我想创建一个项目模板,其中包含指向系统上应该... 查看详情
如何在 Visual Studio 2010 中删除/卸载项目模板
】如何在VisualStudio2010中删除/卸载项目模板【英文标题】:Howtoremove/uninstallitemtemplatesinVisualStudio2010【发布时间】:2012-01-0507:24:42【问题描述】:我有一个项目模板,我做错了,想删除。我从输出位置删除了zip文件并运行了devenv/in... 查看详情
如何为 SQL 文件、Visual Studio 数据库项目创建项目模板
】如何为SQL文件、VisualStudio数据库项目创建项目模板【英文标题】:HowtocreateitemtemplatesforSQLfiles,forVisualStudioDatabaseProjects【发布时间】:2010-05-0501:54:05【问题描述】:可以为普通项目类型定义您自己的自定义模板,例如用于类库... 查看详情
Visual Studio 2013/2015 测试项目模板 - 用于 NUnit?
】VisualStudio2013/2015测试项目模板-用于NUnit?【英文标题】:VisualStudio2013/2015TestProjectTemplate-forNUnit?【发布时间】:2015-10-1611:00:28【问题描述】:我是单元测试新手,从MSTest开始,想使用NUnit。我看到VS2015中有一个测试项目模板,... 查看详情
如何在 Visual Studio 2015 中成功导出 C++ 项目模板?
】如何在VisualStudio2015中成功导出C++项目模板?【英文标题】:HowtosuccessfullyexportC++projecttemplateinVisualStudio2015?【发布时间】:2016-12-2001:47:26【问题描述】:我完成了在VisualStudio2015中将VisualC++项目导出为模板的步骤。它只包含一个... 查看详情
Visual Studio 2013 Ultimate - 缺少 C++ 项目模板
】VisualStudio2013Ultimate-缺少C++项目模板【英文标题】:VisualStudio2013Ultimate-MissingC++ProjectsTemplates【发布时间】:2015-06-1715:15:58【问题描述】:我安装了我的MicrosoftVisualStudioUltimate2013的全新安装并启动了它。然后我去了FILE->New-&a... 查看详情
Visual Studio 2017 c++ win32 控制台项目模板
】VisualStudio2017c++win32控制台项目模板【英文标题】:VisualStudio2017c++win32consoleprojecttemplate【发布时间】:2017-08-2018:56:47【问题描述】:我在VisualStudioCommunity2017v.15.3.1上,我似乎找不到Win32控制台应用程序或Win32项目。仍然有空的c++... 查看详情