从“项目模板参数化(下)“开始到现在,我们应用IWizard接口的都是WebClient这个子项目,实际情况是,我们怎么对每个项目做定制,或者换个角度,我们这里有三个模板元数据文件,每个都可以定义和配置WizardExtension这个节点。
一,用LifeCycleTracerIWizard跟踪多项目模板的多个子项目
重新配置3个模板元数据文件的WizardExtension节点如下(先不配置ChainingLifeCycleTracerIWizard):
<WizardExtension>
<Assembly>Ethan.Woo.TemplateWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e82b5e824e88ddd5</Assembly>
<FullClassName>Ethan.Woo.TemplateWizard.LifeCycleTracerIWizard</FullClassName>
</WizardExtension>
这样的结果能看出什么规律么?有规律!请看我的分析:
1)蓝色部分是Solution级别的跟踪,即根项目元数据文件所配置的WizardExtension的作用范围。它的执行过程为RunStarted->ProjectFinishedGenerating->RunFinished三步,ShouldAddProjectItem没执行,因为它只包括子项目,不直接包括源文件。
2)绿色和黄色部分分别是WebClient和DataService这两个项目模板实例化过程中创建的IWizard实例的跟踪。跟踪结果和上一篇完全一样。
3)根项目元数据文件配置指定的IWizard接口方法的执行,被子项目的IWizard的生命周期在执行序列上隔开了,而且RunStarted这个方法的第三个参数的枚举值是我们上篇文章里的WizardRunKind.AsMultiProject,表明当前执行的是多个项目的模板实例化。注意,实际上这里配置的三处都用到了Ethan.Woo.TemplateWizard.LifeCycleTracerIWizard,但是实例化出来的是三个单独的实例,它们都是IWizard的实例,从某种意义上来说,根项目元数据文件所对应的的IWizard接口实现的生命周期期限,才是最长的。
这里我给出我自己的一个总结的图示,很直观的表述了之前的结果:
二,将ChainingLifeCycleTracerIWizard也串联过来
我们看看ChainingLifeCycleTracerIWizard串联到3个模板元数据文件的结果,很简单了吧,不多做解释了。
三,IWizard与Visual Studio的自动化。
注意到RunStarted方法的第一个参数是一个Object对象,其实对于Visual Studio 2010这个寄主而言,它实际上的对象类型是DTE2类型,利用这个模型,我们可以操作Visual Studio这个强大的IDE,完成复杂的逻辑操作,这里不展开讨论这个话题,仅提供两个MSDN的相关链接:
Extending the Visual Studio Environment:http://msdn.microsoft.com/zh-cn/library/esk3eey8.aspx
Automation and Extensibility Reference:http://msdn.microsoft.com/zh-cn/library/1xt0ezx9.aspx
好了,对IWizard这个接口的深入讨论,就到这个地方,在这个基础上,我们可以根据我们的需要,定制我们自己的实现,甚至可以定制出一整套使用的框架,专门用于建立我们的复杂项目模板。当然了,最复杂的项目模板实际上我们把它叫做Starter Kits(初学者工具包),它是普通项目模板的增强版,下文会继续介绍。
摘自:http://www.ethan-woo.com/post/2011/05/03/Deep-into-IWizard-Second.aspx
这篇文章,我们来研究一下IWizard这个接口。
一,一个用来跟踪的接口实现LifeCycleTracerIWizard。
因为IWizard接口是Visual Studio来管理实例化和执行方法的,也就是说它的类型实例的生命周期不是我们能够控制的,那么我们可以换一种思路来探讨这样的生命周期,所以我定义这样的一个IWizard实现LifeCycleTracerIWizard,用来跟踪接口里各个方法的执行循序,以及上下文信息,即参数值。
LifeCycleTracerIWizard.cs的代码如下:
1: public class LifeCycleTracerIWizard : IWizard
2: {
3: private string loggerFilePath = @"D:\LifeCycleTracerWizard.log";
4: private string safeProjectName = "[None]";
5:
6: public virtual String WizardType
7: {
8: get { return "LifeCycleTracerIWizard"; }
9: }
10:
11: private void LogWizardMethodAction(string methodAction)
12: {
13: using (StreamWriter streamWriter = new StreamWriter(loggerFilePath,true))
14: {
15: streamWriter.WriteLine(WizardType.PadRight(30) + "-" + safeProjectName.PadRight(15) + ":" + methodAction);
16: }
17: }
18:
19: #region Implementation of IWizard
20:
21: public void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams)
22: {
23: if(replacementsDictionary != null && replacementsDictionary.ContainsKey("$safeprojectname$"))
24: {
25: safeProjectName = replacementsDictionary["$safeprojectname$"];
26: }
27:
28: LogWizardMethodAction("RunStarted : " + runKind);
29: }
30:
31: public void ProjectFinishedGenerating(Project project)
32: {
33: LogWizardMethodAction("ProjectFinishedGenerating");
34: }
35:
36: public void ProjectItemFinishedGenerating(ProjectItem projectItem)
37: {
38: LogWizardMethodAction("ProjectItemFinishedGenerating");
39: }
40:
41: public bool ShouldAddProjectItem(string filePath)
42: {
43: LogWizardMethodAction("ShouldAddProjectItem : " + filePath);
44: return true;
45: }
46:
47: public void BeforeOpeningFile(ProjectItem projectItem)
48: {
49: LogWizardMethodAction("BeforeOpeningFile");
50: }
51:
52: public void RunFinished()
53: {
54: LogWizardMethodAction("RunFinished");
55: }
56:
57: #endregion
58: }
二,用LifeCycleTracerIWizard跟踪多项目模板的子项目。
我们修改上一篇文章中的VSTemplate-WizardExtension-FullClassName子节点值为:Ethan.Woo.TemplateWizard.LifeCycleTracerIWizard,部署后新建实例,查看生成的Log文件“D:\LifeCycleTracerWizard.log”内容:
从结果可以看出以下几点结论:
1)确实是模板元数据文件中指定的Ethan.Woo.TemplateWizard.LifeCycleTracerIWizard接口实现被实例化了。
2)各个方法执行的顺序依次为:RunStarted->ShouldAddProjectItem->ProjectFinishedGenerating->RunFinished,这里注意一下,有两个方法没有执行到,ProjectItemFinishedGenerating是用于项模板向导的,所以这里肯定不会执行;BeforeOpeningFile这个方法也没有执行,因为它在我们制作 Starter Kit的时候才会用到,所以以后会再次讲到这个话题。
3)RunStarted这个方法的第三个参数是一个WizardRunKind枚举(有三个值:AsMultiProject,AsNewItem,AsNewProject),这里跟踪到的值是AsNewProject,表示新建一个项目。
4)ShouldAddProjectItem这个方法执行了多次?是的,我们的项目源里的项目包含了多个子文件,所以,这里明显是在模板实例化阶段,添加每个子文件时调用的,而且它有一个Boolean返回值,表示十分要添加当前的子文件到项目实例中。
三,用多个IWizard接口实现跟踪多项目模板的子项目。
先摘一段来自MSDN的原文:
Visual Studio supports chaining, which enables a single template to have multiple IWizard implementations. These implementations are called sequentially, so you can create templates that have rich, flexible functionality.
A Microsoft-implemented VsTemplate wizard is invoked to process a template by reading its .vstemplate file. A VsTemplate can list one or more assemblies that have an IWizard implementation that will be called to participate in processing the template. To take advantage of chaining, all the wizards must be listed in the template's .xml file in the order in which they should be called.
意思是说:Visual Studio支持单模板的IWizard接口实现的串联,并且是顺序执行的,还能推断出VSTemplate这个节点可以包含多个WizardExtension的设置,实现这样的一种串联。
这里我们新定义一个ChainingLifeCycleTracerIWizard接口实现,继承之前的LifeCycleTracerIWizard;
1: public class ChainingLifeCycleTracerIWizard : LifeCycleTracerIWizard
2: {
3: public override string WizardType
4: {
5: get
6: {
7: return "ChainingLifeCycleTracerIWizard";
8: }
9: }
10: }
然后,我们将这个ChainingLifeCycleTracerIWizard和之前的LifeCycleTracerIWizard一起串联到WebClient项目的模板中,当然了,还是通过模板元数据文件,注意这里的声明的顺序;
1: <VSTemplate xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Version="3.0.0" Type="Project">
2: 。。。此处省略。。。
3: <WizardExtension>
4: <Assembly>Ethan.Woo.TemplateWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e82b5e824e88ddd5</Assembly>
5: <FullClassName>Ethan.Woo.TemplateWizard.LifeCycleTracerIWizard</FullClassName>
6: </WizardExtension>
7: <WizardExtension>
8: <Assembly>Ethan.Woo.TemplateWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e82b5e824e88ddd5</Assembly>
9: <FullClassName>Ethan.Woo.TemplateWizard.ChainingLifeCycleTracerIWizard</FullClassName>
10: </WizardExtension>
11: </VSTemplate>
最后我们看看Log文件“D:\LifeCycleTracerWizard.log”内容:
是不是很有意思,都执行了一遍哦,不用解释了吧。
摘自:http://www.ethan-woo.com/post/2011/05/03/Deep-into-IWizard-First.aspx
本篇主要来介绍一下怎么利用IWizard接口提供的代码向导动态进行参数的设值。
方式三:利用IWizard接口动态设值
1,首先我们先看一下IWizard这个接口,这个接口第一在Microsoft.VisualStudio.TemplateWizardInterface.dll这个程序集中,命名空间为Microsoft.VisualStudio.TemplateWizard,签名如下:
1: public interface IWizard
2: {
3: void BeforeOpeningFile(ProjectItem projectItem);
4: void ProjectFinishedGenerating(Project project);
5: void ProjectItemFinishedGenerating(ProjectItem projectItem);
6: void RunFinished();
7: void RunStarted(object automationObject, Dictionary<string, string> replacementsDictionary, WizardRunKind runKind, object[] customParams);
8: bool ShouldAddProjectItem(string filePath);
9: }
我们暂时先不深入展开这个接口,界于我们讨论的主题,这里我们只要知道两个结论,我先给出这两个结论:
1)当我们用“新建项目”对话框新建项目,按下确定时,Visual Studio会实例化这个接口的实现(Visual Studio怎么知道要实例哪个接口实现呢,下文会给出),并且执行Runstarted这个方法。
2)关于RunStarted方法,这个方法的第二个参数是一个Dictionary<string, string>,通过这个字典,我们可以添加新的Key-Value,Key就是我们要添加的参数,Value就是我们要添加的参数的值,实现IWizard接口动态设值。
2,我们来添加一个TemplateWizard类库,添加EnvDTE和Microsoft.VisualStudio.TemplateWizardInterface这两个程序集引用。我们这个类库是需要强签名的,因为最后要部署到GAC里面。
添加一个DynamicCustomParameterWizard类,让它实现IWizard接口,代码如下:
1: public class DynamicCustomParameterWizard : IWizard
2: {
3: #region Implementation of IWizard
4:
5: public void RunStarted(object automationObject, Dictionary<string, string> replaceme
6: {
7: if (replacementsDictionary != null)
8: {
9: //$CurrentTimeWithFormat$
10: replacementsDictionary["$CurrentTimeWithFormat$"] = DateTime.Now.ToString("y
11:
12: //$AllExistingKeyValues$
13: StringBuilder stringBuilder = new StringBuilder();
14: foreach (var item in replacementsDictionary)
15: {
16: stringBuilder.Append(item.Key.PadRight(40));
17: stringBuilder.Append(item.Value);
18: stringBuilder.Append("=".PadRight(10));
19: stringBuilder.Append("\r\n");
20: }
21: replacementsDictionary["$AllExistingKeyValues$"] = stringBuilder.ToString();
22: }
23: }
24:
25: public bool ShouldAddProjectItem(string filePath)
26: {
27: return true;
28: }
29:
30: public void RunFinished()
31: {
32: }
33:
34: public void BeforeOpeningFile(ProjectItem projectItem)
35: {
36: }
37:
38: public void ProjectItemFinishedGenerating(ProjectItem projectItem)
39: {
40: }
41:
42: public void ProjectFinishedGenerating(Project project)
43: {
44: }
45:
46: #endregion
47: }
当然,我们还要新建一个DynamicCustomParameterExperiment.txt,
CurrentTimeWithFormat = $CurrentTimeWithFormat$
AllExistingKeyValues:
$AllExistingKeyValues$
以及,添加元数据到项目元数据文件中:
<ProjectItem ReplaceParameters="true">DynamicCustomParameterExperiment.txt</ProjectItem>
这时候,该解决前面留下的一个问题了,怎么让Visual Studio知道WebClient这个子项目模板要依赖我们定义好的DynamicCustomParameterWizard实现,既然我们说过,模板元数据文件是纽带,所以这里有一个新的节点可以定义这种接口向导实现,直接定义在VSTemplate根节点下:
<WizardExtension>
<Assembly>Ethan.Woo.TemplateWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e82b5e824e88ddd5</Assembly>
<FullClassName>Ethan.Woo.TemplateWizard.DynamicCustomParameterWizard</FullClassName>
</WizardExtension>
3,部署和使用:项目模板的ZIP包还是和之前一样做法,注意,这里接口向导所在的程序集由于要部署到GAC里,所以,不需要打到这个ZIP包里面。既然Visual Studio有这个GAC部署接口向导程序集的限制,且不是给我们的一站式部署带来了麻烦?是的,确实是个问题,但是可以解决,我们在以后的文章中再专门探讨这个问题。
部署好ZIP包和程序集以后,我们来看下新建项目后的效果:
Tips:如果我们在开发一个复杂的IWizard接口实现,经常需要调试新版本时,手工部署GAC是很麻烦的,这里我推荐使用Build Events机制(Post-build event command line):
"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\gacutil.exe" /u "Ethan.Woo.TemplateWizard, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e82b5e824e88ddd5"
"C:\Program Files\Microsoft SDKs\Windows\v7.0A\bin\NETFX 4.0 Tools\gacutil.exe" /i "$(TargetPath)"
这篇文章还没有到结束的时候,让我们分析一下这个替换的结果,从这个结果可以得出一些新的结论:
1)在RunStarted这个方法开始执行的时候,方式一和方式二指定的参数已经有值,并且被置入这个Dictionary里面,分别对应绿色和灰色。蓝色的部分很明显,是方式三动态加入的。那么红色的部分是哪里来的,我推断最大的可能是,微软MSDN中没有公布出来或者漏掉的系统参数。
2)由于RunStarted这个方法公布了这个Dictionary,并且有结论1,那么,我们完全可以覆盖掉已经存在的参数和值,这在某些特殊场合是有用的。
这篇文章,已经对我们的话题阐述到位了,但是,对于IWizard的讨论,还没有结束,下一篇,我会深入来看一下IWizard这个接口。
摘自:http://www.ethan-woo.com/post/2011/05/02/VisualStudio-Template-Parameterization-Second.aspx
如果我们希望之前的那个项目模板更加实用,比如代码加个版权信息,包括创建时指定的项目名称,创建时间。该怎么办呢?实际上,Visual Studio的项目模板就有这样的一种机制,我个人把它定义为“参数化”机制。
让我们先看一个图,很简单
可以看到,我这里定义的“参数化”是指一个动态的过程,包括项目模板的准备阶段的“参数格式化”,和模板实例化时的附加动作“参数替换”。
参数格式化:在准备阶段(即制作“项目模板源”),我们可以在任意的源文件中,附加一些参数占位符,占位符的格式是 $参数名称$ ,参数名称是大小写敏感的,Visual Studio自己的模板机制只认识这种格式的参数。
参数替换:参数替换是参数化的执行阶段,也是这篇文章的重点,我总结了一下,Visual Studio 的模板有三种参数替换的方式,借助于这些方式,可以让模板的实例化成为现实。
方式一:系统保留的内置参数替换
Visual Studio有一些内置的参数可供我们使用,只要我们在源文件中定义的占位符参数和内置参数名称一致,IDE就会自动帮我们执行参数替换,下表是内置参数列表,摘自MSDN:
我们这里只做一个小实验,看看这些参数替换的结果,我们在之前的模板源里,添加一个BuildInParameterExperiment.txt文件,把这些内置的参数放在里面,让IDE帮我们执行替换,注意一点,这里需要把itemname,safeitemname,rootnamespace这三个排除掉,因为它们是用于项模板的。
同时,WebClient.vstemplate要用ProjectItem节点添加这个新的源文件,而且ReplaceParameters设置为true,告诉Visual Studio,我们要检索这个源文件中的参数,让Visual Studio帮助我们完成参数值的替换。
<ProjectItem ReplaceParameters="true">BuildInParameterExperiment.txt</ProjectItem>
这种方式的局限:即因为它是内置的,也只局限于一些最简单和通用的参数。
方式二:自定义参数以及传递
如果我们定义的参数不是内置的,我们该怎么传递值呢。Visual Studio模板提供了传递自定义参数值的功能。
首先,我们新建一个新的CustomParameterExperiment.txt文件,并且定义两个参数,这里的名字是自己选的:
DeveloperName = $DeveloperName$
ApplicationName = $ApplicationName$
好了,下面我们要做的事情是告诉Visual Studio这些参数的值,让Visual Studio在模板实例化阶段,能够用这些值将我们自定义参数替换掉。怎么做?很简单,将参数添加到TemplateContent节点下的CustomParameters集合下即可。
<CustomParameters>
<CustomParameter Name="$DeveloperName$" Value="Ethan Woo"/>
<CustomParameter Name="$ApplicationName$" Value="Two Layer Template Parameter Experiment Application"/>
</CustomParameters>
重新打包和部署,利用这个模板新建项目实例,看到结果:
这种方式的局限:很明显,就是参数值是写死了的,实际需求往往是需要和用户进行交互,捕捉到用户的输入,然后处理。能办到吗?答案是肯定的,必须相信微软!
为了增强易读性,将这个方式放到下一篇专门阐述。
摘自:http://www.ethan-woo.com/post/2011/05/02/VisualStudio-Template-Parameterization-First.aspx
现在,我们来手工创建一个项目模板,并且研究一下部署和使用的问题。这里我们还是用上一篇初始准备的那个模板源。
1,认识模板元数据文件。
关于模板元数据文件的格式,MSDN有详细解释,请参照这两个链接:
Visual Studio Template Metadata Files:http://msdn.microsoft.com/en-us/library/xsxc3ete.aspx
Visual Studio Template Schema Reference :http://msdn.microsoft.com/en-us/library/xwkxbww4.aspx
模板的Schema还是很复杂的,所以,这里只会对我们的例子中涉及的常用节点做一些介绍,顺便提一下,模板元数据也有自己的XSD验证文件,默认位置如下:
C:\Program Files\Microsoft Visual Studio 10.0\xml\Schemas\1033\vstemplate.xsd
如果利用Visual Studio打开这个XSD可以很直观的看到它对元数据的格式要求进行了详细的定义。
2,手工创建多项目模板的最佳实践之一。
首先,这里我组织的示例结构是优化后的方案,可以看作最佳实践之一,如下图所示。
我们用一个.sln文件来组织并作为我们的项目文件,实际上项目模板的ZIP包是不需要.sln文件的,这里我用它来统一管理模板中的各个元素。当然我们这样做也有一个不算缺点的小缺点,后面再说。
当然了,这里看到的并不是实际的物理结构,物理文件结构可以参考下图:
组织好这个逻辑结构后,剩下的就编写.vstemplate文件,用它来串联项目模板内容的方方面面。
TwoLayerTemplate.vstemplate代码:
<?xml version="1.0" encoding="utf-8"?>
<VSTemplate xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Version="3.0.0" Type="ProjectGroup">
<TemplateData>
<Name>Two Layer Web Application</Name>
<Description>Provide a two layer solution for web development</Description>
<ProjectType>CSharp</ProjectType>
<Icon>TwoLayerTemplateIcon.png</Icon>
<PreviewImage>TwoLayerTemplatePreview.png</PreviewImage>
<DefaultName>TwoLayerWebApp</DefaultName>
</TemplateData>
<TemplateContent>
<ProjectCollection>
<ProjectTemplateLink ProjectName="WebClient">WebClient\WebClient.vstemplate</ProjectTemplateLink>
<ProjectTemplateLink ProjectName="DataService">DataService\DataService.vstemplate</ProjectTemplateLink>
</ProjectCollection>
</TemplateContent>
</VSTemplate>
WebClient.vstemplate代码:
<?xml version="1.0" encoding="utf-8"?>
<VSTemplate xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Version="3.0.0" Type="Project">
<TemplateData>
<Name>WebClient</Name>
<ProjectType>CSharp</ProjectType>
</TemplateData>
<TemplateContent>
<Project File="WebClient.csproj">
<ProjectItem>Properties\AssemblyInfo.cs</ProjectItem>
<ProjectItem>Default.aspx</ProjectItem>
<ProjectItem>Default.aspx.cs</ProjectItem>
<ProjectItem>Default.aspx.designer.cs</ProjectItem>
<ProjectItem>Web.config</ProjectItem>
</Project>
</TemplateContent>
</VSTemplate>
DataService.vstemplate代码:
<?xml version="1.0" encoding="utf-8"?>
<VSTemplate xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Version="3.0.0" Type="Project">
<TemplateData>
<Name>DataService</Name>
<ProjectType>CSharp</ProjectType>
</TemplateData>
<TemplateContent>
<Project File="DataService.csproj">
<ProjectItem>Properties\AssemblyInfo.cs</ProjectItem>
<ProjectItem>DataService.cs</ProjectItem>
</Project>
</TemplateContent>
</VSTemplate>
每一个vstemplate元数据文件都包含一个<VSTemplate>根节点以及<TemplateData>和<TemplateContent>两个子节点。
1)VSTemplate根节点有两个属性:
Version属性:用于指定模板的版本,根据MSDN参考,VS2010使用3.0.0;
Type属性:用于指定模板的目标类型,常用三种值:Item,Project和ProjectGroup。其中Item表示项模板,Project表示单项目模板,ProjectGroup表示多项目模板。
2)TemplateData节点用来组织模板本身的信息,提供“新建项目”对话框中显示的元数据,这些显示的要点可以在上一篇的截图中看出来,其实这些内容大多和项目向导填写信息那一步做的事情差不多,不过这里可以做到更多,比如说DefaultName属性,指定以后可以作为新建项目对话框里名称的默认值出现,实际上默认显示的是[DefaultName]+N格式,如下图:
3)TemplateContent主要节点用来组织模板源,说到这里就不得不解释一下"TwoLayerTemplate.vstemplate"和"WebClient.vstemplate","DataService.vstemplate"的关系了。
这里,我个人定义一个新概念“根模板元数据文件”,表示这个元数据结构比较特殊,它用来索引每个单独的项目“模板元数据文件”,我个人也给它一个新的概念“子模板元数据文件”,这里的"TwoLayerTemplate.vstemplate"就是这个根文件,ProjectCollection作为TemplateContent的子节点,用来存放ProjectTemplateLink的集合,ProjectTemplateLink顾名思义,只是一个链接,链接到其他两个子模板元数据文件。多项目模板包含的实际子项目甚至可以是不同编程语言项目,但是整个多项目模板只能有一个总的类别,在根模板元数据文件的TemplateData下的ProjectType属性指定。
ProjectTemplateLink的属性ProjectName要提一下,单项目模板的最终项目名称是由“新建项目”对话框指定的,但是多项目模板的各个子项目,最终项目名称不可能逐个指定,所以这里直接用这个属性指定了。
另外,对于其他两个“子模板元数据文件”,它们的TemplateContent节点下的配置方法是相同的,Project节点引用“模板源”里的.csproj文件,ProjectItem子节点用来索引这个项目里援引的所有源文件,唯一要注意的是要保证相对路径都要正确。
至此,可以用下面的图示来表示一下这个项目模板相关内容的一个逻辑结构。
这时,我们离概念上的项目模板还差一步,打包,其实很简单,选中要打包的内容,发送到压缩文件夹即可。这里我将TwoLayerTemplate.sln文件排除了,这就是我在第2步提到的那个“小缺点”,因为这个文件对于项目模板来说不需要,当然,如果误打到包里面,也是能够Working的。
3,部署和使用效果。
因为这里的“多项目模板”中“根模板元数据文件”定义的ProjectType是CSharp,所以我们部署的时候要放到如下目录里:
C:\Users\<CurrentUser>\Documents\Visual Studio 2010\Templates\ProjectTemplates\Visual C#
否则在“新建项目”对话框的类别里表里会找不到我们的模板,下面我们来看看效果:
可以查看命名空间文件结构,来验证模板的适用性。当然了,我们这里举的是很简单的例子,适用的项目模板可能会很复杂,定制程度会更高。下一篇我们继续探讨。
摘自:http://www.ethan-woo.com/post/2011/04/24/First-VisualStudio-Template-Manually.aspx
前文已经提及到我们这个系列主要先讨论项目模板的创建,创建项目模板有两种方法,向导创建,还有就是手工创建。
现在让我们来创建一个简单的项目模板,通过这个简单的例子,一并了解一下两种创建方式的创建过程。
这篇文章先看第一种创建方法,向导创建。
1,模板的创建准备:
模板本质上来自于一个模板源(这里我自己定义了一个概念,表示模板的准备的内容),所以我们要首先做出这样的一个源,假设公司现在有很多Web项目要做,很多项目组开发Web项目第一步,就是要创建项目的多层框架,这些层之间有较复杂的关系,每个层还会引用不同的程序集,如果有一个通用的模板,项目组搭框架不是很轻松?Web开发有很多模式和层次,这个不是此系列的主题,为了例子能够简化并且能帮助我说明这篇文章关注的问题,假设这些项目都需要一个相同的两层简易架构。
先新建一个这样的两层架构的模板源文件,这里称它为模板源,还因为它只是制作模板的第一步,第一步做出来的东西还不叫项目模板,因为Visual Studio 模板机制不认识它。
这个模板由一个Class Library,和一个Web项目组成。
修改好命名空间以适合需要
最后的结构如下
2,使用Visual Studio的“导出模板”向导创建项目模板。
按照“File”->“Export Template”打开向导->选择“Project Template”,打开第一步
从最下方我们可以看到,向导制作项目模板的最大缺点,就是只能对单个Project制作一个模板,不管它的这个限制,我们只看一下生成的东西是什么。所以果断选择DataService这个项目进入下一步。
这些所填的内容项不解释了,因为可以一眼看出来意思。这里的两个勾选框稍微解释一下,第一个表示生成之后自动拷贝到用户模板目录(可以参考前面一篇文章),第二个表示生成好之后自动打开上面的“Output location”的目录,实际上可以自己验证一下,确实如此。
我们可以顺便看研究一下这个DataServiceTemplate.zip,它的名字是我们之前定义的"Template name",并且它是一个标准的ZIP包(其他的压缩格式是不支持的),我们解压这个包,看看里面的内容。
我先给出结论,这个包里有3个部分组成,模板源(DataService.csproj指向的项目,打开它就是我们之前做的模子)+模板元数据文件(MyTemplate.vstemplate)+模板支持文件(两个png图片)。模板元数据文件是核心,用来组织所有其他部分。
这时候,我们打开新建项目向导,发现了我们的模板出现了。同时注意到它的图标和预览图都是我们制作时设置的图片,另外请注意截图中红色箭头标识的一些点,下面的系列中会涉及到这些相关点。
我们用这个项目模板创建一个项目,发现就是我们定义的模板的效果,命名空间,包含的文件,添加的引用等等,我们可以自己逐个验证一下。
3,结论:
1,向导创建模板的方法很简单和直观,很快就能创建出想要的模板,并且部署也很方便快捷,只需要知道一些基本的概念,即第一篇介绍的内容,就可应用到项目中。这里没有介绍制作项模板向导,大家可以尝试一下,同时体验一下两者概念,制作和使用上的区别。
2,向导的方式有个最大的缺点,就是只能做基于单个项目的项目模板,对于基于多个项目的模板,就无能为力了。
3,模板包是一个ZIP包,包含模板源文件,模板元数据文件,模板支持文件三个部分,模板元数据文件用来索引其他所有模板包的内容。
摘自:http://www.ethan-woo.com/post/2011/04/24/First-VisualStudio-Template-By-Wizard.aspx
之前一部分时间,由于工作中需要,一度接触Visual Studio的模板这个主题,也是颇有一些心得,写这个系列出于要做一个PPT在小组内部做一个Share Session,所以有必要重新梳理一下对这个主题的认识,让它更加清晰和理论化。
一,Visual Studio 模板分类
Visual Studio 的模板有两种类型项目模板和项模板。
项模板:是用户可以通过使用“添加新项”对话框添加到项目中的各个项。项模板根据包含的项的个数分为单文件项模板和多文件项模板。(项模板会在这个系列的最后介绍,其实相对于项目模板,更加简单)
项目模板:是整个项目,用户可以使用“新建项目”对话框从项目模板来创建新项目。项目模板包括开始特定类型的项目所需的所有文件。项目模板根据包含的项目的个数分为单项目模板和多项目模板。
二,Visual Studio 模板的加载途径
我们在做项目的过程中很少会担心我们建不了合适的项目,因为我们直接通过创建的向导就可以选择并且创建我们需要的项目类型,Visual Studio是怎么帮我们列出这些模板的列表的呢,或者说是从哪里找到这些模板的供我们选择的呢?
1,Visual Studio通过索引列出本地安装的模板。
1) Visual Studio自带的模板,安装在Visual Studio的产品子目录:
<VisualStudioInstallDir>\Common7\IDE\ProjectTemplates\Language\Locale
<VisualStudioInstallDir>\Common7\IDE\ItemTemplates\Language\Locale
注意:如果偶然遇到模板丢失的情况,可以在VS的命令行里执行 devenv.exe /InstallVSTemplates 来重新安装模板。
2,用户模板目录,默认是如下两个用户级别路径:
My Documents\Visual Studio 2010\Templates\ProjectTemplates\Language
My Documents\Visual Studio 2010\Templates\ItemTemplates\Language
当然了,用户模板的路径是可以配置的,如下: ![]()
3,Microsoft寄主的在线的模板库。
通过“新建项目”对话框
或者,“新建项”对话框
下一篇,我们就从一个最简单的例子开始,继续我们的这个系列。
摘自:http://www.ethan-woo.com/post/2011/04/21/VisualStudio-Template-Introduction.aspx
继续Visual Studio自定义模板(二),要想实现动态的自定义模板参数,还要回到vstemplate文件,在该文件中,我们可以配置自己的向导扩展WizardExtension, 通过WizardExtension,我们就可以在向导生成文件的过程中加入自定义的代码了。
首先我们实现一个简单的自定义Wizard,
namespace MyTemplate
{
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TemplateWizard;
publicclass MyWizard : IWizard
{
///<summary>
/// Runs custom wizard logic before opening an item in the template.
///</summary>
///<param name="projectItem">The project item that will be opened.</param>
publicvoid BeforeOpeningFile(EnvDTE.ProjectItem projectItem)
{
}
///<summary>
/// Runs custom wizard logic when a project has finished generating.
///</summary>
///<param name="project">The project that finished generating.</param>
publicvoid ProjectFinishedGenerating(EnvDTE.Project project)
{
}
///<summary>
/// Runs custom wizard logic when a project item has finished generating.
///</summary>
///<param name="projectItem">The project item that finished generating.</param>
publicvoid ProjectItemFinishedGenerating(EnvDTE.ProjectItem projectItem)
{
}
///<summary>
/// Runs custom wizard logic when the wizard has completed all tasks.
///</summary>
publicvoid RunFinished()
{
}
///<summary>
/// Runs the started.
///</summary>
///<param name="automationObject">The automation object.</param>
///<param name="replacementsDictionary">The replacements dictionary.</param>
///<param name="runKind">Kind of the run.</param>
///<param name="customParameters">The custom parameters.</param>
publicvoid RunStarted(object automationObject,
Dictionary<string, string> replacementsDictionary,
WizardRunKind runKind,
object[] customParameters)
{
replacementsDictionary.Add("$MyDate$", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
}
///<summary>
/// Indicates whether the specified project item should be added to the project.
///</summary>
///<param name="filePath">The path to the project item.</param>
///<returns>
/// true if the project item should be added to the project; otherwise, false.
///</returns>
publicbool ShouldAddProjectItem(string filePath)
{
returntrue;
}
}
}
至于IWizard各个方法的具体定义,请大家参考MSDN,这里,我在RunStarted方法中添加了一个自模板参数$MyDate$,它的值为DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),也就是说在模板文件中所有$MyDate$都会被替换成指定的具体的日期字符串。
下一步就是在vstemplate文件中加入我们定义的Wizard的配置,VS有一个要求,所有向导扩展都必须安装在GAC中,所以我们要强命名我们刚才生成的程序集,安装到GAC中。首先在[我的文档]\Visual Studio 2005\Templates\ItemTemplates中找到我们在2中生成的模板,解压后修改vstemplate文件如下,
…
<WizardExtension>
<Assembly>MyTemplate, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5ea13aa0d9868d4b</Assembly>
<FullClassName>
MyTemplate.MyWizard
</FullClassName>
</WizardExtension>
</VSTemplate>
注,上面Assembly的配置信息要根据具体的强命名信息作适当更改。
继续Visual Studio自定义模板(一)的自定义模板的话题,我们进行定义更完美的模板。首先我们来分析一个Visual Studio模板文件的构成。Visual Studio 2005导出的模板的位置是在“[我的文档]\Visual Studio 2005\My Exported Templates\”文件夹中,而对于项模板,要想应用到“添加新项”的对话框中,必须把模板copy到“[我的文档]\Visual Studio 2005\Templates\ItemTemplates”。
我们可以看到一个模板文件就是一个zip文件,解压该文件,我们会看到三个文件,.ico, .cs, .vstemplate,ico是显示在“添加新项”对话框中显示的图标,.cs文件就是我们定义的模板原文件,而vstemplate文件,是一个xml文件,该文件定义了模板的元数据,对于Visual Studio 2005,该文件的schema文件保存在“[Visual Studio InstallDirectory]\Xml\Schemas\2052\vstemplate.xsd”中,下面是该文件的一个原型,
<VSTemplate Version="2.0.0" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" Type="Item">
<TemplateData>
<DefaultName>MyClassTemplate.cs</DefaultName>
<Name>MyClassTemplate</Name>
<Description><没有可用的说明></Description>
<ProjectType>CSharp</ProjectType>
<SortOrder>10</SortOrder>
<Icon>__TemplateIcon.ico</Icon>
</TemplateData>
<TemplateContent>
<References />
<ProjectItem SubType="Code" TargetFileName="$fileinputname$.cs" ReplaceParameters="true">MyClassTemplate1.cs</ProjectItem>
</TemplateContent>
</VSTemplate>
至于各个结点的说明,大家可以参考上面提到的schema文件,或者msdn(ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vsxmlref/html/6f74a2d5-3811-43d6-8b10-eb5823ad8995.htm),在<TemplateContent>结点下有一个子结点<CustomParameters>,在该结点下,我们可以方便的定义自己的模板参数,如下,
<TemplateContent>
...
<CustomParameters>
<CustomParameter Name="$MyParameter1$" Value="MyValue1"/>
</CustomParameters>
...
</TemplateContent>
上面我们定义了一个参数$MyParameter1$,该参数的值为MyValue1,自定义的模板参数跟VS提供的默认模板参数的使用方法一样,我们可以直接在模板原文件中使用,如下所示:
#region Copyright (C) Rainsoft All rights reserved
/*******************************************************************************************
* Creation:
* Author: $username$
* Date: $time$
* Description: $MyParameter1$
* Version:
* Modification:
* Author:
* Date:
* Description:
* Version:
*******************************************************************************************/
#endregion
namespace $rootnamespace$
{
using System;
public class $itemname$
{
}
}
我们在Description中使用了我们刚才定义的模板参数$MyParameter1$,VS在根据该模板生成新项时,会自动的把我们定义的模板参数定义成相应的值,但这个值只能是静态的值,不能动态生成,比如在很多公司中登录windows的用户名可能是公司的内部员工编号,不适合作文件头定义中的Author项,通过定义模板参数,我们就可以解决这个问题,但对于自定义格式的Date项,这种方式还不行,下一篇文章我将提供最终的解决方法。
每个公司都有自己的编码标准,其中最基本的一条就是文件要有文件头,但是Visual Studio中默认的模板是没有任何文件头信息的,这就需要我们定义自己的模板。
Visual Studio里的模板分为两类,项目模板和项模板,项目模板就是我们在添加项目是应用的模板,如Windows应用程序,类库等等,出现在“添加新项目”的对话框中,如下图,
我们在一个项目中添加新项时,比如添加一个类,一个接口,等等,这是应用的模板是项模板,出现在“添加新项”的对话框中,如下图,
这篇文章主要介绍如何自定义项模板。
在Visual Studio中定义自己的模板有一个非常快捷的方式,先新建一个文件,定义我们想要的模板格式,再通过[文件]菜单下的[导出模板]子菜单生成一个模板,如下图,
下面是我定义的一个简单的模板,
#region Copyright (C) Rainsoft All rights reserved
/*******************************************************************************************
* Creation:
* Author: $username$
* Date: $time$
* Description:
* Version:
* Modification:
* Author:
* Date:
* Description:
* Version:
*******************************************************************************************/
#endregion
namespace $rootnamespace$
{
using System;
public class $itemname$
{
}
}
通过[导出模板]->选择项模板,就可快速的生成一个自定义模板,生成完后,在“添加新项”的对话框中就会出现我们刚才定义的模板,如下图,
下面是通过该模板生成的一个类,
#region Copyright (C) Rainsoft All rights reserved
/*******************************************************************************************
* Creation:
* Author: Arrui
* Date: 2008-10-6 19:33:51
* Description:
* Version:
* Modification:
* Author:
* Date:
* Description:
* Version:
*******************************************************************************************/
#endregion
namespace MyTemplate
{
using System;
public class MyClassTemplate2
{
}
}
在我们刚定义的模板文件中,用到了$rootnamespace$, $username$等一些模板参数,这些都是Visual Studio给我们提供的一些默认的模板参数,这些参数有
| Parameter | Description |
| clrversion | Current version of the common language runtime (CLR). |
| GUID [1-10] | A GUID used to replace the project GUID in a project file. You can specify up to 10 unique GUIDs (for example, guid1). |
| itemname | The name provided by the user in the Add New Item dialog box. |
| machinename | The current computer name (for example, Computer01). |
| projectname | The name provided by the user in the New Project dialog box. |
| registeredorganization | The registry key value from HKLM\Software\Microsoft\Windows NT\CurrentVersion\RegisteredOrganization. |
| rootnamespace | The root namespace of the current project. This parameter is used to replace the namespace in an item being added to a project. |
| safeitemname | The name provided by the user in the Add New Item dialog box, with all unsafe characters and spaces removed. |
| safeprojectname | The name provided by the user in the New Project dialog box, with all unsafe characters and spaces removed. |
| time | The current time in the format DD/MM/YYYY 00:00:00. |
| userdomain | The current user domain. |
| username | The current user name. |
| webnamespace | The name of the current Web site. This parameter is used in the Web form template to guarantee unique class names. If the Web site is at the root directory of the Web server, this template parameter resolves to the root directory of the Web Server. |
| year | The current year in the format YYYY. |
(from msdn: ms-help://MS.VSCC.v90/MS.MSDNQTR.v90.en/dv_vssoln/html/1b567143-08c6-4d7a-b484-49f0671754fe.htm)
注意,这些模板参数都是大小定敏感的。
一般来说,通过这些模板参数足够我们定义一些我们日常通过的模板,但对一些完美主义者,(比如我,:-) ),这些还不够,比如我希望生成的日期的格式不带时间,而且月/日都是固定的两位,中间用.分隔,如2008.10.06,由于Visual Studio默认提供的模板参数不能让我们自定义格式,要想实现上面的功能,我们就需要自定义模板参数,这也是我下一篇文章的主题。

