Creating a template for 'dotnet new' for Microsoft.Build.Traversal

Using Visual Studio Code to write the definition of a dotnet template

In a recent post I described using Microsoft.Build.Traversal in lieu of solutions for building your projects, with steps to create a sample solution and the Traversal project using the .NET Core dotnet command line. Because there's no native support for Traversal projects in the dotnet command this involved creating the Traversal project as a Class Library project and then removing some of the un-necessary bits. One other option available to those using the .NET Core CLI is to create a template for dotnet new. The steps to do this are pretty simple:

  1. Create a project to use as the template
  2. Set all the appropriate meta-data for the template project
  3. Install the project as a template so dotnet new knows about it

Creating a project to use

As with creating the whole shebang in my previous post probably the easiet way to do this is to use the command line, here's the commands I ran to create a project ready to tweak:

md Traversal.Template
cd Traversal.Tempate
dotnet new classlib
md .template.config
del Class1.cs
del obj /S /Q
rd obj

Once these commands had been run, the last three of which are responsible for cleaning out the artifacts of a Class Library project as we don't actually need, nor care about, any of those, it's time to spin up Visual Studio Code (or the editor of your choice) to start tweaking. From the open command prompt, the easiest way to do this is to run:

code .

In Visual Studio Code, open the Traversal.Template.csproj file and change its contents to be this:

<Project Sdk="Microsoft.Build.Traversal/1.0.22">
  <ItemGroup>
    <ProjectReference Include=".\src\**\*.csproj" />
  </ItemGroup>
</Project>

That's a basic Microsoft.Build.Traversal project that'll trigger project builds on all projects that live under the \src\ folder. As I mentioned in the post where I talked about using Traversal projects, there's a lot more that you can do here, you can be fully prescriptive about the projects that get built and hook into the various extensibility points that are present to make it even more funky.

Creating the template configuration

