Share via



October 2011

Volume 26 Number 10

F# Programming - Authoring an F#/C# VSIX Project Template

By Dan Mohl | October 2011

Software developers are increasingly expected to provide complex solutions in less time and with fewer defects. One area that often causes productivity loss is the initial setup of new F#, C# or Visual Basic projects in just the way you require. The best way to reduce these repetitive project setup tasks is to create a Visual Studio Extension (VSIX) project template.

You may also wish to author a VSIX project template to promote software development standards, showcase a new product or provide a quick launch into a new technology. Whatever your reason, adding the ability to create these VSIX project templates to your development bag of tricks is certain to have benefits.

In this article, I’ll show you how to author a VSIX project template made up of a C# ASP.NET MVC 3 Web Application, an F# Library that contains the server-side code and an F# Library that can be used to contain unit tests. Additionally, you’ll learn a few advanced techniques that can be used to add flexibility and power to your project templates. In the end, you’ll be armed with the knowledge to greatly reduce the previously mentioned time-wasters through the creation of your own custom project templates.

Getting Set Up

Before diving in, you must complete a few tasks in order to get your development environment set up. First, make sure you have Visual Studio 2010 Professional or higher with the F# and C# components installed. Next, install the Visual Studio 2010 SDK, which you can download from bit.ly/vs-2010-SDK. The Visual Studio 2010 SDK provides everything needed to create a VSIX project. To help reduce the tedium associated with creating a multiproject template, you should also download and install the Export Template Wizard from bit.ly/export-template-wiz. Finally, in order to complete the example provided with this article, you should download and install the ASP.NET MVC 3 Tools Update, available at bit.ly/mvc3-tools-update. This update provides a number of useful features and tools, including NuGet 1.2. For more information, check out bit.ly/introducing-mvc3-tools-update

Building the Initial Application

Now that your environment is set up, you’re ready to build the base application that will be created every time the project template is launched from the New Project Wizard within Visual Studio. This lets you set up exactly what you need in order to reduce time wasted by repetitive project-initialization tasks. The solution developed at this point can be as simple or complex as needed. 

The first project you need to create is a C# ASP.NET MVC 3 Web application that should be named MsdnWeb. The role of this project is to provide the presentation layer for the overall solution. You should launch the New Project Wizard in Visual Studio and select ASP.NET MVC 3 Web Application, which can be found within Visual C# | Web. For this example, you should ensure that the “Empty” template, the Razor view engine and the “Use HTML5 semantic markup” options are selected, and then click OK. Once Visual Studio finishes generating the project, locate the Global.asax and Global.asax.cs files. You’ll be writing the majority of the code for the Global.asax in F#, so you can now remove the Global.asax.cs file and update the Global.asax markup as shown in Figure 1. As a final step for this project, you can create a new folder in the Views folder named Home and add a new View within it named Index.

Updating the Global.asax Markup
Figure 1 Updating the Global.asax Markup

Next, create a new F# Library project named MsdnWebApp and remove the default .fs and .fsx files that Visual Studio creates. The primary purpose of this project is to contain the controllers, models and other server-side code that will drive the views. Because this is an F# project, you’ll be able to create this server-side code in the clean, crisp and succinct style that F# provides. In order to use this project for its intended purpose, you need to add references to the System.Web, System.ComponentModel.DataAnnotations, System.Web.Abstractions and System.Web.Mvc (version 3.0+) assemblies. You also need to add a Global.fs file that contains the code shown in Figure 2.

Figure 2 The Global.fs File

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)

You also need to add a HomeController.fs file that contains the code shown here:

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

As a final task in building the initial application, you should create the second F# Library project and name it MsdnWebAppTests. As the name suggests, the purpose of the MsdnWebAppTests project is to contain the unit tests for MsdnWebApp. A production-ready version of this project would be primed with unit tests. However, in order to keep things simple, you can remove the default .fs and .fsx files that Visual Studio generates and leave the empty project as a container for the creation of future tests. To see an example of the code that could be added to this project, install the FsUnit.MvcSample NuGet package at bit.ly/fsunit-mvc3-tests.

Creating the VSIX Project Template

The example project template I’m walking you through has several goals. These goals are the foundation on which the rest of this article is based:

  1. Provide a VSIX project template made up of a C# project and two F# projects.
  2. Dynamically add each of the projects to a shared solution during new project creation.
  3. Programmatically add any necessary project references each time the project template is launched.
  4. Install various NuGet packages to the created ASP.NET MVC 3 Web Application.
  5. Provide a UI that allows a user to specify various project-creation options. 

Visual Studio provides a rich extensibility model that makes accomplishing the first goal fairly trivial. The easiest way to take advantage of this is to launch the Export Template Wizard by going to File | Export Template as VSIX. Visual Studio will then display a VSIX package-creation wizard that allows the selection of multiple projects to be compressed into a VSIX package. While this handles simple multiproject scenarios, it can’t handle more advanced requirements such as goals two through five. To do these things, you’ll need a bit more power.

