F# 程式設計

編寫 F#/C# VSIX 專案範本

陳 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 專案。若要降低建立多專案範本相關聯的 tedium,您應該一併下載及安裝匯出範本精靈,從 bit.ly/匯出的範本-精靈。最後,若要完成本文所提供的範例,您應該下載並安裝 ASP。NET MVC 3 工具更新,請在 bit.ly/mvc3 工具的更新。這個更新提供一些有用的功能和工具,包括 NuGet 1.2。如需詳細資訊,請參閱 bit.ly/簡介-mvc3-工具-更新

建立初始應用程式

現在,您的環境設定,您就可以建立將會在每次從 Visual Studio 中的 [新增專案精靈啟動專案範本建立的基本應用程式。這可讓您設定完全您需要什麼,以減少浪費在重複的專案初始設定工作的時間。現在為簡單或複雜,端視需要,可以是開發方案。

您要建立的第一個專案是 C# ASP。命名為 MsdnWeb 的 NET MVC 3 Web 應用程式。這個專案的角色是展示層提供整體解決方案。您應該啟動 [新增專案精靈],在 Visual Studio 中,選取 [ASP。NET MVC 3 Web 應用程式,請參閱 Visual C# 中的 |網頁。例如,必須先確定 「 空白 」 範本,剃刀檢視引擎和 「 使用 HTML5 語意標記"選項被選取,並再按一下 [確定]。一旦 Visual Studio 完成產生專案,找出 Global.asax 和 Global.asax.cs 檔案。您將需要撰寫大部分的程式碼的 Global.asax 中 F#,讓您現在可以移除 Global.asax.cs 檔案,並更新 Global.asax 標記中所示圖 1。這個專案的最後一個步驟中,您可以在名為首頁的 [檢視] 資料夾中建立新的資料夾,並新增新檢視名稱為索引中。


圖 1 更新 Global.asax 標記

接下來,建立名為 MsdnWebApp 的新 F# 程式庫專案,並移除 Visual Studio 建立的預設.fs 和.fsx 檔案。這個專案的主要目的是包含控制站,模型和其他功能可帶來檢視的伺服器端程式碼。因為這是一個 F# 專案,您可以建立此伺服器端程式碼中 F# 提供簡潔、 清晰且簡潔的樣式。若要使用它的特定目的此專案,您必須將參考加入至 System.Web、 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。 這個專案的實際執行版本會裝滿彈藥,單元測試。 不過,為了簡單起見,您可以移除預設.fs 和.fsx 的檔案會產生 Visual Studio,並保留空的專案做為未來測試建立的容器。 無法加入至這個專案的程式碼範例,請安裝 FsUnit.MvcSample NuGet 封裝在 bit.ly/fsunit-mvc3-測試

建立 [VSIX] 專案範本

我帶您逐步完成範例專案範本會有數個目標。 這些目標是本文的其餘部分所依據的基礎:

  1. 提供組成 C# 專案範本和兩個 F# 專案的 VSIX 專案範本。
  2. 動態加入每個專案共用的方案在新的專案建立期間。
  3. 程式設計方式加入任何必要的專案參考的專案範本會啟動每一次。
  4. 若要建立的 ASP 安裝各種 NuGet 套件。NET MVC 3 Web 應用程式。
  5. 提供 UI,允許使用者指定不同的專案建立選項。

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 的專案],然後按一下 [下一步]。在 [結果] 頁面中,輸入範本名稱及描述您想要出現在 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 專案,可以在 [新增專案精靈] 在視覺 C# 中找到的 |擴充性。Visual Studio 建立的專案包含的 source.extension.vsixmanifest 檔案,可以檢視及編輯簡單的按兩下。您現在填寫您的專案範本相關的基本的中繼資料資訊。圖 4 提供的範例。


圖 4 填寫中繼資料

現在可以加入 MsdnFsMvc3.zip 檔案 Visual Studio 中的 [VSIX] 專案。若要這樣做,請您先瀏覽到 [Windows 檔案總管] 中的 [VSIX] 專案的根資料夾,並建立一個名為 ProjectTemplates 的新資料夾。在這個資料夾中,您應該新增一個名為 ASPNET 的新資料夾。這個第二個資料夾的建立是決定針對專案範本的子專案型別。您現在將 MsdnFsMvc3.zip 檔案複製到 [ASPNET] 資料夾。

回到 Visual Studio,在設計檢視中的 source.extension.vsixmanifest 檔案中,您應該按一下 「 新增內容 」 按鈕。圖 5 您可以指定在 [結果] 視窗會顯示項目。


[圖 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 檔對應以您想要的專案範本,從範本中將專案加入方案。程式碼的最後一行會呼叫 AddProject 函式的 MsdnWeb 和 MsdnWebApp 的專案。類似 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.

在清單中的每一 tuple 代表後面加上專案所參考的名稱的目標專案的名稱。 例如,第一個 tuple 表示目標專案的名稱"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 會顯示 [已完成新增內容] 視窗中。這樣就完成目標二到四個。


圖 7 完成新增內容視窗

新增 Windows Presentation Foundation UI

完成最終目標的處理程序,新增的 UI,可以用來向使用者收集資訊,在專案建立過程中,未經變更許多在過去幾年。從 O'Reilly 媒體 Inc.2007年文件(oreil.ly/建置-vs-之專案的精靈) 會提供不錯的概觀,執行此動作。處理程序的前提保持不變,而您需要知道幾件事,實作這項功能在 「 Windows Presentation Foundation 」 (WPF),並將它連接到您的專案範本。

若要開始,您需要先建立新 C# 使用者控制項程式庫。您現在應該變更從目標架構。Net 4 到的用戶端設定檔。NET Framework 4。接下來,您可以移除預設的 UserControl1.xaml,並新增新的 WPF 視窗。透過您偏好哪種方法,您現在可以操作 XAML 設計所需的 UI。最後,您必須公開 (expose) 任何您想要的屬性,然後定義任何必要的事件處理常式。程式碼後置的 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();
}

在取得 UI 完全將您想要的方式之後, 您要簽署組件。 接下來,您應該在其中加入專案,做為內容類型的範本精靈的內容 — VSIX 專案。 在範本精靈專案中,然後加入至專案,其中包含使用者介面的參考。 在這所有完成時,您必須加入程式碼會顯示 UI,並擷取結果,IWizard 實作的 RunStarted 方法,如下所示:

// 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 專案範本是簡單的方法,以鼓勵重複使用,並減少重複的專案設定工作上浪費的時間。 現在在您擁有這些技術,您可以藉由建立專案範本包含少或太多的複雜性,因為您的案例需要增加產能。

奧 Mohl 是 Microsoft MVP 和 F# 高手。他的部落格在 blog.danielmohl.com 他按照在 Twitter twitter.com/dmohl

感謝到下列的技術專家來檢閱這份文件: Elijah 莊舍Chris Marinos羅子 Minerich