Featured image of post Custom .Net project template

Custom .Net project template

Today about what I repeated most often, that is how to create a project and have all the libraries we like to use installed right away. In other words: how to make your own project template, which you can use with dotnet new command or in Visual Studio

Table of contents

If you are looking for specific examples or more advanced solutions, then I encourage you to check out this Microsoft’s repository: https://github.com/dotnet/dotnet-template-samples.

Two ways of creating templates

I distinguish two ways of creating templates:

  • Simple - where you put the files you want to use into a directory, and the dotnet new command simply copies the files to the new location and changes their content if necessary,

  • Nuget - where you can create your own template package and freely distribute it as a nuget. It also offers the possibility to put many templates into one package.

Simple

To create a simple template you need to:

  1. Create a directory for the stuff that will go into the template - let’s call it simplest - this will be our current working directory.

  2. Inside the working directory, create another one called .template.config, and inside the .template.config directory, create a file called template.json

    After completing these two steps, you should get this directory structure on your desktop

    Template file structure
    Desktop/
    └── simplest/
        ├── Installer.cs
        └── PersonComponent.Core.csproj
  3. To the template.json file, paste the contents:

    The file that configures our template
    {
        "$schema": "http://json.schemastore.org/template",
        "author": "Zaprogramuj To Raz!",
        "classifications": [ "Common", "Console" ],
        "identity": "ZTR.CoreTemplate.CSharp",
        "name": "Program It Once core template",
        "shortName": "ztr-core",
        "sourceName": "PersonComponent"
      }
  4. Add any file to the working directory, for example, a project file PersonComponent.Core.csproj and a C# class file named Installer.cs with the contents:

    Example of PersonComponent.Core.csproj project file
    <Project Sdk="Microsoft.NET.Sdk">
      <PropertyGroup>
        <TargetFramework>net5.0</TargetFramework>
      </PropertyGroup>
      <ItemGroup>
        <PackageReference Include="FluentValidation" Version="10.3.3" />
        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
      </ItemGroup>
    </Project>
    Example of C# class Installer.cs file
    using System;
    using Microsoft.Extensions.DependencyInjection;
    
    namespace PersonComponent.Core
    {
        public static class Installer
        {
            public static void InstallPersonComponentCore(this IServiceCollection services)
            {
            }
        }
    }
  5. Now go in the console to the parent directory for the working directory and invoke the `dotnet new -i .\simplest' command there. You should see an output at the end:

    Template name                  Short Name      Language   Tags
    -----------------------------  --------------  ---------  --------------
    Program It Once core template  ztr-core                   Common/Console

    Note how the shortName, name and classifications values from the template.json file correlate with what is displayed on the screen. I’ll go into more detail about this later.

  6. Now the best part Create a new directory named MyProject somewhere else and navigate to it in the console, where call the dotnet new ztr-core command.

    You will see that the contents of the template have been copied into the directory, but with the names changed!

    Final file structure
    Desktop/
    └── MyProject/
        ├── Installer.cs
        └── MyProject.Core.csproj

    Additionally, the content of the Installer.cs file has been changed too! How did this happen? Well, the line "sourceName": "PersonComponent" from the configuration file, which indicates which string to replace with the project name that we provide when using the template in the dotnet new ztr-core -n "MyProject" command. But we didn’t specify the -n MyProject ending - it was taken from the directory name by default.

    The template engine will automatically search all files it contains and replace the string PersonComponent with the project name. Even if it occurs in the file name. More modifications are possible, but they go beyond this article, and you can find examples of their use in the repository given at the beginning.

  7. The disadvantage of this solution is that it relies on the template source directory all the time. Add a new dumb.txt file to the simplest directory even without the contents, and you will see that when you call the dotnet new ztr-core method again, this one will also be copied.

  8. Now delete or rename the source directory (simplest) and try to create a new project again (dotnet new ztr-core)

    PS> dotnet new ztr-core
    The specified template content cannot be located because the template cache may be corrupted. Try running the command "dotnet Microsoft.TemplateEngine.Cli.CommandParsing.BaseCommandInput--debug:reinit" to resolve the problem.

    And everything stops working. That’s why this approach is good for the beginning, when we learn the engines and possibly for some specific local templates.

    Let me emphasize again what was written above. A simple template are any files and folders in the source directory! Moving the source directory will cause it to stop working.
  9. To uninstall a template use the command dotnet new -u "full path to template".

    What if you can’t remember where your template is? Use the dotnet new -u command without anything extra. You will see a list of templates that you can uninstall with the appropriate command:

    Help view how to uninstall templates
    PS> dotnet new -u
        C:\Users\md\Desktop\simplest
          Templates:
             Program It Once core template (ztr-core)
          Uninstall command:
       dotnet new --uninstall C:\Users\md\Desktop\simplest

    Then just copy the last line of the entry for the template you want to remove and voilà - you have order back.

Nuget

The Nuget template has two major advantages:

  • May be packaged as a nuget,

  • May be a template bundle, that is, it may contain many single, completely different templates provided in one file.

    1. To create such a package, create a directory tree with files as below:

      Structure of template directories to be packaged as nuget
      MyPackageTemplates/
      ├── ZtrDotnetTemplates.csproj (1)
      └── templates/
          ├── ConsoleTemplate/
          │   └── .template.config/
          │       └── template.json (2)
          └── CoreTemplate/
              └── .template.config/
              │   └── template.json (2)
              ├── Installer.cs (3)
              └── PersonComponent.Core.csproj (3)
      1 is the project file that is our template package,
      2 are files describing a given template. There must be one such file for each template,
      3 files that will be attached to our templates.

      I leave the ConsoleTemplate directory empty (except for the configuration file) to limit the size of this example.

    2. Complete the project ZtrDotnetTemplates.csproj file :

      Contents of the ZtrDotnetTemplates.csproj file
      <Project Sdk="Microsoft.NET.Sdk">
        <PropertyGroup>
          <PackageType>Template</PackageType> (1)
      
          <PackageVersion>1.0</PackageVersion> (2)
          <PackageId>ZTR.Utilities.Templates</PackageId>
          <Title>Zaprogramuj to raz! Core template</Title>
          <Authors>Zaprogramuj to raz!</Authors>
          <Description>Template for creating core projects</Description>
          <PackageTags>dotnet-new;ztr;templates</PackageTags>
          
          <IncludeContentInPack>true</IncludeContentInPack>
          <IncludeBuildOutput>false</IncludeBuildOutput>
          <ContentTargetFolders>content</ContentTargetFolders>
      
          <TargetFramework>net5.0</TargetFramework> (3)
        </PropertyGroup>
      
        <ItemGroup>
          <Content Include="templates\**\*" Exclude="templates\**\bin\**;templates\**\obj\**" /> (4)
          <Compile Remove="**\*" />
        </ItemGroup>
      </Project>
      1 Note the specific project type Template. It is necessary for the creation of the package.
      2 This is where the standard fields for describing a Nuget package begin.
      3 TargetFramework in this case specifies the .Net version used to build the nuget package with the templates.
      4 Note that all templates located in the templates directory will be included, except for the files located in the bin and obj directories.
    3. Complete the console template configuration file templates/console/.template.config/template.json

      Contents of file console/.template.config/template.json
      {
          "$schema": "http://json.schemastore.org/template",
          "author": "Zaprogramuj To Raz!",
          "classifications": [ "Common", "Console" ], 
          "identity": "ZTR.ConsoleTemplate.CSharp",
          "name": "Program It Once Console template", 
          "shortName": "ztr-console", 
          "tags": {
            "language": "C#", 
            "type": "project"
          }
        }

      It is not different from what we saw in the previous example, except that here I added information about the language used in the template.

    4. Complete the templates/core/.template.config/template.json, templates/core/Installer.cs and templates/core/PersonComponent.Core.csproj files.

      These files are identical to the same ones listed above, so I will just paste them here without further discussion.

      File templates/core/.template.config/template.json
      {
          "$schema": "http://json.schemastore.org/template",
          "author": "Zaprogramuj To Raz!",
          "classifications": [ "Common",  "Console", "Library" ],
          "identity": "ZTR.CoreTemplate.CSharp",
          "name": "Program It Once core template",
          "shortName": "ztr-core",
          "tags": {
            "language": "C#",
            "type": "project"
          },
          "sourceName": "PersonComponent"
        }
      File templates/core/PersonComponent.Core.csproj
      <Project Sdk="Microsoft.NET.Sdk">
      
        <PropertyGroup>
          <TargetFramework>net5.0</TargetFramework>
        </PropertyGroup>
      
        <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
          <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
          <WarningsAsErrors />
        </PropertyGroup>
      
        <ItemGroup>
          <PackageReference Include="FluentValidation" Version="10.3.3" />
          <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.2" />
        </ItemGroup>
      
      </Project>
      File templates/core/Installer.cs
      using System;
      using System.Collections.Generic;
      using System.ComponentModel.Design;
      using System.Linq;
      using System.Text;
      using System.Threading.Tasks;
      using Microsoft.Extensions.DependencyInjection;
      
      namespace PersonComponent.Core
      {
          public static class Installer
          {
              public static void InstallPersonComponentCore(this IServiceCollection services)
              {
                  // Here add your services from core
                  // services.AddTransient<IPersonService, PersonService>();
                  // services.AddTransient<IValidator<PersonModel>, PersonValidator>();
              }
          }
      }
    5. Build and install nuget!

      In the MyPackageTemplates directory, run the dotnet pack command. Then go to the ./bin/debug/ folder, and there you will see a file named ZTR.Utilities.Templates.1.0.0.nupkg. To install this package use the dotnet new -i ZTR.Utilities.Templates.1.0.0.nupkg command.

      Package building and installation process
      ~\MyPackageTemplates> dotnet pack
      ...
      Pomyślnie utworzono pakiet "~\MyPackageTemplates\bin\Debug\ZTR.Utilities.Templates.1.0.0.nupkg".
      ~\MyPackageTemplates> cd .\bin\Debug\
      ~\MyPackageTemplates> dotnet new -i .\ZTR.Utilities.Templates.1.0.0.nupkg
      Success: ZTR.Utilities.Templates::1.0.0 installed the following templates:
      Template name                     Short name      Lanugage  Tags
      --------------------------------- --------------- --------  ----------------------
      Program It Once Console template  ztr-console     [C#]      Common/Console
      Program It Once core template     ztr-core        [C#]      Common/Console/Library
    6. Use it!

      Installed packages are used the same way (e.g. with dotnet new ztr-console command), except that now changing the source directory doesn’t break our template! If we want to uninstall a package, we do it with dotnet new -u .\ZTR.Utilities.Templates.1.0.0.nupkg command.

Summary

  • I think that a custom project template can be a very useful tool, not only for .Net projects, but also for other solutions, not related to any programming language.

  • Remember, that this article is just a seed, and the whole template engine has still many possibilities, which you can discover in the links below - there are, among others, repository addresses related to this project.

  • Note that the whole thing will be even more fun if you link your own templates to CI/CD (e.g. on Github) which will give you access to your favorite patterns almost everywhere!

References

Translated with www.DeepL.com/Translator (free version)References

comments powered by Disqus
Please keep in mind that the blog is in preview form at this point and may contain many errors (but not merit errors!). Nevertheless, I hope you enjoy the blog! Many illustrations appeared on the blog thanks to unsplash.com!
Built with Hugo
Theme Stack designed by Jimmy