This power comes in the form of composition via the IWizard interface. This is commonly referenced as creating a template wizard. To build a simple template wizard in F#, you need to create a new F# Library project, add a class that implements the IWizard interface (see Figure 3) and add references to EnvDTE and Microsoft.VisualStudio.TemplateWizardInterface.

Figure 3 Implementing the IWizard Interface

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

In order to handle more-advanced functionality such as programmatically adding project references and installing NuGet packages, you’ll also need to add references to 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 (version 1.2+) and NuGet.VisualStudio (version 1.2+). If you’ve followed the instructions for setting up your environment, all of these libraries are available on your local machine. A few of the key libraries can also be found in the lib folder of the source code that accompanies the article, which can be found at fsharpmvc3vsix.codeplex.com.

As a final step to building the basic template wizard, you’ll need to sign the template wizard assembly. If you don’t already have one, you’ll first need to generate a strong named key (.snk) file (see bit.ly/how-to-create-snk for information on how to do this). Once you have an .snk file, you can sign the F# template wizard assembly by going to the properties of the project, selecting the Build tab and typing the following into the “Other flags:” textbox (you must replace <Path> and <snk File Name> with the values that relate to your specific .snk file):

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

In order to use this template wizard, you’ll need to create a multiproject template that references the signed template wizard assembly. The easiest way to do this is to use the Export Template Wizard Visual Studio 2010 extension to kick-start the process, then manually tweak the result. In the wizard that displays when you launch File | Export Template as VSIX, select the MsdnWeb, MsdnWebApp and MsdnWebAppTests projects and click Next. On the resulting page, enter the template name and description that you wish to appear in the Visual Studio New Project Wizard window and specify the location of the template wizard DLL for the Wizard Assembly. After clicking Next, you should uncheck the “Automatically import the template into Visual Studio” option and click Finish. Visual Studio will then create the VSIX package, launch Windows Explorer and take you to the directory that contains the VSIX file.     

This gives you a nice starting point, but now you must get your hands dirty and make a few manual modifications. Because VSIX files are really just .zip files with a different extension, you can dig into the guts of this package by simply changing the file extension to .zip and extracting all of the files. Once complete, navigate to the “Solution” directory within the folder that the extraction process revealed and extract the compressed file found within. This extraction process reveals the projects that constitute your project template as well as a .vstemplate file.

In order to meet the goals for this project template, you must modify this .vstemplate file. The first step you should do is rename the file to MsdnFsMvc3.vstemplate. Because this .vstemplate file is a simple XML file, you can now open the file in your text editor of choice and do the following:

  1. Verify that the <Name> and <Description> elements contain the information that you wish to have displayed for this project template in the Visual Studio New Project Wizard.
  2. Change the value in the <ProjectType> element to FSharp.
  3. Remove all child elements of the <ProjectCollection> element.

You can now save and close the file, and then compress the three folders as well as the modified .vstemplate file into a file named MsdnFsMvc3.zip.

The MsdnFsMvc3.zip file could easily be combined back into a VSIX package at this point, but you would then be left without the ability to test or enhance the package with debugging support. It would be much better if Visual Studio could be used for these types of tasks. Luckily, the Visual Studio 2010 SDK that you previously installed provides exactly the type of tooling needed to do this. To get started, you first need to create a new VSIX Project, which can be found in the New Project Wizard under Visual C# | Extensibility. The project that Visual Studio creates includes a source.extension.vsixmanifest file, which can be viewed and edited via a simple double-click. You can now fill out the basic metadata information about your project template. Figure 4 provides an example.

Filling out the Metadata
Figure 4 Filling out the Metadata

The MsdnFsMvc3.zip file can now be added to the VSIX project in Visual Studio. To do this, you first navigate to the root folder of the VSIX project within Windows Explorer and create a new folder named ProjectTemplates. Within this folder, you should add a new folder named ASPNET. The creation of this second folder is what determines the project subtype for the project template. You now copy the MsdnFsMvc3.zip file into the ASPNET folder.

Back in Visual Studio, in the design view of the source.extension.vsixmanifest file, you should click the “Add Content” button. Figure 5 shows the entries that you can specify in the resulting window.

Adding Content in the Design View of a Manifest File
Figure 5 Adding Content in the Design View of a Manifest File

The directory structure that’s automatically created in the VSIX project by this process now needs a little adjustment. You do this by right-clicking on the ProjectTemplates folder and creating a new folder named ASPNET. Last, you should move the compressed file from the ProjectTemplates folder to the ASPNET folder and click Yes when prompted to overwrite the existing file. Building the solution quickly reveals that the creation of your VSIX project template by Visual Studio has been successful. If desired, you could now add this project template to Visual Studio by double-clicking the .vsix file that was created by Visual Studio and walking through the resulting installation wizard.

Extending the Project Template

The steps completed so far accomplish the first goal for this template and set the stage for attaining the rest of the goals. These additional 
goals are primarily accomplished by adding code to the RunFinished method of the IWizard implementation of the template wizard. I’ll now walk you through the code needed to accomplish the remaining goals for this template.

