F# 编程

创建 F#/C# VSIX 项目模板

Dan Mohl

下载代码示例

软件开发人员越来越多地期望能够提供复杂解决方案更少的时间和较少的缺陷。通常会导致生产效率损失的一个领域是新的 F#、 C# 或 Visual Basic 项目,您需要的方式中的初始设置。为了减少这些重复的项目设置任务的最佳方法是创建 Visual Studio 扩展名 (VSIX) 的项目模板。

您可能还希望创作提升软件开发标准、 展示一种新产品或提供一种新技术快速启动 VSIX 项目模板。无论您的原因,将添加到您的技巧的开发包这些 VSIX 项目模板创建的能力一定会有好处。

在本文中,我将向您展示如何创作组成 C# ASP VSIX 项目模板。NET MVC 3 Web 应用程序,包含服务器端代码 F# 库和 F# 库可以用来包含单元测试。此外,您将学习可以用于添加到您的项目模板的灵活性和更强大的一些高级的技巧。最后,将掌握的知识可大大减少前面提到的时间-wasters 通过创建您自己的自定义项目模板。

开始设置

在采用之前,您必须完成几个任务以获得您的开发环境设置。首先,请确保您有 Visual Studio 2010年专业版或更高版本的 F# 和 C# 组件的安装。接下来,安装 Visual Studio 的 2010 SDK,您可以从下载的 bit.ly/vs-2010年-SDK。Visual Studio 2010 SDK 提供所需的所有创建 VSIX 项目。应同时下载和安装从导出模板向导以帮助减少这个冗长的过程与创建多项目模板相关联, bit.ly/导出的模板向导。最后,若要完成本文附带的示例,您应该下载并安装 ASP。NET MVC 3 工具更新,请在可用 bit.ly/mvc3-工具更新。此更新提供了许多有用的功能和工具,包括 NuGet 1.2。有关详细信息,请检查bit.ly/引入-mvc3-工具-更新

生成初始应用程序

现在,您的环境设置,您就可以生成基本应用程序每次从新建项目向导内 Visual Studio 启动的项目模板创建的。这允许您设置完全您需要什么来减少浪费了重复的项目初始化任务的时间。开发解决方案此时可以简单也可以根据需要复杂。

您需要创建的第一个项目是 C# ASP。NET MVC 3 Web 应用程序,应该被命名为 MsdnWeb。此项目的作用是为整体解决方案提供的表示层。您应启动 Visual Studio 在新建项目向导,然后选择 ASP。NET MVC 3 Web 应用程序,可找到内 Visual C# |Web。对于此示例,您应确保"空"的模板,切割视图引擎和"使用 HTML5 语义标记"选项被选定,然后单击确定。一旦 Visual Studio 完成生成项目时,找到的 Global.asax 和 Global.asax.cs 文件。您将进行的大部分代码为编写 Global.asax 在 F# 中,因此现在可以删除 Global.asax.cs 文件,并更新 Global.asax 标记,如中所示图 1。作为此项目的最后一步,可以开始名为 [视图] 文件夹中创建一个新文件夹并添加名为索引中的新视图。

Updating the Global.asax Markup
图 1 更新 Global.asax 标记

接下来,创建名为 MsdnWebApp 的新 F# 库项目并删除 Visual Studio 创建的默认.fs 和.fsx 文件。此项目的主要目的是要包含的控制器,模型和其他服务器端代码,它将推动视图。由于这是 F# 项目,您可以在 F# 提供了干净、 清晰和简洁样式中创建该服务器端代码。若要使用此项目,其用途,您需要将引用添加到映射、 System.ComponentModel.DataAnnotations、 System.Web.Abstractions 和 System.Web.Mvc (版本 3.0 +) 的程序集。您还需要将 Global.fs 文件包含中所示的代码添加图 2

图 2 Global.fs 文件

namespace MsdnWeb
 
open System
open System.Web
open System.Web.Mvc
open System.Web.Routing
 
