.NET Core / 6 Custom Templates

On one of my recent project around serverless, we were creating lots of microservices and each of that service was its own Visual Studio Solution. Since there were lots of development teams involved in building these services, we wanted to standardize on a solution structure which could be easily created / adopted across these teams.

We were looking to automate the scaffolding of these solutions, so we could quickly and easily create consistent solutions and this is where we started looking at Custom .NET Templates.

If you read through the previous link, you can see that the custom templating engine has been significantly evolved since .NET core 2 and its now relatively very easy to create custom templates.

NOTE: There is lots to the custom templating engine, however in this post we will only look at some of the features relevant to our use case.

Now, our solutions were following a very consistent structure, a close example of which is shown below:

Microservice.sln
│   .editorconfig
|
└───Documentation
    ├───Wiki
    |
└───Source
    ├───Service.csproj  
        │───Startup.cs
        |
    |
    └───Shared
        ├───Domain.csproj
        |
 |
 └───Testing
    ├───IntegrationTest.csproj 
        │───Program.cs
        |  
    ├───UnitTest.csproj 
  • Documentation folder contained all the relevant documentation related to that Microservice.

  • Source folder as the name indicates, contained the actual Service as the Azure Function project and Shared folder, which housed a class library for encapsulating domain logic.

  • Testing folder contained IntegrationTest console project and UnitTest, as a standard MSTest project.

These microservice solutions also followed a naming convention such as {Company}.{Application}.{Component}.name.csproj and therefore when scaffolded, had to have the ability to set those.

OK, so in order to get started, my first step was to create this solution structure and templatize the variables which could be substituted when the final solution was created. This was all accomplished via the template.json which is included under the .template.config folder of the source code.

I have shown that file below for our review and will go over some of the relevant items. If you want to understand all of the (Package, Content, Source) definitions / options, this reference does a pretty good job and I would encourage you to read that.

{
  "$schema": "http://json.schemastore.org/template",
  "author": "Adi Thakker",
  "classifications": [ "Solution", "Service" ],
  "identity": "Adi.FunctionAppTemplate.CSharp",
  "name": "Adi Thakker Custom function app template",
  "shortName": "adifunctionappservice",
  "tags": {
    "language": "C#",
    "type": "solution"
  },
  "symbols": {
    "company": {
      "type": "parameter",
      "isRequired": true,
      "datatype": "text",
      "replaces": "Company",
      "defaultValue": "Your Company name",
      "fileRename": "Company"
    },
    "application": {
      "type": "parameter",
      "isRequired": true,
      "datatype": "text",
      "replaces": "Application",
      "defaultValue": "Your Application name",
      "fileRename": "Application"
    },
    "component": {
      "type": "parameter",
      "isRequired": true,
      "datatype": "text",
      "replaces": "Component",
      "defaultValue": "Your Component name",
      "fileRename": "Component"
    },
    "includeIntegrationTest": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true"
    }
  },
  "sources": [
    {
      "modifiers": [
        {
          "condition": "(!includeIntegrationTest)",
          "exclude": [ "Testing/Company.Application.Component.IntegrationTest/**" ]
        }
      ]
    }
  ],
  "preferNameDirectory": true
}

OK, so some of the key elements in this file include:

  • classifications, this is used to classify our template type when creating a solution of that type.

  • shortname, this value is used with dotnet new command when generating solution of that type.

  • symbols are used to define and substitute the variables for solution naming convention.

Also included is includeIntegrationTest symbol for conditionally generating the integrationtest.csproj since not every solution scaffolded, included that and the sources.modifiers section of the file verifies for that flag to include/exclude that in the final generated solution.

Alright… with all this in place, the next step was to install the template locally via:

dotnet new --install <PATH_TO_FOLDER_CONTAINING_TEMPLATE.CONFIG_FOLDER>

which displayed the following output:

image

I was also able to verify its successful installation via:

dotnet new --list

As, you can see below the very first template displayed is our custom one with correct properties:

image

Finally, I created a folder for my test microservice and was ready to generate my custom solution via dotnet new command as shown below:

image

NOTE You can also see the validation kicking in for the required parameters when these tokens were omitted.

The final generated scaffolding is shown below:

image

As a next step we also exported this template as a nuget library by updating its csproj file which is also shown below:

<Project Sdk="Microsoft.NET.Sdk">
  
  <PropertyGroup>
    <PackageType>Template</PackageType>
    <PackageVersion>1.0.0</PackageVersion>
    <PackageId>My Custom Azure Function Template</PackageId>
    <Title>Azure Function Template</Title>
    <Authors>Adi Thakker</Authors>
    <Description>Solution Template that scaffolds a custom Azure Function Microservice Projects</Description>
    <TargetFramework>net6.0</TargetFramework>
    <IncludeContentInPack>true</IncludeContentInPack>
    <IncludeBuildOutput>false</IncludeBuildOutput>
    <ContentTargetFolders>content</ContentTargetFolders>
    <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
    <NoDefaultExcludes>true</NoDefaultExcludes>
    <PackageOutputPath>./nupkg</PackageOutputPath>
    <PackageType>function-app;microservice;service</PackageType>
  </PropertyGroup>

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

I would encourage you to read this reference which explains all the above csproj elements for nuget package in detail.

Alright…so, with this automation in place, we really expedited consistent microservices scaffolding across different teams.

The entire code is available here, if you want to explore it further.

Let me know what your thoughts are!!!

Related Posts