1. 预备知识
单个基本模版制作: VS自定义项目模版
多个工程模版制作:Creating project template using VSIX extension for Visual Studio 2012
自定义VS的MVC的基架: Creating a Custom Scaffolder for Visual Studio
-
创建VSPackage:
- Creating a VSPackage
- VS2013在右键菜单添加命令插件开发
制作工程或项目模版的时候还会用到IWizard这个接口,主要控制在模版生成的时候动态的做一些事情: How to: Use Wizards with Project Templates, 当然这个程序集放到必须放到全局缓冲区才能在模版生成的时候调用
Nuget基本概念和使用: https://www.nuget.org/
-
全局缓冲区(GAC)本地路径:
C:\Windows\Microsoft.NET\assembly\GAC_MSIL
-
VS插件扩展路径:
C:\Users\win7831\AppData\Local\Microsoft\VisualStudio\12.0\Extensions
-
VS工程运行时临时文件路径:
C:\Windows\Microsoft.NET\Framework\v4.0.30319\Temporary ASP.NET Files
T4模版基本使用 : 你必须懂的 T4 模板:深入浅出
2. 预备工具
VS开发包(必须) :Visual Studio Software Development Kit (SDK)
模版制作的VS插件(必须): SideWaffle Template Pack
T4模版调试工具 :tangible T4 Editor 2.3.0
基于MVC5的基架插件 : Happy Scaffolding for MVC5
Nuget包制作及使用: NuGetPackageExplorer
-
VS开发者工具,可以在安装目录下找到,具体示版本而定, 里面可以使用下面工具,就不用全世界去找了
- 本机工具地址:
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\Shortcuts\Developer Command Prompt for VS2013
- 程序集加入全局缓冲区工具: gacutil.exe
- 程序集生成公钥标记的签名工具 : sn.exe
- 本机工具地址:
打包.NET程序的第三方插件: InstallShield Limited Edition
-
Wix Toolset 制作安装包工具:
- Wix Toolset安装包(必须)
- VS2013插件(必须)
- 参考: Installing VSIX package via WiX installer
VS 2013压缩脚本插件(必须): Web Essentials 2013.5
3. 工程结构
- 工程说明图
[图片上传失败...(image-d1b4fd-1571983320144)]
- 工程依赖图
[图片上传失败...(image-e12d1b-1571983320144)]
- 新建插件工程
[图片上传失败...(image-4ac5a6-1571983320144)]
4. VanGo.VSIX制作步骤
对于初学着来说,很多都是非常细小的步骤或者配置的问题,这里不做详细的每一步的制作,细节太多,但是上面的预备知识中有步骤需要一步一步做的例子,这非常有用,本人也是基于上面别人的资料来做的,下面的步骤只是针对自己制作过程中的大步骤以及自己遇到的问题的总结
VSIX: 首先得了解VanGo平台的模版都是VS的插件,VS可以通过各种插件来扩展功能, 这里有一些VSIX的概念,下图展示,VSIX是一个标准的VS插件安装包类型,不同的基于VSIX的工程从本质上来说只是默认生成VS框架设计的指定模式,包括默认引用的程序集, 定义各种插件的配置文件,以什么样的形式导入VS而已,VanGo平台中就用到下面四种不同的工程实现
[图片上传失败...(image-f0f424-1571983320144)]-
ProjectTemplate: VanGo插件安装最基本的就是模版制作,分为ProjectTemplate和ItemTemplate,他们的工程结构类似,实现原理相同,只是VSIX引入的时候区分,VS才能在不同时候使用不同模版,他们都基于一个重要的文件类型(.vstemplate),这个文件定义了模版的基本信息,参考MSDN, 下图显示VanGo平台下的VanGo.ProjectTemplate.vstempalte文件,注意里面类似的模版参数,VS这样定义可以在生成模版时根据具体用户输入信息等定义模版内容,而且模版参数可以在模版文件的任何位置使用,参考MSDN
[图片上传失败...(image-e99956-1571983320144)]
- 自己遇到的坑,上图中的.Application这个模版参数,在传入VanGoProject.Application.vstemplate模版后所有的成了上面的值,比如用户输入Test, 工程名就为Test.Application,而模版中的所有不是Test而是Test.Application(Stackoverflow Issue),要高版本才支持,可是我升级了还是不能用CopyParameters这个参数,替代方法,使用向导WIzard在进入模版前拦截,手动又把改回来
public void RunStarted(object automationObject, Dictionary
replacementsDictionary, WizardRunKind runKind, object[] customParams) { if (replacementsDictionary.ContainsKey("$safeprojectname$")) { string[] tempName = replacementsDictionary["$safeprojectname$"].Split('.'); replacementsDictionary["$safeprojectname$"] = tempName[0]; } } - 一个工程导出为模版的时候会自动生成.vstemplate这个文件,这样比较方便,不用每一个文件添加,但是生成的模版一定是不符合我们的要求的,需要自己更改, 模版的基本信息,哪些文件名需要生成时重命名,哪些文件可以用模版参数进行替换等等,需要耐心修改,下面只是示例
_Layout.cshtml _Layout.js Error.cshtml VanGoProjectWebViewPageBase.cs web.config
- 自己遇到的坑,上图中的.Application这个模版参数,在传入VanGoProject.Application.vstemplate模版后所有的成了上面的值,比如用户输入Test, 工程名就为Test.Application,而模版中的所有不是Test而是Test.Application(Stackoverflow Issue),要高版本才支持,可是我升级了还是不能用CopyParameters这个参数,替代方法,使用向导WIzard在进入模版前拦截,手动又把改回来
-
TemplateWizard: 模版文件是静态的,怎么在模版生成时动态控制模版生成,就要用到模版向导,基本逻辑是这样的:
新建Wizard向导工程,继承IWizard接口,实现方法,注意IWizard定义的方法的使用及调用顺序,其次重要的是EnvDTE的使用,这个理解为运行时VS的一个实例对象,这样就可以对VS和模版为所欲为了,参考GitHub: 例子
为向导程序集增加签名并生成PublicTokenKey(只是程序集签名的一个缩写标识而已,用来作为gac中的唯一标识,相当于GUID)
模版.vstemplate文件中指定用程序集作为向导,这里用到了PublicTokenKey来引用
-
把向导程序集放入GAC中,下面显示详细步骤
签名步骤
[图片上传失败...(image-6bdfb6-1571983320144)]
引用方式
[图片上传失败...(image-ff4808-1571983320144)]
GAC路径
[图片上传失败...(image-b82d8e-1571983320144)] 替代放入GAC中方法 : 今天思考为什么一定要把TemplateWizard放入gac中,这样的话VSIX不能把程序集添加到GAC中,还要重新做一个MSI之类的安装包,参考Visual Studio Extension Deployment,最后发现VSIX中把TemplateWizard按照Assembly的assets加载进来,在创建模版时同样可以引用到此程序集,参考 How to properly embed a custom wizard assembly in VSIX template project in VS 2013?,这样感觉非常完美,就不用通过命令行把程序集添加到GAC中了
-
vstemplate中的WizardExtension中使用IWizard的dll扩展中replacementsDictionary所有值, 调用方法dll和值的情况如下:
- 注意: 在下面值中我修改了一个值和添加了一个值, 但是在接下来的application的模版生成中这些变化没有带到application里面, 说明两者之间是独立的,不能跨项目传值, 即使在父模版下面多个子模版也是不行的, 改变的值只能在当前模版内的工程里用
VanGoTemplateWizard, Version=1.0.0.0, Culture=Neutral, PublicKeyToken=270c7df44e506ac1 VanGoTemplateWizard.WebChildWizard WebChildWizard: 键: $guid1$值: 01e6bc37-1f12-4a53-8a8f-b81b20baedc6 // 中间有10个guid,省略 WebChildWizard: 键: $guid10$值: 7e18b938-e50a-4326-9c4b-51cc5490d3fe WebChildWizard: 键: $time$值: 2/24/2017 10:05:17 AM WebChildWizard: 键: $year$值: 2017 WebChildWizard: 键: $username$值: win7831 WebChildWizard: 键: $userdomain$值: win7831-PC WebChildWizard: 键: $machinename$值: WIN7831-PC WebChildWizard: 键: $clrversion$值: 4.0.30319.36373 WebChildWizard: 键: $registeredorganization$值: ChangeValue(自己改的值,原来为空) WebChildWizard: 键: $runsilent$值: False WebChildWizard: 键: $wizarddata$值: (vstempalte中自己定义的WizardData下面的所有信息包含于此)
... ... WebChildWizard: 键: $solutiondirectory$值: c:\users\win7831\documents\visual studio 2013\Projects\VanGoWeb7\ WebChildWizard: 键: $projectname$值: VanGoWeb7 WebChildWizard: 键: $safeprojectname$值: VanGoWeb7 WebChildWizard: 键: $currentuiculturename$值: en-US WebChildWizard: 键: $installpath$值: C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE WebChildWizard: 键: $specifiedsolutionname$值: WebChildWizard: 键: $exclusiveproject$值: False WebChildWizard: 键: $destinationdirectory$值: c:\users\win7831\documents\visual studio 2013\Projects\VanGoWeb7\VanGoWeb7\VanGoWeb7 WebChildWizard: 键: $targetframeworkversion$值: 4.5.1 WebChildWizard: 键: AddValue值(自己添加的值): 8ae594fd-d6c4-46e9-9053-6a043ae0b82d
-
Scaffolder: 基于MVC的基架或脚手架,在VSIX引用中属于MEF,同样也会被放入到全局缓冲区,我们可以在GAC的路径中看到他,但是这个是VSIX安装时自动拷贝进去的,所以才会有上面替代gac的思考,VanGo.Scaffolder是基于Sidewaffle插件新建的插件工程,可以快速开发基于MVC的基架,基本逻辑实现如下
继承微软的基架代码工厂类CodeGeneratorFactory, 定义代码生成的信息CodeGeneratorInformation, 创建代码生成实例
继承CodeGenerator, 在覆盖方法中创建UI控件, 代码生成具体控制
UI中使用WPF的MVVM来绑定变量,代码生成中使用T4模版来实现模版的生成
-
自己遇到的坑: 在生成模版的时候,由于需求要生成嵌套的模版,基于T4模版不能实现,所以只好手动获取DTE实例,再自己写入文件流的方式来动态的添加嵌套模版,这种方法虽然不规范,但也是无奈之举,相当于只有一个cshtml页面用到了标准的T4模版,T4模版的好处相当于在模版中无障碍使用c#,实现动态生成模版的效果,就如上面的ProjectTempalte中的Wizard的功能,十分酷炫,也是MVC生成模版的标准形式,简单示例如下:
<#@ template language="C#" HostSpecific="True" #> <#@ output extension=".cshtml" #> <#@ include file="Imports.include.t4" #> <# // The following chained if-statement outputs the file header code and markup for a partial view, a view using a layout page, or a regular view. if(IsPartialView) { #> <# } else if(IsLayoutPageSelected) { #> @{ ViewBag.Title = "<#= ViewName#>"; <# if (!String.IsNullOrEmpty(LayoutPageFile)) { #> Layout = "<#= LayoutPageFile#>"; <# } #> }
欢迎使用VanGo平台
<#= ViewName#>
<# } else { #> @{ Layout = null; }<#= ViewName #> <# PushIndent(" "); } #> <# if(!IsPartialView && !IsLayoutPageSelected) { #> VanGo.Scaffolder类图关系
[图片上传失败...(image-12f4c8-1571983320144)]
-
NuGet: 专门写一下,因为自己花了太多时间去了解它的使用,以及依赖方法,在模版中继承Nuget包,自己制作nuget包,自己被坑的地方也很多,大致有如下
- 吐槽: 在制作模版是要了解工程依赖的nuget包关系, 这些nuget包有来自package的包,有来自.net本来就有的包,也有奇葩的本来.net就有的包,但是mvc5需要高版本还是什么原因又依赖于package中下载的,一团乱麻,导致我做了一张依赖关系图, 更坑的是很多library的名字和package中文件夹的名字不一样,十分想骂人,这就算了, 各种library名字看着很重复,多个s,少个s,不同工程中的nuget包依赖的版本不一样,但是在web的工程中的web.config又可以指定老版本统一调用某一个版本,各种模版建好后library程序集加载错误的bug,真是想骂人,一个感觉哪里都有nuget,哪里的nuget都不一样,因为一个依赖System.Runtime的库在模版制作好后就是加载报错,排查了一个星期左右,连微软都说这是.net的bug,下了补丁还是不行,改IIS的运行时还是不行, 清理工程运行时缓存还是不行, 最后发现在模版生成的时候, 在web.config中因为我在csproj文件中引用了此类库,不知什么时候多加了一个运行时Assembly的标签,导致的奇葩报错,真是要跪了,吐槽归吐槽,还是总结一下在制作VISX中需要用到nuget的地方以及注意事项
[图片上传失败...(image-725b3b-1571983320145)] - csproj文件使用nuget: 因为是基于MVC的工程制作的ProjectTempalte,所以在依赖的地方全是标准的nuget依赖方法,类库都放在packages文件夹下,这是nuget生成的标准文件夹,如下所示,指定了路径的Refernece就是nuget还原的包,而没指定的则是.net中自带的程序集,指定工程版本会引用到不同的位置,就像gac一样的,还要注意MSbuild文件类型中Reference下面的标签使用,参考Common MSBuild Project Items
- Include: 指定依赖的程序集,包含Version,Culture,PublicKeyToken,一般同一个公司的PublicKeyToken是一样的,不会由于版本升级而变
- SpecificVersion: 指定为true的代表告诉project指定加载这个version的程序集,如果为false,则会去package文件夹下寻找相似可代替的程序集,版本不同没关系,只要程序集名字一样就行,唉,这真是,为了编译成功,什么都不顾了..
- Private: 这个和工程中Properties中的copy local对应,true则表示会拷贝到最终的bin下的生成路径中去,false则不会拷贝,注意当没有这个标签的时候默认值是true
- HintPath: 依赖的程序集的相对路径
True False ..\..\packages\System.Collections.Immutable.1.1.36\lib\portable-net45+win8+wp8+wpa81\System.Collections.Immutable.dll False ..\..\packages\Microsoft.AspNet.WebApi.Client.5.2.3\lib\net45\System.Net.Http.Formatting.dll
- packages.config生成nuget信息: 当用nuget还原工程中依赖的包时,换原好的包就会在pacakge.config中记录还原的包的信息,一开始都是我自己手动加进去的,too young, too simple,这里面的信息是根据.csproj文件里面的依赖信息生成的
- id: 这是Reference中的程序集名字
- version: 同version
- targetFramework: 这取决于新建的工程的.net版本,不是此程序集的版本...
- vstemplate中的nuget: 标准的MVC模版生成后会把nuget包全部加载完成,VanGo平台的模版也同样要实现这个功能,不过目前都是本地的nuget包生成的依赖关系,实现的逻辑是
-
在vstemplate中的WizardData中加入pacakges的标签包含依赖的包,这里的repositoryId是引用此模版的VSIX的Product ID,里面定义pakcage的每个包信息,只需提供程序集名字和version即可
-
在VSIX中新建文件夹为packages, 这里用.nupkg的nuget包来引用,这是nuget包的格式,经过加密的二进制文件,我们可以用NuGet Package Explorer制作自己的nuget包,也可以发布到nuget galllery上面
注意: 这里的每个nupkg包的properties必须把Build Action为Content, 然后Include in Visx设置为true
[图片上传失败...(image-86d0f5-1571983320145)]NuGet Package Explorer
[图片上传失败...(image-cd6b72-1571983320145)]
-
- 吐槽: 在制作模版是要了解工程依赖的nuget包关系, 这些nuget包有来自package的包,有来自.net本来就有的包,也有奇葩的本来.net就有的包,但是mvc5需要高版本还是什么原因又依赖于package中下载的,一团乱麻,导致我做了一张依赖关系图, 更坑的是很多library的名字和package中文件夹的名字不一样,十分想骂人,这就算了, 各种library名字看着很重复,多个s,少个s,不同工程中的nuget包依赖的版本不一样,但是在web的工程中的web.config又可以指定老版本统一调用某一个版本,各种模版建好后library程序集加载错误的bug,真是想骂人,一个感觉哪里都有nuget,哪里的nuget都不一样,因为一个依赖System.Runtime的库在模版制作好后就是加载报错,排查了一个星期左右,连微软都说这是.net的bug,下了补丁还是不行,改IIS的运行时还是不行, 清理工程运行时缓存还是不行, 最后发现在模版生成的时候, 在web.config中因为我在csproj文件中引用了此类库,不知什么时候多加了一个运行时Assembly的标签,导致的奇葩报错,真是要跪了,吐槽归吐槽,还是总结一下在制作VISX中需要用到nuget的地方以及注意事项
-
VSPackage制作
参考: VS2013在右键菜单添加命令插件开发
- vsct文件解析
EnvDTE: 是最核心的程序集,所有后续要讲到的东西都归于它名下.
MSDN上对它的介绍:
EnvDTE 是包含 Visual Studio 内核自动化的对象和成员的用程序集包装的 COM 库。 在 EnvDTE80、EnvDTE90、 EnvDTE90a 和 EnvDTE100 命名空间中包含更改和新功能。
- EnvDTE80、90、100按照数字,越大的表示越新,因为Visual Stuido有好多版本,不同的版本会提供新的功能,而这几个版本的 EnvDTE 正是对应了这些更新,不同的版本只是在功能上做了补充,并没有谁能替代谁的关系,比如editPoint2 比 editPoint 可能多了某些新特性,当你要使用这些新特性的时候,就应该使用editPoint2,否则还是使用 editPoint。
DTE对象: 在 Visual Studio 中, DTE 对象是自动化模型中的顶级对象,通过操作DTE对象可以获取对 Visual Studio 的控制,比如你可以得到当前活动的文档、活动的窗口、活动的项目、查找与替换、向解决方案中添加文件、执行预定义命令、录制宏等。
Wix安装包制作
- 安装包安装时错误原因
- 插件安装过了, VSIX插件需要卸载
- customAction中有异常也会报错
模版更改流程
- 直接在工程模版中更改文件, 注意文件的build Action要设置为Content内容
- 如果增加或删除了文件或文件夹,在工程模版中的.csproj文件中要更新保持文件结构的一致性, 这保证生成模版后的文件结构正确
- 同时在.vstemplate的文件中也要更新保持一致性, 这保证在编译打包模版的时候能正确的找到文件
自定义UI
wix本身自带有一套UI,使用这些UI我们可以满足大多数的安装界面要求,你可以决定到底使用哪种WixUI:
- WixUI_Mondo 包含WixUI附带的全部用户界面:welcome界面,许可协议,安装类型(经典、自定义、完全),部件定制(安装类型自定义),浏览目标目录,磁盘消耗,同时也包含维护模式的界面;
- WixUI_FeatureTree 与WixUI_Mondo的区别就是不能选择安装类型,许可协议界面之后直接到了部件定制界面;
- WixUI_InstallDir 不会出现选择安装类型和自定义部件的界面,许可协议之后会进入到选择安装目录界面
- WixUI_Minimal 简化的安装界面,在欢迎和许可协议后会自动安装,不能自定义部件和安装路径
- WixUI_Advanced 跟WixUI_Minimal 相似,可以一键直接安装,也允许定义部件和安装路径