The .NET SDK has many built-in templates that you can use to create new applications. However, in many cases you would like to add your own components (such as nugets) without the need to configure them. You can, of course, create an application from the default template, then manually copy and paste all required stuff but it takes some time. Fortunately, there’s a better way. You can create your custom templates and add whatever you want to them. In the next sections, I’ll show you how to do this.

Introduction

The dotnet new command creates a .NET project or other artifacts based on a template. It calls the template engine (which is open source and the source code can be found here) to create the artifacts on disk based on the specified template and options. You can also use this command to install/uninstall templates. It doesn’t require installing additional packages. All you need is the .NET SDK.

Creating a new template

Let’s create a new template called my-custom-template. For this purpose, create a hierarchical structure of files and directories as below:

  • templates
    • my-custom-template
      • .template.config
        • template.json
      • source
      • SERVICE_NAME.sln

A template.json file is the definition of your template. You can specify its name, parameters and much more. For now, copy and paste the following content (it’s quite legible):

{
  "$schema": "http://json.schemastore.org/template",
  "classifications": [ "C#" ],
  "identity": "My.Custom.Api",
  "name": "My Custom Template",
  "shortName": "my-custom-template",
  "tags": {
    "language": "C#",
    "type": "project"
  },
  "symbols": {
    "serviceName": {
      "type": "parameter",
      "datatype": "text",
      "description": "Service name",
      "replaces": "SERVICE_NAME",
      "fileRename": "SERVICE_NAME",
      "isRequired": true
    }
  }
}

In the code above I’ve already defined a template called My Custom Template with a required parameter serviceName. Now you can use this parameter in the names of files or directories and in the content of any file. If you create a new application based on this template, all occurrences of SERVICE_NAME will be replaced with the provided data.

In the source directory let’s add a web API project SERVICE_NAME.Api.csproj (or whatever else you want). You can take a look at the template I’ve created here.

Finally, I have a draft of my template. Let’s check out other features offered by the template engine.

Transformation of parameters

Sometimes you want to change the value of the input parameter. In such situations, you can use data transformations. There are many ways to do this, I’ll show you one of them. For example, if you want a lowercase version of the serviceName parameter, you can do the following:

  • Define a new parameter of type derived.
  • Add a forms section and create a new form that transforms its value.
  • Use it wherever you want.

You can find a full example below:

{
  ...
  "symbols": {
    ...
    "serviceLowerName": {
      "type": "derived",
      "datatype": "text",
      "valueSource": "serviceName",
      "valueTransform": "lowerCaseForm",
      "replaces": "SERVICE_LOWER_NAME",
      "fileRename": "SERVICE_LOWER_NAME"
    }
  },
  "forms": {
    "lowerCaseForm": {
      "identifier": "chain",
      "steps": [
        "lowerCaseInvariant"
      ]
    }
  }
  ...
}

Now you can use SERVICE_LOWER_NAME in the same way as SERVICE_NAME. What is more, you can always reuse already defined forms for other parameters.

Custom operations

In some cases, you want to conditionally generate the content of the file. The template engine allows you to define a section called SpecialCustomOperations, for example:

{
  ...
  "symbols": {
    ...
  },
  "SpecialCustomOperations": {
    "**/*.txt": {
      "operations": [
        {
          "type": "conditional",
          "configuration": {
            "if": [ "---#if" ],
            "else": [ "---#else" ],
            "elseif": [ "---#elseif" ],
            "endif": [ "---#endif" ],
            "trim": "false",
            "wholeLine": "true"
          }
        }
      ]
    }
  }
}

In the example above, I’ve just defined conditional operations for files with the extension txt. If I define a custom parameter of type choice in the following way:

{
  ...
  "symbols": {
    ...
    "customChoice": {
      "type": "parameter",
      "datatype": "choice",
      "description": "Custom choice description",
      "choices": [
        {
          "choice": "first",
          "description": "First description"
        },
        {
          "choice": "second",
          "description": "Second description"
        }
      ],
      "isRequired": false,
      "defaultValue": "first"
    }
  }
  ...
}

Then I can write in any txt file:

---#if (customChoice=="first")
customChoice -> first
---#elseif (customChoice=="second")
customChoice -> second
---#else
customChoice -> none
---#endif

If any condition is met, its content will be added.

Installing templates

At this stage, I’ve already discussed the configuration of templates. It’s time to find out how to install them. You can do this by:

  1. Specifying a file system directory that contains templates.
  2. Creating and installing a nuget package.

Specifying a file system directory

It’s quite simple. You have to go to the root folder of your templates and run the command dotnet new -i .\. That’s all. If you want to uninstall templates, just execute the command dotnet new -u and choose which one you want to delete.

Creating and installing a nuget package

First of all, you have to create a nuget package. Go to the root folder, create a file called templatepack.csproj and paste the following content (I don’t think any further explanation is needed):

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <PackageType>Template</PackageType>
    <PackageVersion>1.0.0</PackageVersion>
    <PackageId>Custom.Templates</PackageId>
    <Title>Custom Templates</Title>
    <Authors>Me</Authors>
    <Description>Custom Templates</Description>
    <PackageTags>dotnet-new;templates</PackageTags>
    <TargetFramework>netstandard2.1</TargetFramework>
    <IncludeContentInPack>true</IncludeContentInPack>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <ContentTargetFolders>content</ContentTargetFolders>
    <NoDefaultExcludes>true</NoDefaultExcludes>
  </PropertyGroup>

  <ItemGroup>
    <Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**;templates\**\.vs\**" />
    <Compile Remove="**\*" />
  </ItemGroup>

</Project>

After that, you can simply run the command dotnet pack .\templatepack.csproj (remember to update the package version) to create a new nuget package. You can install it by executing the command dotnet new -i Custom.Templates.<version>.nupkg. If you want to uninstall templates just run the command dotnet new -u Custom.Templates.

Usage

Once you’ve installed your custom templates, you can use them by executing the command dotnet new <template-name>. For the template above, the usage looks like dotnet new my-custom-template --serviceName Custom.Application --customChoice="second". If you followed step by step, you should see the newly generated application from your own template. Congratulations!

Summary

In this post I showed you how to create and use custom .NET templates.

The template engine offers features that allow you to replace values, conditionally generate the content of the file and execute custom operations when your template is used.

The source code of the above examples can be found here.