type Route = { controller : string
               action : string
               id : UrlParameter }
 
type Global() =
  inherit System.Web.HttpApplication()
 
  static member RegisterRoutes(routes:RouteCollection) =
    routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
    routes.MapRoute("Default",
                    "{controller}/{action}/{id}",
                    { controller = "Home"; action = "Index"
                      id = UrlParameter.Optional } )
 
  member this.Start() =
    AreaRegistration.RegisterAllAreas()
    Global.RegisterRoutes(RoutTable.Routes)

您还需要添加一个 HomeController.fs 文件,包含此处所示的代码:

namespace MsdnWeb.Controllers
 
Open System.Web
Open System.Web.Mvc
 
[<HandleError>]
type HomeController() =
  inherit Conroller()
  member this.Index() =
    this.View() :> ActionResult

作为最后一项任务中构建初始应用程序,您应创建第二个 F# 库项目并将其命名为 MsdnWebAppTests。 顾名思义,MsdnWebAppTests 项目的目的是为 MsdnWebApp 中包含的单元测试。 此项目的生产版本将高速使用单元测试。 但是,为了简单起见,可以删除 Visual Studio 生成的默认.fs 和.fsx 文件并作为将来测试创建容器留空项目。 若要查看无法添加到此项目的代码示例,FsUnit.MvcSample NuGet 时安装程序包 bit.ly/fsunit-mvc3-测试

创建 VSIX 项目模板

我正在遍历您的示例项目模板有多个目标。 这些目标是本文的其余部分所基于的基础:

  1. 提供一个 C# 项目和两个 F# 项目组成的 VSIX 项目模板。
  2. 动态每个项目向共享解决方案中添加新项目创建过程中。
  3. 每次启动的项目模板时以编程方式添加的任何必要的项目引用。
  4. 各种 NuGet 软件包安装到已创建的 ASP 中。NET MVC 3 Web 应用程序。
  5. 提供一个用户界面以允许用户指定不同的项目创建选项。

Visual Studio 提供了丰富的扩展性模型,使完成第一个目标相当微不足道。 利用这一点的最简单方法是通过转至文件,启动导出模板向导 |将模板导出为 VSIX。 然后,Visual Studio 将显示允许选择多个项目的压缩到一个 VSIX 包中的 VSIX 软件包创建向导。 虽然这处理简单的多项目方案,它不能处理更高级的需求,如目标二到第五。 若要做这些事情,您将需要更多的电源。

此功能也会通过 IWizard 接口的组合形式。 这通常是作为创建模板向导进行引用。 若要生成简单的模板向导在 F# 中,您需要创建一个新的 F# 类库项目,添加实现 IWizard 接口的类 (请参阅图 3) 并将引用添加到 EnvDTE 和 Microsoft.VisualStudio.TemplateWizardInterface。

图 3 实现 IWizard 接口

namespace MsdnFsTemplateWizard
 
open System
open System.Collections.Generic
open EnvDTE
open Microsoft.VisualStudio.TemplateWizard
 
type TemplateWizard() =
  interface IWizard with
    member this.RunStarted (automationObject:Object,)
           replacementsDictionary:Dictionary<string,string>,
           runKind:WizardRunKind, customParams:Object[]) =
           "Not Implemented" |> ignore
    member this.ProjectFinishedGenerating project = "Not Implemented" |> ignore
    member this.ProjectItemFinishedGenerating projectItem =
      "Not Implemented" |> ignore
    member this.ShouldAddProjectItem filePath = true
    member this.BeforeOpeningFile projectItem = "Not Implemented" |> ignore
    member this.RunFinished() = "Not Implemented" |> ignore

