Creating a F# Project Template

Objective

In this post we’ll create a F# console app Visual Studio project template. We’ll start with the template we created in the previous post, and modify it to make a F# project template.

Prerequistes

We are going to start with an existing C# project template to build our F# project template. If you don’t have an existing project template that you’d like to modify then I’d suggest you read Creating a Project Template and start from there. That is where we will starting in this post.

Starting State

You may remember we currently have a project template named FancyProjectTemplate. It’s structure looks like this.

Modifying the files

Changing AssemblyInfo.cs to AssemblyInfo.fs

Since we want to turn this into an F# project template we want our .cs files to be .fs files. Let’s start by modifying the AssemblyInfo.cs. The first thing to do is just rename it to AssemblyInfo.fs. Now let’s look at the contents of the file.

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("$projectname$")]
[assembly: AssemblyDescription(<cite>)]
[assembly: AssemblyConfiguration(</cite>)]
[assembly: AssemblyCompany("$registeredorganization$")]
[assembly: AssemblyProduct("$projectname$")]
[assembly: AssemblyCopyright("Copyright © $registeredorganization$ $year$")]
[assembly: AssemblyTrademark(<cite>)]
[assembly: AssemblyCulture(</cite>)]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("$guid1$")]</p>
// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '<em>' as shown below:
// [assembly: AssemblyVersion("1.0.</em>")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

In F# we use the key word open instead of using to declare we want to reference components inside of a namespace. So we’ll modify the usings first. Also F# doesn’t use semicolons for statement endings. So we can also remove those. That will leave us with

open System.Reflection
open System.Runtime.CompilerServices
open System.Runtime.InteropServices

In the AssemblyInfo class there are many Assebmly Attributes. In F# attributes are declared by using the [< >] syntax as opposed to the [ ]syntax in C#. Lets update the assembly attributes now.

open System.Reflection;
open System.Runtime.CompilerServices;
open  System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[<assembly: AssemblyTitle("$projectname$")>]
[<assembly: AssemblyDescription(<cite>)>]
[<assembly: AssemblyConfiguration(</cite>)>]
[<assembly: AssemblyCompany("$registeredorganization$")>]
[<assembly: AssemblyProduct("$projectname$")>]
[<assembly: AssemblyCopyright("Copyright © $registeredorganization$ $year$")>]
[<assembly: AssemblyTrademark(<cite>)>]
[<assembly: AssemblyCulture(</cite>)>]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[<assembly: ComVisible(false)>]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[<assembly: Guid("$guid1$")>]
// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '<em>' as shown below:
// [<assembly: AssemblyVersion("1.0.</em>")>]
[<assembly: AssemblyVersion("1.0.0.0")>]
[<assembly: AssemblyFileVersion("1.0.0.0")>]

Finally, F# AssemblyInfo files typically end with a return of unit, or (). So lets add that now and complete the AssemblyInfo.fs file.

open System.Reflection;
open System.Runtime.CompilerServices;
open  System.Runtime.InteropServices;
//other attributes here, most left out for brevity
[<assembly: AssemblyVersion("1.0.0.0")>]
[<assembly: AssemblyFileVersion("1.0.0.0")>]
do
  ()

The completes the AssemblyInfo modifications.

Changing Class1.cs to Library1.fs

Now lets talk about the Class1.cs file. This project template we used assumed we were creating a C# library. So we can make the same assumption about the F# of the template. When first creating a F# library you start out with a Library1.fs file. So lets do that here. Simply rename Class1.cs to Library1.fs.

If we look at the starting contents of the file that was Class1.cs now Library1.fs we’ll see the following.

using System;
using System.Collections.Generic;
$if$ ($targetframeworkversion$ >= 3.5)using System.Linq;
$endif$using System.Text;
namespace $safeprojectname$
{
	public class Class1
	{
	}
}

There isn’t much here of use to us but we can mimic the idea of a class by using an F# type. In fact I’m going to suggest we borrow the code from the F# library template. In order to do this I created a F# library project in the same solution and named it FancyLibrary. This is what its Library1.fs looks like.

namespace FancyLibrary1

type Class1() = 
    member this.X = "F#"

We will simply copy this code and paste into our Library.fs project in our FancyProjectTemplate solution. Now that that is done, we need to make sure that the namespace gets updated to use the name of the project that the user enters when using our FancyProjectTemplate. So we’ll replace the word FancyLibary with $safeprojectname$. Our Library1.fs file should now look like this.

namespace $safeprojectname$

type Class1() = 
    member this.X = "F#"

Adding script.fsx

In the standard F# library project template there is also a script.fsx file. I’d suggest we add that as well to our project. We can copy and paste it from our FancyLibrary project that we created earlier and put it into our FancyProjectTemplate. Now our current solution should look like this.

The code for script.fsx is almost exactly what we want, except for one small problem. Can you tell what it is?

