Step-by-Step Guide to Creating a .NET New Template
As a Software Engineer, I created dozens of solution projects throughout my career, and through that time I did all kinds of messed up code organization.
When you create many solutions, you start to realize some patterns and apply the copy-and-paste strategy to have your projects with the same structure, file location, etc. After reaching that point, you realise how much time you are wasting in creating the solution, the files, the DevOps pipelines, and so on, and think: “There must be a better way to do this, or at least that takes less time”.
Guess what?! There is.
Prerequisites
Before we begin, make sure you have the following installed on your machine:
- .NET Core SDK (6 or later).
- Visual Studio or Visual Studio Code (optional but recommended).
I’m using .NET SDK 8, and Visual Studio 2022. But it is a bit irrelevant. You must be comfortable with the tools while following the article.
With the SDK projects, creating a dotnet new template is as simple as complex as you want it to be. My experience states that you should keep it simple as you can, to reuse your templates. Instead of having a mega template with a Solution Structure, Source Code Structure, Test Code Structure, etc, you should have one template for each.
Step 1: Identifying your need Structure
Before stepping into the template, we must think about what we are templating about. Writing this down will go a long way in creating our template.
For now, we keep in mind that the supported types are: project, item, or solution, and for purposes of this article I’ll keep things clean and simple, but remember that a lot can be done with templates. Check out the official documentation.
We will create a template for a solution and a project, and we will start by defining the structure of our template.
So for our Solution Template, we will have the following:
File Description:
- .editorconfig: IDE file with the rules and code styles to be applied to the project.
- .gitignore: Git ignore file.
- azure-pipelines.yml: Azure DevOps build pipeline.
- CHANGELOG.md: We will keep the history of the project.
- Directory.Build.props: We will have the metadata of our project.
- global.json: Define which .NET SDK version is used.
- icon.64.png: Icon for the application.
- LICENSE: The open-source license that we have. MIT License in this case.
- nuget.config: Defines the source of the package.
- README.md: Information file about the project.
The .placeholders are files the only purpose is to keep the folders live since in git empty folders are not pushed to the server.
The SolutionTemplate.sln is the empty solution file. Although is irrelevant the name of the solution, it is crucial to have a simple name to use because this is the name that will be replaced when creating a new solution with our template.
This structure is usually my standard for new solutions, and once structured the automation will be cleaner.
For our Project Template, we will have the following:
This template project is a REST API project, with:
- Serilog for logging.
- Feature Flags.
- HealthCheck endpoint.
- A default controller.
- Global Exception handler.
- OpenAPI Specification Configuration.
It is nothing much, it is irrelevant what the project holds, but it is the default and standard structure of new projects. What is relevant is the name of the project: ProjectTemplate.csproj, which will be the name to be replaced when creating the new project with this template.
Step 2: Preparing for the Template
There are many ways to achieve the goal. Over the years I tested a bunch of them, and most of them I reached the point of insanity. So I will keep it simple, clean and faster.
Let us begin:
$> mkdir template-preparing
$> cd .\template-preparing
$> mkdir SolutionTemplate
$> mkdir ProjectTemplate
$> cd .\SolutionTemplate
$> dotnet new sln -n SolutionTemplate
$> cd ..\ProjectTemplate
$> dotnet new web -n ProjectTemplate
And now we fill both projects with the files that we identified before.
Make sure after all changes are made both “solutions” are building without errors.
Step 3: Creating the Solution Template
Although creating a template is simple, it can also be very confusing in the beginning. The documentation helps, but sometimes I got the feeling that something was lost in translation.
By now we should have the following structure:
To create a template we need to:
- Create a folder named .template.config.
- Within that folder, create a file named template.json.
$> cd template-preparing
$> cd SolutionTemplate
$> mkdir .template.config
$> cd .template.config
$> echo "" > template.json
With and text editor open the template.json, and add the following:
{
"$schema": "http://json.schemastore.org/template",
"author": "Me",
"classifications": ["My", "Solution" ],
"identity": "My.Template.Solution",
"name": "Solution Template",
"shortName": "my-sln",
"tags": {
"language": "C#",
"type": "solution"
},
"sourceName": "SolutionTemplate",
"preferNameDirectory": true
}
In this configuration, there are four remarks to make (the rest of the file is self-explanatory):
- identity: This is the id of your template, and must be unique in the all world.
- shortName: This is the name of the template that you set on the command line.
- tags:type: This identifies the kind of template we are creating.
- sourceName: This is the name that will be found and replaced if one’s name is set, or the default name of the template.
And that is it.
Step 4: Creating the Project Template
To create a Project Template, the steps are the same with one or other change.
To create a template we need to:
- Create a folder named .template.config.
- Within that folder, create a file named template.json.
$> cd template-preparing
$> cd ProjectTemplate
$> mkdir .template.config
$> cd .template.config
$> echo "" > template.json
With and text editor open the template.json, and add the following:
{
"$schema": "http://json.schemastore.org/template",
"author": "Me",
"classifications": ["My", "Project" ],
"identity": "My.Template.Project",
"name": "Project Template",
"shortName": "my-web",
"tags": {
"language": "C#",
"type": "project"
},
"sourceName": "ProjectTemplate",
"preferNameDirectory": true
}
You will notice that the tags:type template is different, now is project, as we are creating a template project. Everything else follows the same structure and rules of the solution template.
Step 5: Packing the Templates
When we created the templates under the template-preparing folder, we created a “storage” for all our templates. If we want to add more templates, we follow the previous recipe and add them to this folder.
We are using SDK projects and with this, we can take advantage of the .NET features to build and pack our templates. So the only thing we need is to create a csproj file, add some metadata and generate a package (nuget package).
$> cd template-preparing
$> echo "" > dotnet.template.pack.csproj
And add the following metadata to the file.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<PackageType>Template</PackageType>
<PackageVersion>1.0.0</PackageVersion>
<PackageId>My.NET.Templates</PackageId>
<PackageIcon>icon.64.png</PackageIcon>
<Title>My NET Templates</Title>
<Authors>Me</Authors>
<PackageReadmeFile>readme.md</PackageReadmeFile>
<PackageTags>dotnet-new</PackageTags>
<TargetFrameworks>netstandard2.0;netstandard2.1;netcoreapp3.1;net6.0;net8.0;</TargetFrameworks>
<IncludeContentInPack>true</IncludeContentInPack>
<IncludeBuildOutput>false</IncludeBuildOutput>
<ContentTargetFolders>content</ContentTargetFolders>
<NoWarn>$(NoWarn);NU5128</NoWarn>
<NoDefaultExcludes>true</NoDefaultExcludes>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/masterzdran/dotnet-new-custom-templates</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<RepositoryBranch>main</RepositoryBranch>
</PropertyGroup>
<ItemGroup>
<Content
Include="SolutionTemplate\**\*;ProjectTemplate\**\*"
Exclude="SolutionTemplate\**\bin\**;SolutionTemplate\**\obj\**;SolutionTemplate\**\.vs\**;SolutionTemplate\**\.git\**;ProjectTemplate\**\bin\**;ProjectTemplate\**\obj\**;ProjectTemplate\**\.vs\**;ProjectTemplate\**\.git\**;" />
<Compile Remove="**\*" />
</ItemGroup>
<ItemGroup>
<Content Include="SolutionTemplate\readme.md">
<Pack>true</Pack>
<PackagePath>readme.md</PackagePath>
</Content>
</ItemGroup>
<ItemGroup>
<None Include="icon.64.png" Pack="true" PackagePath="\" />
</ItemGroup>
</Project>
As you can see, it is pretty straightforward. It is the NuGet metadata, with some exclusions, so we don’t add trash to our library.
For packing our template library we just execute:
$> dotnet pack --configuration Release --output ./dist/
And soon enough we have a NuGet package.
Step 6: Install/Uninstall the Templates
To install our template is as simple as:
$> dotnet new install <PATH_TO_NUPKG_FILE>
To create a new solution from our template:
$> dotnet new my-sln -n GreatestSolutionEver
To uninstall the template:
$> dotnet new uninstall <Template PackageId>
As simple as this. :)
Conclusion
Creating .NET New Templates is pretty simple, but can be scary without proper documentation. This approach works for me and saves me a lot of time when creating new solutions, and projects.
I hope that will save you time too.