为了处理更高级功能,如以编程方式添加项目引用和安装 NuGet 软件包,您还需要添加到 EnvDTE80,VSLangProj,VSLangProj80,Microsoft.VisualStudio.ComponentModelHost,
Microsoft.VisualStudio.OLE.Interop,Microsoft.VisualStudio.Shell,Microsoft.VisualStudio.Shell.Interop,
Microsoft.VisualStudio.Shell.Interop.8.0,NuGet.Core (版本 1.2 +) 的引用和 NuGet.VisualStudio
(版本 1.2 +)。如果您已执行的说明设置您的环境,这些库都可以在本地计算机上。密钥库中的几个还可以找到的 lib 文件夹中的附带文章,源代码,可以在中找到 fsharpmvc3vsix.codeplex.com

作为构建基本模板向导的最后一步,您将需要模板向导程序集签名。如果您尚没有一种,您首先需要生成强命名密钥 (.snk) 文件 (请参阅 bit.ly/how-到-创建-snk 有关如何执行此操作的信息)。.Snk 文件之后,您可以 F# 模板向导集进行签名通过转到项目的属性,选择生成选项卡并键入下列因素"其他标志:"文本框 (您必须替换 <Path> 和 < snk 文件名称 > 值与您特定的.snk 文件相关的):

'--keyfile:"<Path><snk File Name>.snk"'

若要使用此模板向导,您需要创建一个引用签名的模板向导程序集的多项目模板。 这样做的最简单方法是使用导出模板向导 Visual Studio 2010年扩展将启动该进程,然后手动调整结果。 在向导中显示时启动文件 |将模板导出为 VSIX、 MsdnWeb、 MsdnWebApp 和 MsdnWebAppTests 项目中选择,然后单击 Next。 在结果页上,输入模板的名称和说明您希望出现在 Visual Studio 的新建项目向导窗口中指定的模板向导 DLL 向导程序集的位置。 后单击下一步,应取消选中"自动将模板导入到 Visual Studio"选项并单击完成。 Visual Studio 将创建 VSIX 包、 启动 Windows 资源管理器并转到包含 VSIX 文件的目录。

这使您可以很好的起点,但现在您必须您自己动手并执行一些手动修改。 因为 VSIX 文件实际上只是用不同的扩展名的.zip 文件,您可以通过深入此程序包的具体编码仅通过更改文件扩展名为.zip 并提取的所有文件。 一次完成、 定位到抽取进程显示在文件夹内的"解决方案"目录和提取压缩的文件中找到。 此抽取进程显示构成您的项目模板,以及.vstemplate 文件的项目。

若要满足此项目模板的目标,必须修改此.vstemplate 文件。 您应该做的第一步是文件重命名为 MsdnFsMvc3.vstemplate。 因为此.vstemplate 文件是一个简单的 XML 文件,现在可以选择的文本编辑器中打开该文件并执行下列操作:

  1. 验证 <Name> 和 <Description> 元素包含您想要为此项目模板 Visual Studio 新项目向导中显示的信息。
  2. <ProjectType> 中的值更改 元素 FSharp
  3. 删除所有子元素的 <ProjectCollection> 元素。

您可以现在保存和关闭该文件,然后压缩到名为 MsdnFsMvc3.zip 文件中的三个文件夹,以及修改后的.vstemplate 文件。

MsdnFsMvc3.zip 文件可以很容易地合并回 VSIX 包针对这一点,但您将再也不用胡乱不能够测试或加强与调试支持包。 如果 Visual Studio 无法用于这些类型的任务,它就会好得多。 幸运的是,以前安装的 Visual Studio 2010 SDK 提供了完全的刀具执行此操作所需的类型。 若要开始,您首先需要创建一个新的 VSIX 项目,可以在新建项目向导中下 Visual C# 中找到 |可扩展性。 Visual Studio 创建的项目包括一个 source.extension.vsixmanifest 文件,其中可以查看和编辑通过简单双击。 您可以现在将基本元数据信息填写有关您的项目模板。 图 4 提供了一个示例。

Filling out the Metadata
图 4 填写元数据

