Creating a project from scratch each time is a challenge and at the same time inspiration because you can try something new 🤩 (new libraries, new practices, etc.). At the project starting point, you have the inspiration to create a project better than the last one. But what if in an organization you have multiple teams with different levels of knowledge and experience in .NET Core. And teams also create projects and components, but does that team build and structure .NET Core project the same as you or the organization expected? And did you thought about the time you spend to add all dependencies you need to cover all quality rules organization has? And only after this task completion you can start to implement business requirements. When we counted that time we spend for all teams in total, we will get a scary number 🤔. Again and again, each developer starting a new project do the same tasks: organize project structure, include dependencies, consulting with a senior developer to get to know how to use those dependencies. Sounds familiar daily routine? Let`s try to improve that part of the development and decrease the time we spend to create a new project using self-made .NET Core project templates. ✨

Using .NET Core project templates we can define project templates including common dependencies, naming convention, and project directory structure. The main goal is to create a new project only with one command dotnet new commonlib -n SimpleProject. And that command adds all dependencies and structure projects automatically without any manual work. Challenge accepted! 🚀

Let`s try out create .NET Core templates!

Steps:

  • Install prerequisite tools
  • Templating tool dotnet new
  • Create template
  • Test template
  • Add custom parameters
  • Pack and publish the template

Prerequisites 🔨

Tools you have to install before create this example project:

Templating tool dotnet new

When start building a new project we use dotnet new command. This command call .NET Core Template Engine that uses a predefined template to create a project. Using command dotnet new --list you can view all installed .NET Core templates. Templates are located in %USERPROFILE%\.templateengine directory. Template Engine is open source and builds on .NET Core.

dotnet/templating
This repo contains the Template Engine which is used by dotnet new - dotnet/templating
https://github.com/dotnet/templating

What I like in .NET Core Template Engine that you do not need to tokenize your template with magic symbols, like CraftBakery.{value-to-replace} or CraftBakery.%value-to-replace%, etc. .NET Core Template Engine uses your defined words, find all matches and replace with values you pass to dotnet new command. The main benefit is when you create your template it compiles and you can run and test that.

Benefits described in Microsoft documentation about .NET Core templates. đź‘Ť

The template engine doesn't require you to inject special tokens into your project's source code.

The code files aren't special files or modified in any way to work with the template engine. So, the tools you normally use when working with projects also work with template content.

You build, run, and debug your template projects just like you do for any of your other projects.

You can quickly create a template from an existing project just by adding a ./.template.config/template.json configuration file to the project.

Create template 🏗️

Creating .NET Core template starts with running the same command as we will create new projects from scratch. We will create template for making .NET Core libraries.

The template we make consists of the following parts:

  • src - the directory contains all source code
  • tests - the directory contains all tests
  • .gitignore - contains VS Code and Visual Studio Git ignore the definition
  • README.md - readme file, that contains project description
  • .sln - project solution file

Usually, when I create .NET Core projects I follow guidelines that David Fowler published on GitHub.

.NET project structure
.NET project structure. GitHub Gist: instantly share code, notes, and snippets.

Create template base

To create template parts I use Windows Terminal. By executing the following commands we will create a template base.

At first, we need to create a directory our template will be placed commonlib and two subdirectories src and tests.

> mkdir commonlib
> cd commonlib

> mkdir src
> mkdir tests
Commands to create directory structure

Our template base will consist of two .NET Core projects. One for the library, placed in src directory, and one for tests (my preference is for xUnit test framework), placed in tests directory. All projects are added to the solution file .sln. For each project we created, I recommend deleting all default classes Class1.cs and UnitTest1.cs.

> dotnet new sln -n CraftBaker
> dotnet new classlib -n CraftBakery -o .\src\
> dotnet new xunit -n CraftBakery.Test -o .\tests\
> dotnet add .\tests\CraftBakery.Test\CraftBakery.Test.csproj reference .\src\CraftBakery\CraftBakery.csproj

> dotnet sln add .\src\CraftBakery\CraftBakery.csproj --solution-folder src
> dotnet sln add .\tests\CraftBakery.Test\CraftBakery.Test.csproj --solution-folder tests
Commands to add solution and projects

And at the end, we create README.md and .gitignore files by executing the following commands.

> New-Item README.md
> New-Item .gitignore
Commands to add readme and gitignore files
Content for .gitignore I generated using https://www.toptal.com/developers/gitignore.

After executing all commands we should get a template base structure.