The code required to accomplish the second goal of dynamically adding each of the projects to the solution during project creation is shown in Figure 6.

Figure 6 Dynamically Adding Projects to the Solution

// 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.

The first line of code in the method shown in Figure 6 returns the location of the multiproject template on the machine from which the New Project Wizard was launched. The second defines a function named AddProject. This function contains the code necessary to update the Visual Studio status bar, specifies the appropriate .vstemplate file that corresponds with the desired project template and adds the project from the template to the solution. The last lines of code call the AddProject function for the MsdnWeb and MsdnWebApp projects. A similar call to AddProject could’ve also been added for the MsdnWebAppTests project, if that were desired.

To accomplish the third goal of dynamically adding any desired project references, you need to first create a map of the project names and associated Project objects that make up the solution (see bit.ly/fsharp-maps for information on F# maps). This is accomplished by calling a custom function named BuildProjectMap with a provided argument of a collection of the Projects in the solution.

This is then bound to a value named “projects,” as shown here:

// 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.

Now that you have the project map, you can call another custom function to kick off the process of adding the project references. The first line of the code shown here creates a list of tuples:

// 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.

Each tuple in the list represents the name of the target project followed by the name of the project that’s being referenced. For example, the first tuple indicates a target project name of “MsdnWeb” with an associated project reference name of “MsdnWebApp.” This list is then piped to the BuildProjectReferences function.

This code shows the BuildProjectReferences function:

// 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))

This function simply takes the list of tuples, iterates through it, tries to retrieve the appropriate Project object from the project map by name and calls the AddProjectReference function to do the actual work.

The AddProjectReference function finishes up the process by verifying that the target and projToReference arguments both contain a valid project, as shown here:

// 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

If they do contain valid projects, this function removes any existing reference. Finally, it adds the reference to the project.

The fourth goal for this project template is a concept that was recently introduced by the ASP.NET MVC team. It provides a great way to add references to libraries or frameworks that are likely to be enhanced in the not-too-distant future. NuGet 1.2, which ships with the ASP.NET MVC 3 Tools Update, includes an assembly named NuGet.VisualStudio that allows NuGet packages to be installed easily from within a template wizard. The ASP.NET MVC 3 Tools Update install also adds several NuGet packages to the local machine to allow faster installation of these specific packages during project creation.

There are a couple of different functions in the sample that are used to accomplish the NuGet package installations. The most important one is show here:

// 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))

The first three lines of code in this function are used to get the concrete implementation of the IVsPackageInstaller interface, which will be used to install the various NuGet packages in the specified project. The fourth line of code calls the GetNuGetPackageLocalPath function, which accesses the System Registry to determine the install path of ASP.NET MVC 3. Last, the provided list of package names is piped to the Seq.iter function, which iterates through the list and installs each package.

Now that all of this functionality has been implemented in your template wizard, you simply need to add the template wizard project as content to the VSIX project with a type of Template Wizard. Figure 7 shows the completed Add Content window. This completes goals two through four.

The Completed Add Content Window
Figure 7 The Completed Add Content Window

Adding a Windows Presentation Foundation UI

The process for accomplishing the final goal—adding a UI that can be used to gather information from the user during the project-creation process—hasn’t changed much over the past several years. A 2007 article from O’Reilly Media Inc. (oreil.ly/build-vs-proj-wiz) provides a nice overview for doing this. While the premise of the process remains the same, you need to know a few things to implement this functionality in Windows Presentation Foundation (WPF) and tie it into your project template.

To get started, you need to first create a new C# User Control Library. You should now change the target framework from .NET Framework 4 Client Profile to .NET Framework 4. Next, you can remove the default UserControl1.xaml and add a new WPF Window. Through whichever method you prefer, you can now manipulate the XAML to design the desired UI. Finally, you need to expose any desired properties and then define any necessary event handlers. A simple example of what the codebehind for the WPF Window might look like is shown here:

// 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();
}

After getting the UI exactly the way you want it, you’ll need to sign the assembly. Next, you should add the project—as content with a Content Type of Template Wizard—to the VSIX project. Within the template wizard project, you then add a reference to the project that contains your UI. Once this is all finished, you need to add code to the RunStarted method of the IWizard implementation that will show the UI and capture the result, as shown here:

// 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())

Finally, you can add the following code to the RunFinished method of the template wizard:

 

// 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.

Reuse and Reduce Time

Authoring an F#/C# VSIX project template is an easy way to encourage reuse and reduce time wasted on repetitive project setup tasks. With these skills now in your possession, you’ll be able to increase productivity by creating project templates that contain as little or as much complexity as your scenario requires.


Daniel Mohl is a Microsoft MVP and F# Insider. He blogs at blog.danielmohl.com and you can follow him on Twitter at twitter.com/dmohl.

Thanks to the following technical experts for reviewing this article: Elijah ManorChris Marinos  and Richard Minerich