现在可以将 MsdnFsMvc3.zip 文件添加到 Visual Studio 中的 VSIX 项目。 要做到这一点,首先导航到 Windows 资源管理器中的 VSIX 项目的根文件夹并创建一个名为 ProjectTemplates 的新文件夹。 在此文件夹中,您应添加一个名为 ASPNET 的新文件夹。 此第二个文件夹的创建是什么决定项目子类型的项目模板。 现在可以将 MsdnFsMvc3.zip 文件复制到该 ASPNET 文件夹中。

返回 Visual Studio,在设计视图的 source.extension.vsixmanifest 文件中,您应单击"添加内容"按钮。 图 5 演示您可以指定在结果窗口中的项。

Adding Content in the Design View of a Manifest File
图 5 在清单文件的设计视图中添加内容

目录结构自动创建的 VSIX 项目中通过此过程现在需要稍作调整。 通过右键单击 ProjectTemplates 文件夹并创建新的文件夹名为 ASPNET 执行此操作。 最后,应从 ProjectTemplates 文件夹的压缩的文件移至 ASPNET 文件夹并提示您覆盖现有文件时,请单击是。 快速构建解决方案将显示出由 Visual Studio VSIX 项目模板的创建已成功。 如果需要,现在无法将此项目模板添加到 Visual Studio,通过双击由 Visual Studio 的.vsix 文件,并遍历结果的安装向导。

扩展的项目模板

到目前为止完成的步骤完成此模板的第一个目标,并设置达到目标的其余部分舞台。 这些额外的 
goals 主要是通过将代码添加到模板向导的 IWizard 实现的 RunFinished 方法来完成的。 我现在将指导您完成此模板的其余目标所需的代码完成。

完成动态地将每个项目添加到解决方案,在项目创建过程中的第二个目标所需的代码所示图 6

图 6 动态地将项目添加到解决方案

// This method can be found in the full source
// as part of the TemplateWizard class.
member this.RunFinished() =
 
  // Non-relevant code omitted.
// this.solution is set in the RunStarted method.
let templatePath = this.solution.GetProjectTemplate("MsdnFsMvc3.zip", "FSharp")
  let AddProject status projectVsTemplateName projectName =
    // this.dte2 is set in the RunStarted method
    this.dte2.StatusBar.Text <- status
    let path =
      templatePath.Replace("MsdnFsMvc3.vstemplate", projectVsTemplateName)
    this.solution.AddFromTemplate(path,
      Path.Combine(this.destinationPath, projectName),
      projectName, false) |> ignore
 
  // webName and webAppName are values that have been
  // bound to strings that identify specific projects.     
  AddProject "Installing the C# Web project..."
    (Path.Combine("MsdnWeb", "MsdnWeb.vstemplate")) webName
  AddProject "Adding the F# Web App project..."
    (Path.Combine("MsdnWebApp", "MsdnWebApp.vstemplate")) webAppName
 
  // Non-relevant code omitted.

在方法中所示的代码的第一行图 6 返回从中启动新建项目向导是在计算机上的多项目模板的位置。 第二个定义了一个名为 AddProject 函数。 此函数包含更新 Visual Studio 的状态栏上所需的代码,指定与所需的项目模板的对应关系,并将项目从模板添加到解决方案中的相应.vstemplate 文件。 最后一个行代码为 MsdnWeb 和 MsdnWebApp 项目调用 AddProject 函数。 AddProject 的类似调用可能已还被添加为 MsdnWebAppTests 项目中,如果所需的。

