BT

InfoQ Homepage Articles Using the .Net Core Template Engine to Create Custom Templates and Projects

Using the .Net Core Template Engine to Create Custom Templates and Projects

Bookmarks

Key Takeaways

  • The .NET CLI includes a template engine, which can create new projects and project items directly from the command line. This is the “dotnet new” command.
  • The default set of templates covers the essential project and file types you need for default console and ASP.NET-based apps, and test projects.
  • Custom templates can create more interesting or bespoke projects and project items, and can be distributed and installed from NuGet packages or directly from the file system.
  • Custom templates can be very simple or much more complex, with substitution variables, command-line parameters and conditional inclusion of files or even lines of code.
  • Maintenance and testing of custom templates is easy, even with conditional code, by making sure a project template is always a runnable project.

This article is part of a series that explores the benefits of .NET Core and how it can help not only traditional .NET developers but all technologists that need to bring robust, performant and economical solutions to market.

With the release of .NET Core 3.0, Microsoft has the next major version of the general purpose, modular, cross-platform and open source platform that was initially released in 2016. .NET Core was initially created to allow for the next generation of ASP.NET solutions but now drives and is the basis for many other scenarios including IoT, cloud and next generation mobile solutions. Version 3 adds a number of oft-requested features such as support for WinForms, WPF and Entity Framework 6.

 

The tooling story changed dramatically with .NET Core, because of its serious emphasis on the command line. This is a great fit for .NET Core's cross-platform, tooling-agnostic image. The dotnet CLI is the entry point to all of this goodness, and it contains many different commands for creating, editing, building, and packaging .NET Core projects. Here, we’ll focus on just one aspect of the dotnet CLI — the dotnet new command.

This command is mainly used to create projects, and you can often create a simple boilerplate project and then forget about it. We’ll look at how to get the most out of this command, by passing arguments to modify the generated projects, and seeing how we can use the command to create files as well as projects. We'll also see that this tool is a full-fledged template engine, and can be used to install custom templates, as well as make personal templates.

dotnet new in action

So how do you use dotnet new? Let's start at the beginning and work up to the most interesting stuff. To create a simple console application, start up the command line, change directory to a new empty folder (an important step, explained below), and call dotnet new console:

> dotnet new console
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on /Users/matt/demo/MyNewApp/MyNewApp.csproj...
  Restoring packages for /Users/matt/demo/MyNewApp/MyNewApp.csproj...
  Generating MSBuild file /Users/matt/demo/MyNewApp/obj/MyNewApp.csproj.nuget.g.props.
  Generating MSBuild file /Users/matt/demo/MyNewApp/obj/MyNewApp.csproj.nuget.g.targets.
  Restore completed in 234.92 ms for /Users/matt/demo/MyNewApp/MyNewApp.csproj.

Restore succeeded.

As I mentioned before, make sure you're in a new, empty folder first. By default, dotnet new will create files in the current folder and will not delete anything that's already there. You can make it create a new folder by using the --output option. For example, you could create a project in a new folder called ConsoleApp42 by typing:

> dotnet new console --output ConsoleApp42
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on ConsoleApp42/ConsoleApp42.csproj...
  Restoring packages for /Users/matt/demo/ConsoleApp42/ConsoleApp42.csproj...
  Generating MSBuild file /Users/matt/demo/ConsoleApp42/obj/ConsoleApp42.csproj.nuget.g.props.
  Generating MSBuild file /Users/matt/demo/ConsoleApp42/obj/ConsoleApp42.csproj.nuget.g.targets.
  Restore completed in 309.99 ms for /Users/matt/demo/ConsoleApp42/ConsoleApp42.csproj.

Restore succeeded.

Looking at what you’ve created

At this point, dotnet new has created a new console project and restored NuGet packages — it's all ready to run. But let's take a look at what's been created:

> ls ConsoleApp42/
ConsoleApp42.csproj  Program.cs           obj/

As you can see, you now have a project file based on the name of the output folder. If you wish, you could use the --name parameter to specify a different name:

dotnet new console --output ConsoleApp42 --name MyNewApp

This would create the project files in a folder called ConsoleApp42, and would use MyNewApp as the name of the console application being created — you'd get MyNewApp.csproj. If you take a look at Program.cs, you'll also see that the name parameter is used to update the namespace:

using System;

namespace ConsoleApp42
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }
    }
}

Prepping for another project

But, if you take a look at the folder structure of the project you've just created, you might spot  something missing — there's no solution file. You've only got a single project, and while this works fine with dotnet run, it will cause problems when you want to add another project. You can easily create one:

dotnet new sln

This will create a new, empty solution file. Then, it’s another step to add a project to it.
If you created your solution in our demo's root folder, it would look like:

dotnet sln add ConsoleApp42/MyApp.sln

You can also use the dotnet sln command to remove or list projects in a solution. If you want to add or remove references to a project, you need to use the dotnet add command. I suggest reading Jeremy Miller's article on the extensible dotnet CLI for more details, or type dotnet help sln or dotnet help add.

Adding another project is very easy also, but you must do it in this two-step fashion — create, then add. For example, you could add a test project to your solution:

dotnet new nunit --output Tests --name MyAppTests
dotnet sln add Tests/MyAppTests.csproj

Adding new files to a project

Adding new files to a project is even easier, mostly thanks to the improvements .NET Core made to MSBuild files. You no longer need to explicitly list C# files in the .csproj file, because they're automatically picked up through wildcards. You just need to create a file in the folder and it will automatically become part of the project. You can create the file manually, but you can also use dotnet new to provide a template file. For example, you could add a test file to your test project using the nunit-test item template:

dotnet new nunit-test --output Tests --name MyNewTests

Speaking of templates, how do you know what templates are available? How can you tell the difference between a project template and an item template? That's a job for dotnet new --list, which outputs a list of available templates:

Templates Short Name Language Tags
Console Application console [C#], F#, VB Common/Console
Class library classlib [C#], F#, VB Common/Library
Unit Test Project mstest  [C#], F#, VB Test/MSTest
NUnit 3 Test Project nunit [C#], F#, VB Test/NUnit
NUnit 3 Test Item nunit-test [C#], F#, VB Test/NUnit
xUnit Test Project xunit [C#], F#, VB Test/xUnit
Razor Page page [C#] Web/ASP.NET
MVC ViewImports viewimports [C#] Web/ASP.NET
MVC ViewStart viewstart [C#] Web/ASP.NET
ASP.NET Core Empty web [C#], F# Web/Empty
ASP.NET Core Web App (Model-View-Controller) mvc [C#], F# Web/MVC
ASP.NET Core Web App razor [C#] Web/MVC/Razor Pages
ASP.NET Core with Angular angular [C#] Web/MVC/SPA
ASP.NET Core with React.js react [C#] Web/MVC/SPA
ASP.NET Core with React.js and Redux reactredux [C#] Web/MVC/SPA
Razor Class Library razorclasslib [C#] Web/Razor/Library/Razor Class Library
ASP.NET Core Web API webapi [C#], F# Web/WebAPI
global.json file globaljson   Config
NuGet Config nugetconfig   Config
Web Config webconfig   Config
Solution File sln   Solution

This lists all templates. You can use the --type parameter to filter this down, using --type project, --type item, or --type other. Project templates will create a project, item templates create a single file, while other is really only useful for the sln template to create a solution file.

The short name (2nd column above) in this list is the name you use in the call to dotnet new (e.g. dotnet new console, dotnet new classlib, dotnet new mvc, etc). Some templates support multiple languages, with the default shown in square brackets (spoiler — it's always C#). You can choose a different language with the --language option, but be careful of the # symbol! Some command-line shells treat this as a comment character and parsing can fail with --language F#. This can be handled by quoting the value - "--language F#".

Finally, each template has one or more tags. These are a way of classifying templates, but aren't currently used as part of the command-line tooling. However, they could be used for grouping or filtering by other hosts. Yes, that's right, the dotnet new template engine can be used in other hosts, such as IDEs. More on that later.

Customising templates

So far, we've just looked at a very simple Hello World console app, and added some tests. Let's take a look at something more interesting. Say you want to create a new ASP.NET project. Looking at the list of templates above, you have a few choices. You could create an empty web project, an MVC project, a project with Angular, or one with React.js. But these are fairly rigid templates. Can you customise these at all? The good news — yes, you can.

Templates can take in parameters that change what's being generated. The -- help command will provide details on the parameters this template understands. Let's start with a simple example:

> dotnet new classlib --help
Class library (C#)
Author: Microsoft
Description: A project for creating a class library that targets .NET Standard or .NET Core
Options:
-f|--framework  The target framework for the project.
                      netcoreapp2.1     - Target netcoreapp2.1
                      netstandard2.0    - Target netstandard2.0
                  Default: netstandard2.0

  --no-restore    If specified, skips the automatic restore of the project on create.
                  bool - Optional
                  Default: false / (*) true


* Indicates the value used if the switch is provided without a value.

Here you can see that the classlib template has two parameters: --framework to specify what target framework is written to the project file; and --no-restore, to control if NuGet restore is performed when the project is created.

dotnet new classlib --framework netcoreapp2.1 --no-restore

The web templates have similar parameters, but there are many more of them than we have space to list here. Try dotnet new mvc --help to get an idea of what's available. There are parameters to decide what type of authentication you want, whether to disable HTTPS or not, whether to use LocalDB instead of SQLite, and so on. Each of these parameters changes how the template code is generated, either replacing content in files or including/excluding files as appropriate.

While we're talking about help, here are two very useful commands: dotnet help new, which opens a web page on the dotnet new command itself; and dotnet new {template} –help, which shows help for the named template and its parameters.

Adding customised templates

The real power of the dotnet new command is the ability to add new, custom templates. Even better, templates can be distributed and shared, simply by packing them into a NuGet package and uploading to nuget.org. This makes it very easy to get started with a framework, or automate the boilerplate of creating new projects or project items.

To add a new custom template, use the dotnet new --install {template} command, passing in either the name of a NuGet package, or a file folder for a local template. But how do you find new templates?

One way is to search for the framework you're using and see if templates are available, but that's a bit hit and miss. Fortunately, you can instead visit dotnetnew.azurewebsites.net and search for templates by keywords. There are over 500 templates tracked on the site, which makes it a good discovery resource.

For example, you could install a set of templates for AWS Lambda projects with dotnet new  --install Amazon.Lambda.Templates. One very nice feature of installing templates via NuGet packages is that each package can contain more than one template. This AWS Lambda package contains 28 different templates, including a tutorial project.

Of course, if you no longer want to use the template, simply uninstall it with dotnet new  --uninstall {package}. The name passed here is the name of the installed template package, and if you're not sure of the  name, simply run dotnet new --uninstall to get a list.

Creating your own templates

You can also create your own custom templates. These don't have to be for popular frameworks, but might be for internal or personal projects. Essentially, if you often find yourself creating a specific folder structure, set of references, or boilerplate files, consider creating project or item templates. Project templates are simply plain text files, including the .csproj files — there's no requirement that the generated templates are .NET Core specific, and they can be made to target any framework.

It’s very easy to create a new template and they are surprisingly easy to maintain. Traditionally, templates that can perform text substitution use a special syntax, like $VARIABLE$ markers that will be replaced when the template is evaluated. Unfortunately, this is usually invalid syntax for the file type, which makes it impossible to run the project to test that the template is correct. This leads to bugs and slow iteration times, and basically, a bit of a maintenance headache.

Fortunately, the designers of the template engine have thought about this, and come up with a much nicer way of working: running templates.

The idea is simple — the template is just plain text files. No special formats, no special markers. So, a C# file is always a valid C# file. If a template wishes to substitute some text, such as replacing a C# namespace for one that's based on the project name, this is handled with simple search and replace. For example, imagine we had a template that looked like this:

namespace RootNamespace
{
  public static class Main
  {
    // ...
  }
}

The template's JSON configuration defines a symbol that will replace the namespace. The symbol value would be based on the project name, possibly with a built-in transform applied to make sure it only contains valid characters. The symbol would also define the text it was replacing — "RootNamespace." When the template engine processes each file, if it sees "RootNamespace," it will replace it with the symbol value.

This simple search and replace is usually based on a symbol that is based on a parameter, such as the template name, the output name, or an actual custom parameter. But, it's also possible to create symbols based on generators, to create GUIDs, random numbers, or the current timestamp, and so on.

But no template is complete without conditional code — something that's added or removed based on a parameter. How does dotnet new handle this and keep "running templates" as an option? This is actually handled on a per-file-type basis, with some default config built in, and the ability to define your own style for unknown file formats. Essentially, the idea is to use the file-specific pre-processor (such as #if for C# or C++) for those file types that support it, and specially formatted comments for those that don't, such as JSON.

```cs
public class HomeController : Controller
{
  public IActionResult Index() => View();

  public IActionResult About()
  {
    ViewData["Message"] = "Your application description page.";
    return View();
  }

#if (EnableContactPage)
  public IActionResult Contact()
  {
    ViewData["Message"] = "Your contact page.";
    return View();
  }
#endif

  public IActionResult Error() => View();
}
```

All of the metadata for a template lives in a template.json file. This includes the template's short name, description, author, tags, and supported language. Because a template can only target a single language, it also includes a "group identity" option, which multiple templates can specify, one for each language. The metadata file can also include optional information about source and target of files to be copied or renamed, conditional file copies, substitution symbols, command-line parameters and post-creation actions such a package restore. But by default, the template engine will copy and process all files in the template’s file structure.

{
  "author": "Matt Ellis",
  "classifications": [ "Hello world" ],
  "name": "Hello world template",
  "identity": "Matt.HelloWorldTemplate.CSharp",
  "shortName": "helloworld",
  "guids": [ "d23e3131-49a0-4930-9870-695e3569f8e6" ],
  "sourceName": "MyTemplate"
}

The template.json file must be placed at the root of the template's folder structure, in a folder called .template.config. The rest of the folder structure is entirely up to you — the template engine will keep the same folder structure when evaluating the template. In other words, if you add a README.md file to the root of your template's folder structure, then the template engine will create a README.md in the root of the output folder when you call dotnet new. So, if you use --output MyApp, you will get a file called MyApp/README.md.

> tree -a
.
├── .template.config
│   └── template.json
├── MyTemplate.csproj
├── Program.cs
└── Properties
    └── AssemblyInfo.cs

2 directories, 4 files

To install and test your template, simply call dotnet new --install {template} as you would to install a custom template, but this time, pass in the path to the root of the template folder structure. To uninstall, use dotnet new --uninstall {template}. Again, if you're not sure of what to pass, use `dotnet new --uninstall` to get a full list.

Distributing your templates

Once you're ready to distribute your template, you can pack it into a NuGet package and upload to nuget.org. You'll need to create a .nuspec file as normal, but with two slight tweaks: add a packageType element and set the name attribute to "Template," then make sure  the template folder structure is copied into a folder called "content"

<package>
  <metadata>
    <id>MattDemo.HelloWorldTemplate</id>
    <version>1.0</version>
    <authors>Matt Ellis</authors>
    <description>Hello World template</description>
    <packageTypes>
      <packageType name="Template" />
    </packageTypes>
  </metadata>
  <files>
    <file src=".template.config/template.json" target="content/.template.config" />
    <file src="MyTemplate.csproj" target="content/" />
    <file src="Program.cs" target="content/" />
    <file src="Properties/*" target="content/Properties/" />
  </files>
</package>

Additionally, it's possible to include multiple templates in a single package — simply create multiple folders under "content" and add a .tempate.config/template.json for each template.

There are many more options and capabilities in the template.json file, but covering them all is beyond the scope of this article. But, based on all we’ve covered here, you can see that the template engine is very powerful, flexible, and yet fairly straightforward to use. Please check out the Microsoft docs site as well as the wiki for the dotnet/templating GitHub site.

The Template Engine

One of the most interesting things about dotnet new is that it’s designed to be used from multiple hosts. The dotnet new CLI tool is simply one host — the template engine itself can be used as an API from other applications. This is great for those of us who prefer to work with an IDE instead of the command line, but still want to be able to easily add custom project templates, something that isn't always easy with an IDE.

We can see this in action in JetBrains Rider. The New Project dialog is powered by the template engine APIs, listing all the available templates, even custom templates. When the user wishes to create a project, the template engine is used to generate the files.

If you look closely, you'll see that Rider has more templates than the .NET CLI. This is because Rider ships extra templates to support .NET Framework and Xamarin projects. The template engine API allows hosts to install templates to a custom location, and can list them from both, meaning Rider will show custom templates installed by dotnet new --install as well as using the Install button in the new project dialog’s "More templates" page. Once reloaded, the new template is shown in the list, just like all the others.

New, custom projects with ease

The dotnet new command makes it easy to create new projects and project items. The default set of templates will get you started with building .NET Core applications, either command line or ASP.NET based, and will help create test projects and target other .NET languages. Custom templates can be easily installed to create projects with other requirements, such as a different folder structure or framework dependencies. And the format for custom templates makes it easy to create your own, taking advantage of substitution variables and conditional code, but still keeping the template project compilable and maintainable. Together with the dotnet sln command, as well as the other extensible dotnet CLI commands, the new template engine makes it easy to create and manage projects, project items and solutions consistently, cross platform and directly from the command line.

About the Author

Matt Ellis is a developer advocate at JetBrains. He has spent over 20 years shipping software in various industries and currently works with IDEs and development tools, having fun with abstract syntax trees and source code analysis. He also works on the Unity support in Rider.

 

This article is part of a series that explores the benefits of .NET Core and how it can help not only traditional .NET developers but all technologists that need to bring robust, performant and economical solutions to market.

With the release of .NET Core 3.0, Microsoft has the next major version of the general purpose, modular, cross-platform and open source platform that was initially released in 2016. .NET Core was initially created to allow for the next generation of ASP.NET solutions but now drives and is the basis for many other scenarios including IoT, cloud and next generation mobile solutions. Version 3 adds a number of oft-requested features such as support for WinForms, WPF and Entity Framework 6.

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT

Is your profile up-to-date? Please take a moment to review and update.

Note: If updating/changing your email, a validation request will be sent

Company name:
Company role:
Company size:
Country/Zone:
State/Province/Region:
You will be sent an email to validate the new email address. This pop-up will close itself in a few moments.