// Learn more about F# at <a href="http://fsharp.org.">http://fsharp.org.</a> See the 'F# Tutorial' project
// for more guidance on F# programming.</p>
#load "Library1.fs"
open FancyLibrary</p>
// Define your library scripting code here</p>

The issue is it has FancyLibrary hardcoded as the namespace. Once again we can replace it with $safeprojectname$. That will ensure we use the correct name when the project is created.

// Learn more about F# at <a href="http://fsharp.org.">http://fsharp.org.</a> See the 'F# Tutorial' project
// for more guidance on F# programming.
#load "Library1.fs"
open $safeprojectname$
// Define your library scripting code here

Creating ProjectTemplate.fsproj from ProjectTemplate.csproj

As you may have guessed the ProjectTemplate.csproj is the project file that will be used as the template for creating a .**proj file from our files that we have been modifying. F# project use files witn ah .fsproj extension. So the first thing we can do is simply change the name of the ProjectTemplate.csproj to ProjectTemplate.fsproj.

The next thing we need to realize is F# project templates are a bit different from C# project templates mainly because they reference different assemblies and use different MSBuild targets for compilation. So lets do the simple thing here, and borrow again from the FancyLibrary project that we created earlier. If you want to view a .*proj file in visual studio the easiest way is to first unload the project. You can do that by right clicking on the .fsproj file and selecting “Unload Project” from the context menu.

Once you have unloaded the project you should be able right click on it again and click “Edit *.fsproj” where * is the name of the project file. We’ll do this for FancyLibrary.fsproj.

Below are the contents of FancyLibrary.fsproj