为动态添加任何所需的项目引用的第三个目标,需要先创建一个项目名称的映射和关联的项目对象解决方案组成 (请参阅 bit.ly/fsharp-映射 F# 的信息将映射)。 通过调用名为 BuildProjectMap 的解决方案中的项目的集合提供参数的自定义函数来完成此操作。

这就被绑定到一个名为"项目,"如下所示的值:

// This function can be found in the full source
// as part of the ProjectService module.
let BuildProjectMap (projects:Projects) =
  projects
  |> Seq.cast<Project>
  |> Seq.map(fun project -> project.Name, project)
  |> Map.ofSeq
 
// This method can be found in the full source
// as part of the TemplateWizard class.
member this.RunFinished() =
 
  // Non-relevant code omitted.
// this.dte is set in the RunStarted method.
let projects = BuildProjectMap (this.dte.Solution.Projects)
 
  // Non-relevant code omitted.

既然有项目地图,您可以调用另一个自定义函数,名册中添加项目引用的过程。 此处所示的代码的第一行创建一个元组的列表:

// This method can be found in the full source
// as part of the TemplateWizard class.
member this.RunFinished() =
 
  // Non-relevant code omitted.   
 
  // webName, webAppName and webAppTestsName are values that have been
  // bound to strings that are used to identify specific projects.     
  [(webName, webAppName); (webAppTestsName, webAppName)]
  |> BuildProjectReferences projects
 
  // Non-relevant code omitted.

在列表中的每个元组表示所引用的项目的名称后跟目标项目的名称。 例如,第一个元组表示目标的项目名称"MsdnWeb"的与关联的项目的引用名称的"MsdnWebApp"。此列表然后通过管道传递给 BuildProjectReferences 函数。

此代码显示了 BuildProjectReferences 函数:

// This function can be found in the full source
// as part of the ProjectService module.
let BuildProjectReferences (projects:Map<string, Project>) projectRefs =
  projectRefs
  |> Seq.iter (fun (target,source) ->
              AddProjectReference (projects.TryFind target)
                (projects.TryFind source))

此函数只需获取的元组的列表、 循环访问它、 试图从项目地图按名称检索适当的项目对象并调用 AddProjectReference 函数来执行的实际工作。

AddProjectReference 函数运行过程结束通过验证目标和 projToReference 参数都包含一个有效的项目,如下所示:

// This function can be found in the full source
// as part of the ProjectService module.
let AddProjectReference (target:Option<Project>)
  (projToReference:Option<Project>) =
  if ((Option.isSome target) && (Option.isSome projToReference)) then
      let vsTarget = target.Value.Object :?> VSProject
      vsTarget.References
      |> Seq.cast<Reference>
      |> Seq.filter(fun (reference) -> reference.Name = projToReference.Value.Name)
      |> Seq.iter(fun reference -> reference.Remove())
      vsTarget.References
        .AddProject((projToReference.Value.Object :?> VSProject).Project)
        |> ignore

如果它们包含有效的项目,此函数中移除任何现有的引用。 最后,它将添加到项目的引用。

此项目模板的第四个目标是由 ASP 最近推出了一个概念。NET MVC 团队。 它提供了将引用添加到库或框架可能在不远的将来增强的极佳方式。 NuGet 1.2,随 ASP。NET MVC 3 工具更新,包括名为 NuGet.VisualStudio,允许方便地从内安装模板向导的 NuGet 包程序集。 ASP。NET MVC 3 工具更新安装还将添加到本地计算机以允许更快地安装这些特定的软件包在项目创建过程中的几个 NuGet 软件包。

有几个示例中用于完成 NuGet 的软件包安装的不同功能。 最重要的是此处显示:

// This function can be found in the full source
// as part of the NuGetService module.
let InstallPackages (serviceProvider:IServiceProvider) (project:Project) packages =
  let componentModel =
    serviceProvider.GetService(typeof<SComponentModel>) :?> IComponentModel
  let installer = componentModel.GetService<IVsPackageInstaller>()
  let nugetPackageLocalPath = GetNuGetPackageLocalPath()
  packages
  |> Seq.iter (fun packageId ->
              installer.InstallPackage(nugetPackageLocalPath,
                project, packageId, null, false))

此函数中的代码的前三行用于获取具体的实现 IVsPackageInstaller 接口,用于将各种 NuGet 软件包安装在指定的项目。 第四行代码调用 GetNuGetPackageLocalPath 函数,访问系统注册表,以确定 ASP 的安装路径。NET MVC 3。 最后,提供的软件包名称的列表是通过管道传递至 Seq.iter 函数,它循环访问列表和安装每个文件包。

既然已经在模板向导中实现所有这些功能,您只需将模板向导项目作为内容添加到具有一个类型的模板向导的 VSIX 项目。 图 7 显示已完成添加内容窗口。 这样就完成目标二到第四。

The Completed Add Content Window
图 7 已完成添加内容的窗口

添加 Windows Presentation Foundation 用户界面

完成最终目标的过程 — — 添加一个用户界面可用于在项目创建过程中从用户收集信息以 — 没有更改许多在过去的几年。 从 O'Reilly 媒体 Inc.2007年文章 (oreil.ly/生成-vs-proj-向导) 概述 nice 执行此操作。 过程的前提,那就保持不变,而您需要知道的几种方法实现此功能在 Windows Presentation Foundation (WPF) 并将其绑定到项目模板。

若要开始,您需要先创建一个新 C# 用户控件库。 现在,您应该更改从目标框架。NET Framework 4 到客户端配置文件。NET 框架 4。 接下来,可以删除默认的 UserControl1.xaml,并添加新的 WPF 窗口。 通过您更喜欢哪种方法,您可以现在操作设计所需的 UI 的 XAML。 最后,您需要公开任何所需的属性,然后定义任何必要的事件处理程序。 WPF 窗口的代码隐藏可能看上去像一个简单示例所示:

// This can be found in the full source as part of the MsdnCsMvc3Dialog class.
public bool IncludeTestsProject { get; set; }
 
private void btnOk_Click(object sender, RoutedEventArgs e)
{
  IncludeTestsProject =
    cbIncludeTestsProject.IsChecked.HasValue ?
cbIncludeTestsProject.IsChecked.Value : false;
  DialogResult = true;
  Close();
}
 
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
  Close();
}