Now that the basic structure of the Traversal project is complete, the template itself needs to be described so that dotnet new can reason about what it needs to do with the files in the project template when asked to create a new instance. In order to do that a file called template.json needs to be added to the .template.config directory. From within Visual Studio Code, right-click on the folder in its file Explorer pane (CTRL-SHIFT-E to open it if it's not visible), choose New File from the context menu and name it template.json.

The very first thing to do with this file is to specify the $schema so that Visual Studio Code knows what members can/must be present and let IntelliSense do some of the grunt work for us:

{
    "$schema": "http://json.schemastore.org/template"
}

Once this is present the opening brace will almost straight away gain a green squiggly underline which indicates (I think!) that some mandatory values haven't been specified:

Visual Studio Codes IntelliSense highlighting that some required values haven't been specified in template.json

It's a simple matter to add those values, I'm going to use the following:

{
    "$schema": "http://json.schemastore.org/template",
    "author": "Robert Wray",
    "classifications": [
        "Build"
    ],
    "identity": "RW.ProjectTemplate.Traversal",
    "name": "Microsoft.Build.Traversal.SDK Project",
    "shortName": "traversal"
}

This covers all the required values that are "must haves" according to the schema, therefore they're probably the base-line to get a project template to work with dotnet new. So let's save that file and see where it gets us when used as a project template!

Once the content's saved, return to the command line and run the command dotnet new --install D:\Git\Traversal.Template (substituting the path to the folder on your system) to install the template. If you get an error that looks like this:

D:\Git\Traversal.Template>dotnet new --install D:\Git\Traversal.Template
Error: Could not install "D:\Git\Traversal.Template".
Error: Value cannot be null.

Then rather than scratching your head for five minutes like I did, go back to Visual Studio code and save template.json - in other words, I'd added all the content above but forgotten to save so dotnet new was trying to process a file that contained nothing other than the $schema value, d'oh! If you've got the content and structure of template.json correct, you should see output that looks something like this:

Usage: new [options]

Options:

... SNIP ...


Templates                                         Short Name         Language          Tags
----------------------------------------------------------------------------------------------------------------------------
Console Application                               console            [C#], F#, VB      Common/Console
... SNIP ...
Web/MVC/SPA
Razor Class Library                               razorclasslib      [C#]              Web/Razor/Library/Razor Class Library
ASP.NET Core Web API                              webapi             [C#], F#          Web/WebAPI
Microsoft.Build.Traversal.SDK Project             traversal                            Build
... SNIP ...
Solution File                                     sln                                  Solution

Annoyingly, the only way to tell that your project template has been installed correctly is to look through the list of installed templates that's output and search for it. There's no positive confirmation message that alerts you to its successful installation.

Verifying that it's all working so far is just a matter of running dotnet new traversal, which I did inside a new directory to make sure it didn't spray files everywhere in somewhere I didn't want it to, and it looks like it's worked:

PS D:\Git\Traversal.Template.Test> ls .


    Directory: D:\Git\Traversal.Template.Test


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       10/09/2018     18:13                Traversal.Template


PS D:\Git\Traversal.Template.Test> ls .\Traversal.Template\


    Directory: D:\Git\Traversal.Template.Test\Traversal.Template


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----       10/09/2018     18:14            145 Traversal.Template.csproj

Running dotnet build .\Traversal.Template\Traversal.Template.csproj didn't generate any errors, albeit with no projects to actually build, but that's a good start.

To uninstall the template, run the same command as you did to install, substituting --uninstall for --install (dotnet new keys off the value passed in to use when referring to the template for uninstall). You'll want to do this when making changes to the template to keep everything clean and tidy.

Templating the template

One thing that's apparent from looking at the output above is that the new project generated from the template hasn't inherited any naming, etc,.. from the folder I was in. This holds true even if I explicitly name the project by running dotnet new traversal -n Flibble. To fix this up I'm going to add two additional values to template.json:

"preferNameDirectory": true, 
"sourceName": "Traversal.Template"

The preferNameDirectory value tells dotnet new to match the project name and output directory and sourceName, arguably the more important of the two tells dotnet new what names to replace in the file/directory structure when generating a new project from the template. With these added in and the template re-installed, running dotnet new traversal gives slightly different output:

D:\Git\traversal.test>dotnet new traversal
The template "Microsoft.Build.Traversal.SDK Project" was created successfully.

D:\Git\traversal.test>dir .\

 Directory of D:\Git\traversal.test

10/09/2018  18:45    <DIR>          .
10/09/2018  18:45    <DIR>          ..
10/09/2018  18:45    <DIR>          traversal.test

D:\Git\traversal.test>dir .\traversal.test

 Directory of D:\Git\traversal.test\traversal.test

10/09/2018  18:45    <DIR>          .
10/09/2018  18:45    <DIR>          ..
10/09/2018  18:45               145 traversal.test.csproj

The only remaining nit is the fact that the project file has been created inside a folder, inside the folder, rather than right where we want it. That's because dotnet new has observed, explicitly, the layout of the project the template was created from. So, in the template, move the Traversal.Template.csproj file up to the root folder (and remove the folder it was sat in), re-install the template and run dotnet new traversal, in a directory called flibble:

D:\Git\flibble>dotnet new traversal
The template "Microsoft.Build.Traversal.SDK Project" was created successfully.

D:\Git\flibble>dir .\

 Directory of D:\Git\flibble

10/09/2018  18:53    <DIR>          .
10/09/2018  18:53    <DIR>          ..
10/09/2018  18:53               145 flibble.csproj

That's it, complete! I've used Microsoft.Build.Traversal for this example but you can template any project you want. If there's a baseline set of references / packages your projects need, along with boilerplate legal notices (or other such joy), create and share a template with your colleagues. 

About Rob

I've been interested in computing since the day my Dad purchased his first business PC (an Amstrad PC 1640 for anyone interested) which introduced me to MS-DOS batch programming and BASIC.

My skillset has matured somewhat since then, which you'll probably see from the posts here. You can read a bit more about me on the about page of the site, or check out some of the other posts on my areas of interest.

No Comments

Add a Comment