If I run into tasks that I must repeat countless times, I try to automate them as much as possible. Since we always add the same dependencies to our test projects, we thought it would be a great idea to create a project template that has all the necessary settings already in place. Let us figure out how little effort it takes to create our own project templates for dotnet, Visual Studio and Rider.
The pain of the old days…
If you tried creating a Visual Studio project template in the past, you had a painful experience. You started with an existing template, added all your customisations and additional features, and then you had to break it with various template-specific keywords. You could package that into a project template, but you could no longer compile it. That made updating a template a painful task with a lot of errors and multiple rounds of bugfixes.
…is now gone
Today you still start with an existing template and make your customisations. But now you only add 3 additional files and keep your project otherwise free of any template-specific modifications. That way your template project always compiles and when you need to update it, you just do it. You can check if everything works by compiling that project (and run it if it is an executable).
With all the extra effort gone, you can create templates for all the project types you need often. Instead of adding the same dependencies all over again, you create a template and get rid of those repetitive steps. Even better, the project templates not only work in the command line and in Visual Studio, but also in JetBrains Rider.
Create a new project
For this blog post we start with the built-in template for a C# class library in .Net 6 to create our TestingTemplate project. Next, we add our always used dependencies like NUnit, Moq and FluentAssertion to the project. Visual Studio will fetch the necessary dependencies and writes down our setup to the TestingTemplate.csproj file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <Nullable>enable</Nullable> </PropertyGroup> <ItemGroup> <PackageReference Include="FluentAssertions" Version="6.11.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.6.3" /> <PackageReference Include="Moq" Version="4.18.4" /> <PackageReference Include="NUnit" Version="3.13.3" /> <PackageReference Include="NUnit3TestAdapter" Version="4.5.0" /> </ItemGroup> </Project> |
If we compile the project, NuGet downloads all the dependencies and their transient dependencies and puts them in the bin folder.
Check if everything works
While it this step is optional, it may be a good idea to turn Class1.cs into a basic unit test to check if everything works. The content of Class1.cs can look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using FluentAssertions; using NUnit.Framework; namespace TestingTemplate { public class Class1 { [Test] public void A() { "Hello".Should().BeEquivalentTo("hello"); } } } |
We can recompile our project and run the unit tests. Everything should still work, and our minimalistic test should pass.
Turn the project into a template
We now add a folder .template.config inside our project folder:
1 |
mkdir .template.config |
Inside the newly created folder, we create the file template.json with something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
{ "$schema": "http://json.schemastore.org/template", "author": "Johnny Graber", "classifications": ["Library"], "name": "JG Test Project Template", "description": "JG Test Project Template with NUnit and FluentAssertions", "shortName": "jgtest", "defaultName": "JG.Tests", "identity": "JG.Tests", "tags": { "language": "C#", "type": "project", }, "sourceName": "TestingTemplate", "preferNameDirectory": true, "symbols": { "Framework": { "type": "parameter", "description": "The target framework for the project.", "datatype": "choice", "choices": [ { "choice": "net6.0", "description": "Target net6.0" }, { "choice": "net7.0", "description": "Target net7.0" } ], "replaces": "net6.0", "defaultValue": "net6.0" } } } |
You can find an explanation for the various properties of this JSON file and the options you have to customise it here in the official documentation.
Next, we create the file ide.host.json (also inside the .template.config folder) and add this content:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
{ "$schema": "http://json.schemastore.org/vs-2017.3.host", "icon": "logo.png", "symbolInfo": [ { "id": "AuthorName", "name": { "text": "Johnny Graber" }, "isVisible": "true" }, { "id": "Description", "name": { "text": "UnitTest Project Tempalte with NUnit & FluentAssertions" }, "isVisible": "true" } ] } |
After that, we can add our company or project logo as logo.png in the .template.config folder:
As the last step to prepare our template, we create a TestingTemplate.nuspec next to our *.sln file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<?xml version="1.0" encoding="utf-8"?> <package xmlns="http://schemas.microsoft.com/packaging/2012/06/nuspec.xsd"> <metadata> <icon>Content\.template.config\logo.png</icon> <id>JG.Tests</id> <version>0.6.0</version> <description> JG Test Project Template with NUnit AND FluentAssertions </description> <authors>Johnny Graber</authors> <packageTypes> <packageType name="Template" /> </packageTypes> </metadata> <files> <file src="TestingTemplate\**\*.*" exclude="TestingTemplate\**\bin\**\*.*;TestingTemplate\**\obj\**\*.*" target="Content" /> </files> </package> |
The two *.json files define what is inside our template and create a nice description for the “Add new Project” dialog, while the *.nuspec has all the details to create an installable NuGet package.
Create a NuGet package
We can turn our project into a NuGet template package with this command:
1 |
C:\Tools\nuget.exe pack .\TestingTemplate.nuspec |
(Make sure that you have a NuGet.exe on your computer, otherwise you can find it on nuget.org/downloads)
We now should find our *.nuget file next to the *.nuspec:
Install the new template
We can install our template with this command:
1 2 3 4 5 6 |
dotnet new install .\JG.Tests.0.6.0.nupkg Success: JG.Tests::0.6.0 installed the following templates: Template Name Short Name Language Tags ------------------------ ---------- -------- ------- JG Test Project Template jgtest [C#] Library |
Use the new template
From now on we can use our template in Visual Studio. Open the “Add new Project” dialog and search for our template:
If we select that template, we get the usual dialog to add a name and select the .Net version:
As soon as Visual Studio finishes its project creation, we should have a new project in our solution with all the dependencies already configured:
If you prefer Raider, you can follow along the steps above and after installing the NuGet package, you find the template in Raider as well:
If you want to use the template from the command line, you can use dotnet new with the value for shortName we set for our template in template.json:
1 |
dotnet new jgtest -n Demo.Tests |
Update the template
We can change our template project and add the new features. When we are done, we increment the NuGet version and create a new NuGet package.
If you installed the NuGet package from the file system, you best deinstall the old template before you install the new one:
1 2 3 |
dotnet new uninstall .\JG.Tests.0.6.0.nupkg dotnet new install .\JG.Tests.0.7.0.nupkg |
Share with your team
If you can share the template with the whole world, you can add it to NuGet.org and work with the commands as we did above.
Should you prefer a private NuGet feed (like in Azure DevOps), you need to go through a few extra steps to get the authentication credentials into NuGet. For this task we use the Azure Artifacts Credential Provider with great success.
Conclusion
Creating your own templates has become much easier than in the days of the .Net full framework. Now it is a straightforward task with only three additional files to turn any project into a template.
We use this feature now heavily and created templates for all our project types. It was a great time saver for our big migration to .Net 6. Even when you do not plan to recreate all your projects, it will save you a lot of time when you no longer need to add the same dependencies over and over again.
1 thought on “Great Help & Little Effort: Project Templates for Visual Studio”