获取在用户界面所需的一模一样后,您需要对程序集进行签名。 接下来,您应该将该项目添加 — 为使用内容类型的模板向导的内容 — VSIX 项目。 在模板向导项目中,然后添加到包含您的 UI 的项目的引用。 这所有完成后,您需要将代码添加到 RunStarted 方法,将显示用户界面或捕获结果,IWizard 实现的如下所示:

// This method can be found in the full source
//as part of the TemplateWizard class.
member this.RunStarted () =
 
  // Non-relevant code omitted.
let dialog = new TemplateView()
  match dialog.ShowDialog().Value with
  | true ->
    this.includeTestProject <- dialog.IncludeTestsProject
  | _ ->
    raise (new WizardCancelledException())

最后,您可以将下面的代码添加到模板向导的 RunFinished 方法:

 

// This method can be found in the full source
//as part of the TemplateWizard class.
member this.RunFinished() =
 
  // Non-relevant code omitted
 
  // webAppTestsName is a value that has been bound
  // to a string that represents the tests project.     
  if this.includeTestProject then
    AddProject "Adding the F# Web App Tests project..."
      (Path.Combine("MsdnWebAppTests",
        "MsdnWebAppTests.vstemplate")) webAppTestsName
  // Non-relevant code omitted.

重复使用,并减少时间

创作 F# /C # VSIX 项目模板是一种简单的方法,以鼓励重复使用并减少重复性的项目设置任务上浪费的时间。 现在,在您拥有这些技能,您可以通过创建项目模板包含回收期或尽可能的复杂性,根据您的情况的需要,提高生产效率。

Daniel Mohl 是一个 Microsoft MVP 和 F# 内幕。在他博客 blog.danielmohl.com ,您可以按照他在 Twitter 上 twitter.com/dmohl

这要归功于以下的技术专家审阅这篇文章: Elijah ManorChris Marinos陈德 Minerich