<?xml version="1.0" encoding="utf-8"?>
<project toolsversion="14.0" defaulttargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  <import project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')"></import>
  <propertygroup>
    <configuration condition=" '$(Configuration)' == '' ">Debug</configuration>
    <platform condition=" '$(Platform)' == '' ">AnyCPU</platform>
    <schemaversion>2.0</schemaversion>
    <projectguid>bf61d16e-51f3-42eb-8597-940a08c72a9f</projectguid>
    <outputtype>Library</outputtype>
    <rootnamespace>FancyLibrary</rootnamespace>
    <assemblyname>FancyLibrary</assemblyname>
    <targetframeworkversion>v4.5.2</targetframeworkversion>
    <targetfsharpcoreversion>4.4.0.0</targetfsharpcoreversion>
    <autogeneratebindingredirects>true</autogeneratebindingredirects>
    <name>FancyLibrary</name>
  </propertygroup>
  <propertygroup condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
    <debugsymbols>true</debugsymbols>
    <debugtype>full</debugtype>
    <optimize>false</optimize>
    <tailcalls>false</tailcalls>
    <outputpath>bin\Debug\</outputpath>
    <defineconstants>DEBUG;TRACE</defineconstants>
    <warninglevel>3</warninglevel>
    <documentationfile>bin\Debug\FancyLibrary.XML</documentationfile>
  </propertygroup>
  <propertygroup condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
    <debugtype>pdbonly</debugtype>
    <optimize>true</optimize>
    <tailcalls>true</tailcalls>
    <outputpath>bin\Release\</outputpath>
    <defineconstants>TRACE</defineconstants>
    <warninglevel>3</warninglevel>
    <documentationfile>bin\Release\FancyLibrary.XML</documentationfile>
  </propertygroup>
  <itemgroup>
    <reference include="mscorlib"></reference>
    <reference include="FSharp.Core, Version=$(TargetFSharpCoreVersion), Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
      <private>True</private>
    </reference>
    <reference include="System"></reference>
    <reference include="System.Core"></reference>
    <reference include="System.Numerics"></reference>
  </itemgroup>
  <itemgroup>
    <compile include="AssemblyInfo.fs"></compile>
    <compile include="Library1.fs"></compile>
    <none include="Script.fsx"></none>
  </itemgroup>
  <propertygroup>
    <minimumvisualstudioversion condition="'$(MinimumVisualStudioVersion)' == ''">11</minimumvisualstudioversion>
  </propertygroup>
  <choose>
    <when condition="'$(VisualStudioVersion)' == '11.0'">
      <propertygroup condition="Exists('$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets')">
        <fsharptargetspath>$(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets</fsharptargetspath>
      </propertygroup>
    </when>
    <otherwise>
      <propertygroup condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets')">
        <fsharptargetspath>$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets</fsharptargetspath>
      </propertygroup>
    </otherwise>
  </choose>
  <import project="$(FSharpTargetsPath)"></import>
  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
       Other similar extension points exist, see Microsoft.Common.targets.
  <Target Name="BeforeBuild">
  </Target>
  <Target Name="AfterBuild">
  </Target>
  -->
</project>

Before we go further open ProjectTemplate.fsproj in the FancyProjectTemplate project and delete all the lines in that file. We will put what we want there next. Now let’s copy the contents of FancyLibrary.fsproj our ProjectTemplate.fsproj in the FancyProjectTemplate project. Now we have a viable fsproj file to work with.

There are few things we need to change in order for this to actually be a template. The first thing we need to change is the ProjectGuid. Let’s replace that with $guid1$. Below is the before and after of this change.

Before: NOTE:Your GUID is probably going to be different.

<projectguid>bf61d16e-51f3-42eb-8597-940a08c72a9f</projectguid>

After:

<projectguid>$guid1$</projectguid>

The next thing we’ll need to change is the name. At the moment FancyLibrary library is hardcoded as the Root Namespace and the Assembly Name lets modify those now, as before we can use the replacement token $safeprojectname$.

Before:

<rootnamespace>FancyLibrary</rootnamespace>
<assemblyname>FancyLibrary</assemblyname>

After:

<rootnamespace>$rootnamspace$</rootnamespace>
<assemblyname>$rootnamspace$</assemblyname>

The files that we want in our template already exist in the current ProjectTemplate.fsproj.

  <itemgroup>
    <compile include="AssemblyInfo.fs"></compile>
    <compile include="Library1.fs"></compile>
    <none include="Script.fsx"></none>
  </itemgroup>

If you wanted to add more files to your project template you would add them to the FancyProjectTemplate project and then add the appropriate references to this item group. Also one thing to be aware of, if you have one .fs file that needs to reference another .fs file. The one being referenced should occur first in the ItemGroup.

Updating the FancyProjectTemplate.vstemplate

In a project template the *.vstemplate file determines what files will get written when someone creates a project from the template and if there are any folders created and things of that nature. Lets look at the current contents of FancyProjectTemplate.vstemplate and then make the required modifications for our F# library template.

<?xml version="1.0" encoding="utf-8"?>
<vstemplate version="3.0.0" type="Project" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" xmlns:sdk="http://schemas.microsoft.com/developer/vstemplate-sdkextension/2010">
  <templatedata>
    <name>FancyProjectTemplate</name>
    <description>FancyProjectTemplate</description>
    <icon>FancyProjectTemplate.ico</icon>
    <projecttype>CSharp</projecttype>
    <requiredframeworkversion>2.0</requiredframeworkversion>
    <sortorder>1000</sortorder>
    <templateid>b604e5d6-e82b-4088-a4f2-01e7c881bf79</templateid>
    <createnewfolder>true</createnewfolder>
    <defaultname>FancyProjectTemplate</defaultname>
    <providedefaultname>true</providedefaultname>
  </templatedata>
  <templatecontent>
    <project file="ProjectTemplate.csproj" replaceparameters="true">
      <projectitem replaceparameters="true" targetfilename="Properties\AssemblyInfo.fs">AssemblyInfo.fs</projectitem>
      <projectitem replaceparameters="true" openineditor="true">Class1.cs</projectitem>
    </project>
  </templatecontent>
</vstemplate>

A few things I would like to change so that we have a little bit more friendly and descriptive of a template. Lets update the Name, Description, and Default name to something that represents the intent of our project template.

Before:

</p>
 <templatedata>
    <name>FancyProjectTemplate</name>
    <description>FancyProjectTemplate</description>
    <icon>FancyProjectTemplate.ico</icon>
    <projecttype>CSharp</projecttype>
    <requiredframeworkversion>2.0</requiredframeworkversion>
    <sortorder>1000</sortorder>
    <templateid>b604e5d6-e82b-4088-a4f2-01e7c881bf79</templateid>
    <createnewfolder>true</createnewfolder>
    <defaultname>FancyProjectTemplate</defaultname>
    <providedefaultname>true</providedefaultname>
  </templatedata>

After:

 <templatedata>
    <name>Fancy F# Library</name>
    <description>Creates a Fancy F# Library Project</description>
    <icon>FancyProjectTemplate.ico</icon>
    <projecttype>CSharp</projecttype>
    <requiredframeworkversion>2.0</requiredframeworkversion>
    <sortorder>1000</sortorder>
    <templateid>b604e5d6-e82b-4088-a4f2-01e7c881bf79</templateid>
    <createnewfolder>true</createnewfolder>
    <defaultname>FancyLibrary</defaultname>
    <providedefaultname>true</providedefaultname>
  </templatedata>

Now that the name and description is updated lets change the ProjectType from CSharp to FSharp.

Before:

<projecttype>CSharp</projecttype>

After:

<projecttype>FSharp</projecttype>

Changing this to F# will help make sure that when we are in the File New Project dialog we’ll see our template under the F# node.

That is all that is required in the TemplateData node. Next lets look at the TemplateContent node.

 <templatecontent>
    <project file="ProjectTemplate.csproj" replaceparameters="true">
      <projectitem replaceparameters="true" targetfilename="Properties\AssemblyInfo.cs">AssemblyInfo.cs</projectitem>
      <projectitem replaceparameters="true" openineditor="true">Class1.cs</projectitem>
    </project>
  </templatecontent>

You probably notice a few things are wrong here. First the ProjectTemplate.csproj has been renamed to ProjectTemplate.fsproj so we should change that.

 <templatecontent>
    <project file="ProjectTemplate.fsproj" replaceparameters="true">
      <projectitem replaceparameters="true" targetfilename="Properties\AssemblyInfo.cs">AssemblyInfo.cs</projectitem>
      <projectitem replaceparameters="true" openineditor="true">Class1.cs</projectitem>
    </project>
  </templatecontent>

Next our AssemblyInfo.cs has been renamed AssemblyInfo.fs. Also F# projects will have AssemblyInfo.fs in the root of the project instead of in a Properties directory, So the TargetFilename for AssemblyInfo.fs will be updated to reflect that as well.

 <templatecontent>
    <project file="ProjectTemplate.fsproj" replaceparameters="true">
      <projectitem replaceparameters="true" targetfilename="AssemblyInfo.fs">AssemblyInfo.fs</projectitem>
      <projectitem replaceparameters="true" openineditor="true">Class1.cs</projectitem>
    </project>
  </templatecontent>

We no longer have a class1.cs its now Library1.fs.

 <templatecontent>
    <project file="ProjectTemplate.fsproj" replaceparameters="true">
      <projectitem replaceparameters="true" targetfilename="AssemblyInfo.fs">AssemblyInfo.fs</projectitem>
      <projectitem replaceparameters="true" openineditor="true">Library1.fs</projectitem>
    </project>
  </templatecontent>

Finally we need to add our Script.fsx

 <templatecontent>
    <project file="ProjectTemplate.fsproj" replaceparameters="true">
      <projectitem replaceparameters="true" targetfilename="AssemblyInfo.fs">AssemblyInfo.fs</projectitem>
      <projectitem replaceparameters="true" openineditor="true">Library1.fs</projectitem>
      <projectitem replaceparameters="true">Script.fsx</projectitem>
    </project>
  </templatecontent>

After all changes our ProjectTemplate.vstemplate should look like this.

<?xml version="1.0" encoding="utf-8"?>
<vstemplate version="3.0.0" type="Project" xmlns="http://schemas.microsoft.com/developer/vstemplate/2005" xmlns:sdk="http://schemas.microsoft.com/developer/vstempl230..................ate-sdkextension/2010">
  <templatedata>
    <name>Fancy F# Library</name>
    <description>Creates a Fancy F# Library Project</description>
    <icon>FancyProjectTemplate.ico</icon>
    <projecttype>FSharp</projecttype>
    <requiredframeworkversion>2.0</requiredframeworkversion>
    <sortorder>1000</sortorder>
    <templateid>b604e5d6-e82b-4088-a4f2-01e7c881bf79</templateid>
    <createnewfolder>true</createnewfolder>
    <defaultname>FancyLibrary</defaultname>
    <providedefaultname>true</providedefaultname>
  </templatedata>
  <templatecontent>
    <project file="ProjectTemplate.fsproj" replaceparameters="true">
      <projectitem replaceparameters="true" targetfilename="AssemblyInfo.fs">AssemblyInfo.fs</projectitem>
      <projectitem replaceparameters="true" openineditor="true">Library1.fs</projectitem>
      <projectitem replaceparameters="true">Script.fsx</projectitem>
    </project>
  </templatecontent>
</vstemplate>

That’s all. Save all the changes and check to see if it compiles.

Once it compiles the template should show up in your debug or release output folder in bin\Debug\ProjectTemplates\FSharp\1033\FancyProjectTemplate.zip

Testing our Fancy F# Library Template

After we have successfully compiled our template its time to test it. As you may recall from the last article this is pretty simple. Copy the zip file to your users visual studio templates directory. It should be something like c:\users\adam\Documents\Visual Studio 2015\Templates\ProjectTemplates.

Finally lets try to create a new project from our Fancy Library Project. From the “File” menu in Visual Studio select “New” followed by “Project”. Navigate the F# node in the “New Project” dialog. You should see your Fancy F# Library project template available there. Select the template, enter a project name, and click the “OK” button.

You should now have a new Fancy F# Library!

Conclusion

If you read my previous post Creating a Project Template then you should know the basics of creating project templates for C# or F# projects. If you were inclined to create a VB project you could probably figure that out now as well. I plan on writing future posts to cover some of the more intermediate steps to creating project templates.

Spread The Word
Adam.Wright
 

Adam Wright is a technologist specializing in software development using Microsoft technologies.

Click Here to Leave a Comment Below 0 comments