C:\...\commonlib\
│   .gitignore
│   CraftBakery.sln
│   README.md
│
├───src
│   └───CraftBakery
│           CraftBakery.csproj
│
└───tests
    └───CraftBakery.Test
            CraftBakery.Test.csproj
Template base structure.

Add template configuration

.NET Core Template Engine uses configuration directory .template.config. and template configuration file .template.config/template.json. Template configuration members are described in JSON scheme (http://json.schemastore.org/template). If you use Visual Studio Code to edit template.json file you will get IntelliSense.

Inside directory commonlib create template configuration parts, by executing the following commands.

> mkdir .template.config
> New-Item .\.template.config\template.json
Commands to add template configuration.

Open newly created template.json file using Visual Studio Code by File -> Open File.  Change template configuration file as follows.

{
  "$schema": "http://json.schemastore.org/template",
  "author": "craftbakery.dev",
  "identity": "CraftBakery.Common.Project.Structure.Empty",
  "name": "Common library project structure (empty)",
  "shortName": "commonlib",
  "tags": {
      "language": "C#",
      "type": "project"
  },
  "sourceName": "CraftBakery",
  "classifications": ["Library"]
}
Minimum members required to define template.

Think more of configuration members are self describable, for more detailed description see Microsoft documentation. In a nutshell, the main members contains author name, template describable name, unique identifier, short command name we will use with dotnet new commonlib and keyword sourceName value Tamplate Engine will replace with the value we specified in -n argument dotnet new commonlib -n SomeName.

That`s it, now we can try to install and check if our template is working correctly.

Test template 🧪

Before sharing our template with organization teams we should test it. To test it locally on your machine is very simple. You have to install the template and try to use it. After that, you can check if the template creates the project as you expected.

Install template

Installing a template is easy. You have to execute the same dotnet new command with one argument.

> dotnet new -i .\commonlib\
Command to install template locally.

After executing the command you will get the full list of installed templates. In list, you will find also your template commonlib.

List of installed templates.

Create a project using the template

To use our created template we execute one command dotnet new commonlib -n SimpleProject.  Try to execute that command in a different location to avoid template overriding. After executing the command you have to get the following project structure.

C:\...\SampleProject
│   .gitignore
│   README.md
│   SampleProject.sln
│
├───src
│   └───SampleProject
│           SampleProject.csproj
│
└───tests
    └───SampleProject.Test
            SampleProject.Test.csproj
Sample project structure created using commonlib template.

It is important to notice, that when you test your template it is not necessary to reinstall the template after each change. In the template installation step, you have specified directory that points to the template, so all changes you make will be available without any additional moves.

Uninstall template

Uninstalling a template is easy, the same as installing. When uninstalling the template you have to specify the full path to the template.

By executing command dotnet new -u you will get the full list of templates with detailed information. That information also includes the uninstall command for the template.

Template details with uninstall command.

Add custom parameters

For a real live template, it is not enough with a one -n name parameter which maps to sourceName template member. You also need additional parameters like project author, project description, etc. .NET Core Template Engine allows us to define custom parameters in template.json configuration. Member symbols is responsible for that. This member accepts an array of objects with the parameter description. Each parameter could be described with different attributes.

  • type - attribute has only one option parameter
  • replaces - keyword value which will be replaced with the value passed to this parameter

Describe custom parameters

For demonstration purposes, we will use two of all attributes type and replaces. To add three custom parameters you need to modify .template.config\template.json configuration file.

{
  "$schema": "http://json.schemastore.org/template",
  "author": "craftbakery.dev",
  "identity": "CraftBakery.Common.Project.Structure.Empty",
  "name": "Common library project structure (empty)",
  "shortName": "commonlib",
  "tags": {
      "language": "C#",
      "type": "project"
  },
  "sourceName": "CraftBakery",
  "classifications": ["Library"],
  "symbols": {
    "author":{
      "type": "parameter",
      "replaces": "Project author"
    },
    "description":{
      "type": "parameter",
      "replaces": "Project description"
    },
    "year":{
      "type": "parameter",
      "replaces": "Year"
    }
  }
}
Template configuration with custom parameters.

We have added three parameters author, description, and year. To check if parameters are defined correctly execute command dotnet new commonlib -h. This command outputs generated help.

Template generated help with custom parameters.

What is interesting that .NET Core Template Engine automatically creates short names for our parameters. đź‘Ť

Use custom parameters 🔧

Also after defining parameters we have to put that values. I choose to add parameter values to library .\src\CraftBakery\CraftBakery.csproj file.

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

  <PropertyGroup>
    <Authors>Project author</Authors> <!-- Maps to author parameter -->
    <Description>Project description</Description> <!-- Maps to description parameter -->
    <Copyright>Copyright © Project author, Year</Copyright> <!-- Maps to author and year parameter -->
    <LangVersion>latest</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <ProjectReference Include="..\..\tests\CraftBakery.Test\CraftBakery.Test.csproj" />
  </ItemGroup>

  <PropertyGroup>
    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

</Project>
CraftBakery.csproj with additional project description parameters.

To test if our template replaces all parameter values we have to execute the command.

> dotnet new commonlib -n SampleProject -au "Valdis Pavlukevics" -d "Demonstration project" -y 2020
Commands to add create sample project using template commonlib parameters.

If you open SampleProject.csproj you will see that all values are replaced correctly.

<PropertyGroup>
    <Authors>Valdis Pavlukevics</Authors>
    <Description>Demonstration project</Description>
    <Copyright>Copyright © Valdis Pavlukevics, 2020</Copyright>
    <LangVersion>latest</LangVersion>
</PropertyGroup>
Sample project with replaced values.

Pack and publish the template

We have tested our template and currently, we are ready to share the template with other developers. Packing and publishing for templates are the same as other codebases you publish in NuGet. The template package can contain a bunch of templates. You can pack all your organization template in one package. This sounds awesome. 🤩

⚠️ Before packing and installing package run dotnet new -u to uninstall templates we used for testing.

Prepare template package ⚙️

To pack a template you have to create a directory, place in that directory all templates and in the root directory create template project file CraftBakery.Templates.csproj file.

C:\...\craftbakerytemplates
│   CraftBakery.Templates.csproj
│
└───templates
    └───commonlib
        │   .gitignore
        │   CraftBakery.sln
        │   README.md
        │
        ├───.template.config
        │       template.json
        │
        ├───src
        │   └───CraftBakery
        │           CraftBakery.csproj
        │
        └───tests
            └───CraftBakery.Test
                    CraftBakery.Test.csproj
Templates package structure.

Using Visual Studio Code modify template package definition in CraftBakery.Templates.csproj as following.

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

  <PropertyGroup>
    <PackageType>Template</PackageType>
    <PackageVersion>1.0</PackageVersion>
    <PackageId>CraftBakery.Templates</PackageId>
    <Title>CraftBakery Templates</Title>
    <Authors>Valdis Pavlukevics</Authors>
    <Description>Templates used for demonstration purpose.</Description>
    <PackageTags>dotnet-new;templates;craftbakery</PackageTags>

    <TargetFramework>netstandard2.0</TargetFramework>

    <IncludeContentInPack>true</IncludeContentInPack>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <ContentTargetFolders>content</ContentTargetFolders>
    <NoWarn>$(NoWarn);NU5128;NU5115</NoWarn>
  </PropertyGroup>

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

</Project>

The package definition is very simple. The important part of this file is ItemGroup. This element describes what directories are included in the package and what is excluded.

Pack template into NuGet package 📦

To make a NuGet package with our templates we have to execute one command dotnet pack.

Template package making output.

In the output, we could notice that our .gitignore file was not included in package. To include that file we need got yellow advice to run pack command with -NoDefaultExcludes parameter. After executing again dotnet pack -NoDefaultExcludes we will get our package.

Publish template package

For demonstration purposes, I do not publish my package to NuGet.org. I use locally running NuGet server BaGet. How to run that server locally check this link https://loic-sharma.github.io/BaGet/installation/local/. To publish the template package you have to execute the command dotnet nuget push.

> dotnet nuget push -s http://localhost:5000/v3/index.json CraftBakery.Templates.1.0.0.nupkg
Template package published to local NuGet server.

Install template package

If you use an on-premise hosted NuGet package server then you have to register the package's feed address to your local configuration. Following command registers locally running NuGet server.

> dotnet nuget add source http://localhost:5000/v3/index.json --name local
To remove local package source execute dotnet nuget remove source local

Now you are ready to install all templates your package contains. To install template package you have to execute command dotnet new -i and specify package unique identifier you added PackageId element.

Template installation from package.

Now you are ready to use your template. 🥳

Summary🎄

In this post, we learned how we can unify our experience and knowledge into project templates pack them and publish them. Templates are the same as projects, you can compile them, debug or just take an existing project and make them as a template. Now each team in the organization can build projects faster and do not think about covering quality rules at the starting point, because templates solve a bunch of questions automatically. And we can spend more time coding what we love to do ❤️. If you are interested to dive into templating deeply check the samples repository https://github.com/dotnet/dotnet-template-samples.


Have fun with making templates and perform faster! 🚀

[eof]