This article is the final installment in a comprehensive series, offering a deep dive into the practical creation of custom .NET project templates. Moving beyond theoretical discussions, this guide provides a hands-on journey, empowering you to build robust, reusable templates that significantly enhance your development workflow. By mastering custom templating, you can eliminate repetitive setup tasks, allowing you to focus immediately on application-specific logic and accelerate project initiation.
Unleashing Development Efficiency with Custom Templates
Imagine a scenario where every new project or module begins with a perfectly structured, pre-configured codebase, tailored to your exact specifications. This article transforms that vision into reality. We will explore how to design and implement custom project and item templates that reflect your team\’s best practices, ensuring consistency and efficiency across all your endeavors. This detailed technical walkthrough will equip you with the knowledge to create highly adaptable templates, suitable for internal use or sharing within the broader development community.
Our journey will cover:
- Defining a clear project structure and requirements for an effective custom template.
- Deconstructing the essential components and logic required for template construction.
- Building a complete custom template from the ground up, starting with an empty directory.
Revisiting Foundations: The Journey So Far
This article builds upon the foundational concepts introduced in the preceding parts of this series. While prior review is beneficial for a complete understanding of the underlying mechanics and design philosophies, it\’s not strictly mandatory. You can still follow this guide step-by-step and successfully create custom templates.
- Part 1: Microsoft’s Implementation: Explored Microsoft\’s internal strategies for template creation and the mechanics at play beneath the surface.
- Part 2: Exploring Every Hidden Trail: Delved into identifying and understanding the functionality of each component within a template project.
For those eager to grasp the deeper architectural choices and operational intricacies, revisiting the earlier parts is highly recommended. However, if your primary goal is rapid template construction, this guide provides all the necessary practical steps.
Blueprinting Our Custom Template
To ensure a realistic and comprehensive learning experience, our custom template will be designed to meet a set of specific requirements, mimicking challenges often encountered in real-world projects.
Core Solution Folder Structure:
src/
: Dedicated to source code.tests/
: Contains various test projects.samples/
: Houses lightweight manual testing applications.- A basic
README
template, also available as a standalone item template.
src/
Folder Details:
This folder will hold the primary application logic. Our example will feature two projects: Company.App
(the main application, e.g., a Web API) and Company.App.Engine
(an optional, separate module referenced by the main app). While this structure is simplified, it demonstrates common organizational patterns. The names Company
and App
serve as placeholders, intended to be replaced with user-defined project names upon template instantiation. The src/
folder will also include static front-end assets (HTML, CSS, JS, images) for a unique landing page experience.
tests/
Folder Details:
This section of the template is designed for flexibility. By default, it will include unit tests, but developers will have the option to exclude the entire tests/
folder if not needed. Furthermore, while integration tests will be available, they will not be included by default, requiring explicit user selection. This allows for tailored test setups based on project requirements.
samples/
Folder Details:
The samples/
folder is optional and typically used for developing libraries or APIs, providing a simple console application to manually test functionality. This can be particularly useful for quickly verifying endpoint behaviors in a Web API project.
From Concepts to Code: Building the Template
Let’s translate these requirements into a functional template. We’ll use command-line tools and a code editor (e.g., PowerShell, Windows Terminal, VS Code) to construct our project.
Initial Setup and Repository Configuration
- Create the Root Folder: Begin by creating a new directory for your templates:
powershell
mkdir dotnet-templates
cd .\dotnet-templates\ - Define the NuGet Package: Create a
DotnetTemplates.csproj
file in the root. This XML file specifies how your templates will be packaged into a NuGet package for distribution. Key elements include<PackageType>Template</PackageType>
,<PackageId>
,<PackageVersion>
, and<IncludeContentInPack>true</IncludeContentInPack>
to ensure your template files are included.
Replace “Giorgi” with your own name or company name in the<Authors>
,<Title>
, and<PackageId>
fields. - Initialize Git: Prepare your repository for version control:
powershell
dotnet new gitignore
git init
This adds a standard.gitignore
file and initializes a new Git repository.
Establishing the Template Structure
- Create the Main Template Directory: Create a
templates
folder, and inside it, aweb-app-template
folder. Withinweb-app-template
, create.template.config
and thentemplate.json
. - Add Configuration Files: Navigate into
web-app-template
and add standard configuration files:
powershell
dotnet new gitignore
dotnet new editorconfig - Establish Core Directories: Create the primary project sub-folders:
powershell
mkdir src, tests, samples
Generating Core Projects
We\’ll leverage existing .NET templates and then modify them.
- Solution File: Create the solution file, using a unique placeholder for the project name:
powershell
dotnet new sln -n CUSTOM_TEMPLATE_PLACEHOLDER
Using a distinct placeholder likeCUSTOM_TEMPLATE_PLACEHOLDER
prevents accidental replacement of common words in your code. - Main Web API Project: Generate the primary application:
powershell
dotnet new webapi --use-controllers -o src/CUSTOM_TEMPLATE_PLACEHOLDER
After generation, remove OpenAPI-related lines fromProgram.cs
andCUSTOM_TEMPLATE_PLACEHOLDER.csproj
to simplify the template. - Optional Engine Module: Create a class library for an additional module:
powershell
dotnet new classlib -o src/CUSTOM_TEMPLATE_PLACEHOLDER.Engine
Constructing Test Projects
- Create Test Sub-folders:
powershell
mkdir tests/UnitTests, tests/IntegrationTests - Generate Unit Tests (xUnit):
powershell
dotnet new xunit -o tests/UnitTests/CUSTOM_TEMPLATE_PLACEHOLDER.UnitTests
dotnet new xunit -o tests/UnitTests/CUSTOM_TEMPLATE_PLACEHOLDER.Engine.UnitTests - Generate Integration Tests (xUnit):
powershell
dotnet new xunit -o tests/IntegrationTests/CUSTOM_TEMPLATE_PLACEHOLDER.IntegrationTests
dotnet new xunit -o tests/IntegrationTests/CUSTOM_TEMPLATE_PLACEHOLDER.Engine.IntegrationTests
Adding the Sample Project
- Generate Console Application:
powershell
dotnet new console -o samples/CUSTOM_TEMPLATE_PLACEHOLDER.Sample
Integrating Projects into the Solution
All generated projects must be added to the .sln
file. From the web-app-template
directory, run a series of dotnet sln add
commands for each project:
dotnet sln add src/CUSTOM_TEMPLATE_PLACEHOLDER/CUSTOM_TEMPLATE_PLACEHOLDER.csproj
dotnet sln add .\src\CUSTOM_TEMPLATE_PLACEHOLDER.Engine\CUSTOM_TEMPLATE_PLACEHOLDER.Engine.csproj
dotnet sln add .\tests\UnitTests\CUSTOM_TEMPLATE_PLACEHOLDER.UnitTests\CUSTOM_TEMPLATE_PLACEHOLDER.UnitTests.csproj
dotnet sln add .\tests\UnitTests\CUSTOM_TEMPLATE_PLACEHOLDER.Engine.UnitTests\CUSTOM_TEMPLATE_PLACEHOLDER.Engine.UnitTests.csproj
dotnet sln add .\tests\IntegrationTests\CUSTOM_TEMPLATE_PLACEHOLDER.IntegrationTests\CUSTOM_TEMPLATE_PLACEHOLDER.IntegrationTests.csproj
dotnet sln add .\tests\IntegrationTests\CUSTOM_TEMPLATE_PLACEHOLDER.Engine.IntegrationTests\CUSTOM_TEMPLATE_PLACEHOLDER.Engine.IntegrationTests.csproj
dotnet sln add .\samples\CUSTOM_TEMPLATE_PLACEHOLDER.Sample\CUSTOM_TEMPLATE_PLACEHOLDER.Sample.csproj
Implementing the Front-End Landing Page
Create a wwwroot
folder within src/CUSTOM_TEMPLATE_PLACEHOLDER/
. Inside wwwroot
, add index.html
, css/style.css
, js/script.js
, and assets/icon.png
. These files provide a static landing page, which in our example, includes a fun F1 pitstop game. You can find these assets in the provided GitHub repository.
Configuring Template Behavior with template.json
The template.json
file is central to defining how your template functions, including its options and file transformations.
Start with the basic metadata:
{
"$schema": "http://json.schemastore.org/template",
"author": "Your Name",
"classifications": [ "webapi" ],
"identity": "YourName.Dotnet.Template.Webapi",
"name": "Your Custom Web API template",
"description": "Template for generating custom Web API projects",
"shortName": "yourtemplateapi",
"tags": {
"language": "C#",
"type": "project"
},
"preferNameDirectory": true,
"sourceName": "CUSTOM_TEMPLATE_PLACEHOLDER"
}
Making samples/
Optional
Add a symbols
object to template.json
to define a parameter. This parameter includeSamples
(boolean, default false
) will control the inclusion of the samples
folder.
"symbols": {
"includeSamples": {
"type": "parameter",
"datatype": "bool",
"description": "Include a sample project for manual testing of the main application?",
"defaultValue": "false"
}
}
Next, add a sources
object to configure file exclusion. The exclude
pattern will physically remove the samples
folder if includeSamples
is false
.
"sources": [
{
"source": "./",
"target": "./",
"modifiers": [
{
"condition": "(!includeSamples)",
"exclude": [ "samples/**" ]
}
]
}
]
Finally, wrap the samples
project reference in your .sln
file with an #if(includeSamples)...#endif
conditional block so it\’s only included when the symbol is true.
Dynamic Test Inclusion
Implement two boolean parameters for tests: includeIntegrationTests
(default false
) and includeUnitTests
(default true
).
"includeIntegrationTests": {
"type": "parameter",
"datatype": "bool",
"description": "Include an integration tests project?",
"defaultValue": "false"
},
"includeUnitTests": {
"type": "parameter",
"datatype": "bool",
"description": "Exclude the unit tests project?",
"defaultValue": "true"
}
Add corresponding source modifiers to exclude tests/IntegrationTests/**
if includeIntegrationTests
is false, and tests/UnitTests/**
if includeUnitTests
is false. Crucially, add a modifier to exclude the entire tests/**
folder if (!includeUnitTests) && (!includeIntegrationTests)
.
Update your .sln
file with #if
blocks for each test project and the entire tests
folder to reflect these conditions.
Optional Submodule Inclusion
Add an includeSubmodule
boolean parameter (default false
).
"includeSubmodule": {
"type": "parameter",
"datatype": "bool",
"description": "Include a sub-module?",
"defaultValue": "false"
}
Add a source modifier to exclude src/CUSTOM_TEMPLATE_PLACEHOLDER.Engine/**
and its associated unit/integration test projects (tests/CUSTOM_TEMPLATE_PLACEHOLDER.Engine.UnitTests/**
, tests/CUSTOM_TEMPLATE_PLACEHOLDER.Engine.IntegrationTests/**
) if includeSubmodule
is false.
Remember to wrap the submodule and its related test projects in #if(includeSubmodule)...#endif
blocks within your .sln
file.
Configuring the Web Project (Game Mode)
Modify Program.cs
in src/CUSTOM_TEMPLATE_PLACEHOLDER
to include app.UseStaticFiles()
and app.UseFileServer(new FileServerOptions { EnableDefaultFiles = true });
for static file support. Wrap these lines in a conditional #if(useGameMode)...#endif
block.
Add a useGameMode
boolean parameter (default true
).
"useGameMode": {
"type": "parameter",
"datatype": "bool",
"description": "The game mode will allow you to play an awesome F1 pitstop game",
"defaultValue": "true"
}
Add a source modifier to exclude src/CUSTOM_TEMPLATE_PLACEHOLDER/wwwroot/**
if useGameMode
is false.
Configurable Framework Version
Introduce a framework
parameter of type choice
with net8.0
and net9.0
as options, defaulting to net9.0
. Use the replaces
property to target a placeholder.
"framework": {
"type": "parameter",
"description": "The target framework of the project",
"datatype": "choice",
"choices": [
{ "choice": "net8.0" },
{ "choice": "net9.0" }
],
"defaultValue": "net9.0",
"replaces": "DOTNET_TARGET_FRAMEWORK"
}
In every .csproj
file, replace <TargetFramework>net9.0</TargetFramework>
with <TargetFramework>DOTNET_TARGET_FRAMEWORK</TargetFramework>
.
Testing Your Configured Template
- Install Locally: Navigate to your
web-app-template
folder and install:
powershell
dotnet new install . - List Templates: Verify your template is listed:
powershell
dotnet new list - View Help: Examine the available options for your template:
powershell
dotnet new yourtemplateapi --help - Generate a Project with Options: Test the flexibility by generating a project with specific configurations:
powershell
dotnet new yourtemplateapi --includeSubmodule --includeIntegrationTests --framework net8.0 -o MyCoolApp
Ensure you run this command outside your template project directory. Inspect the generated project structure and.csproj
files to confirm all options are applied correctly.
Enhancing Documentation with a README Item Template
Rather than manually creating README.md
files, we\’ll create a reusable item template.
- Create README Item Template: Inside your
templates
folder, createreadme-template
, with a.template.config
folder andtemplate.json
.
json
{
"$schema": "http://json.schemastore.org/template",
"author": "Your Name",
"classifications": [ "README", "Documentation" ],
"identity": "YourName.Template.Readme",
"name": "README file",
"shortName": "fullsendreadme",
"tags": {
"type": "item"
}
} - Create
README.md
Content: Inreadme-template
, createREADME.md
with placeholders.# PLAIN_TEXT_NAME A brief description ## Technologies **Framework:** DOTNET_TEMPLATE_VERSION_NUMBER ## Repository **URL:** https://github.com/[user_name]/REPO_NAME_KEBAB_CASE ## Documentation ## Features ## Installation ## Dependencies ## Other
- Generate Symbols for Readability: In your main web app template\’s
template.json
, add generated symbols to transform the project name and framework version into more readable formats.plainTextName
: Uses regex to convertCadillacF1.BrakeTemperatureMonitor
toCadillac F1 - Brake Temperature Monitor
.dotNetVersionNumber
: Convertsnet9.0
to.NET 9.0
.- Add these within the
symbols
object, usingreplaces
to target theREADME.md
placeholders. - Derived Symbol for Kebab-Case: Define a
forms
object for transformations and akebabCasedName
derived symbol to convert the project name to kebab-case for the GitHub URL.
Example of generated and derived symbols structure (added to your existing
symbols
object inweb-app-template/template.json
):"plainTextName": { "type": "generated", "generator": "regex", "datatype": "string", "replaces": "PLAIN_TEXT_NAME", "parameters": { "source": "name", "steps": [ { "regex": "(?<=[a-z0-9])(?=[A-Z])", "replacement": " " }, { "regex": "\\.", "replacement": " - " } ] } }, "dotNetVersionNumber": { "type": "generated", "generator": "regex", "datatype": "string", "replaces": "DOTNET_TEMPLATE_VERSION_NUMBER", "parameters": { "source": "framework", "steps": [ { "regex": "^net(\\d+)\\.(\\d+)$", "replacement": ".NET $1.$2" } ] } }, "forms": { "kebabCase": { "identifier": "kebabCase" } }, "kebabCasedName": { "type": "derived", "valueSource": "name", "replaces": "REPO_NAME_KEBAB_CASE", "valueTransform": "kebabCase" }
- Install README Template: Navigate to
readme-template
and install it:dotnet new install .
- Generate a README: Navigate into a generated project and create a README:
powershell
dotnet new fullsendreadme
This will generate aREADME.md
populated with the project name, framework, and kebab-cased repository name automatically.
Your custom template is now fully functional and highly configurable!
Packaging Your Templates for NuGet Distribution
The final step is to make your templates accessible to others by packaging them as a NuGet package.
- Register on NuGet.org: Ensure you have an account on `https://www.nuget.org/` and it is verified.
- Pack the Template: Navigate to the root folder of your template project (where
DotnetTemplates.csproj
is located) and run:
powershell
dotnet pack -c Release
This command will create a.nupkg
file (e.g.,YourName.Dotnet.Templates.1.0.0.nupkg
) in thebin/Release/
directory. - Push to NuGet: Publish your package:
powershell
dotnet nuget push bin/Release/YourName.Dotnet.Templates.1.0.0.nupkg --api-key YOUR_API_KEY --source https://api.nuget.org/v3/index.json
ReplaceYOUR_API_KEY
with your actual NuGet API key. Once pushed, others can install your templates using:
powershell
dotnet new install YourName.Dotnet.Templates::1.0.0
Conclusion
This comprehensive guide has equipped you with the skills to design, build, and deploy highly customizable .NET project and item templates. By implementing dynamic options, conditional file generation, and intelligent text transformations, you can drastically streamline your development workflow, enforce consistent project structures, and boost team productivity. The possibilities for customization are vast, and this framework provides a solid foundation upon which you can continue to expand and refine your templating solutions. We encourage you to explore further, contribute to the open-source repository, and share your innovations with the community. Stay tuned for future announcements!