Stop Recreating .NET Solutions From Scratch

Use Microsoft Template Engine Before Copy-Paste Becomes Your Architecture

Stop Recreating .NET Solutions From Scratch

I have been there myself, so I am not preaching to you.

I have spent far too much time building new .NET solutions from scratch. Actually, that is not completely true. Most of the time, I was not starting from scratch. I was copying from the first solution I built, or the second one, or the third one, then modifying it again for the fourth or fifth project.

It worked, but it was painful.

The worse part is that when you get used to a painful method, it starts to feel normal. Then normal becomes a habit. Then the habit becomes stubborn. Before you know it, you are defending a bad workflow just because you have repeated it enough times.

That was my mistake.

Every time I wanted to start a new API or SaaS-style project, I would spend time thinking about the same things again:

  • project structure
  • configuration
  • OpenAPI
  • health checks
  • authentication and authorisation
  • logging
  • testing setup
  • architecture tests
  • maybe .NET Aspire
  • all the other boring but important moving parts

None of these things are useless. They matter. A good solution needs structure.

But doing the same setup manually again and again is not engineering. It is waste.

Copy and paste is not a real template

There is nothing wrong with learning by manually creating a few solutions. In fact, you probably should do that at the beginning. It helps you understand what is actually inside your application.

The problem starts when you already know your preferred setup, but you still keep copying old projects and cleaning them up by hand.

That’s why I wrote this article. Believe me, things will go messy if you don’t change your approach.

You copy an old solution. Then you rename projects. Then you fix namespaces. Then you remove old features. Then you update package versions. Then you change configuration files. Then you realise you forgot something. Then you discover some random leftover name from the previous project.

That is not a clean starting point. That is a recycled accident.

A proper template gives you a repeatable way to generate a clean starting point. The .NET CLI uses the Microsoft Template Engine behind dotnet new to create projects and artifacts based on templates and options.

Microsoft already gave us the tool

Microsoft Template Engine is not some obscure trick. It is the system behind dotnet new.

You can use existing templates, install template packages, create your own templates, and generate projects from them. Microsoft’s documentation explains that .NET templates can generate projects, files, and resources, and template packages can be installed from NuGet, a NuGet package file, or a file system directory.

How cool is that?

That means your standard API setup does not need to live as a half-forgotten GitHub repository that you copy every few months.

It can become a template.

For example, instead of manually rebuilding the same solution structure, you could have something like:

dotnet new install MySolution.Templates
dotnet new mysolution-api -n MyNewApi

Then your solution is generated with the structure and defaults you already decided are useful.

What should go into a good .NET template?

A useful template should not be a dumping ground for every idea you have ever had.

That is another mistake.

The goal is not to create a “perfect enterprise solution” before the project even starts. That usually leads to over-engineering. The goal is to create a sensible baseline that saves time without forcing unnecessary complexity.

For an API template, I would consider including:

  • a clean solution and project structure
  • sensible naming
  • OpenAPI setup
  • health checks
  • logging configuration
  • basic validation setup
  • test projects
  • architecture test project
  • common configuration files
  • optional authentication setup
  • optional .NET Aspire support, if it genuinely helps

Microsoft’s template system supports replacing values, including and excluding files, and custom processing during template generation, so you can make templates flexible instead of hard-coding everything.

The real benefit is not speed

When you create projects manually, every solution slowly becomes different. One has health checks. One does not. One has better logging. One has outdated package references. One has tests in a different structure. One has old naming conventions.

That inconsistency becomes expensive later.

A template forces you to make decisions once, improve them over time, and reuse them properly.

That is how you turn experience into a tool.

Honest tip

Don’t do this too early.

If you have only built one solution, you probably do not have a template yet. You have an experiment.

Build manually a few times. Learn what repeats. Learn what is actually useful. Learn what you keep deleting. Then create a template from the parts that survive.

How to create a simple .NET solution template

Here is the part that confused me at first.

Microsoft’s documentation explains custom templates, but the examples are mostly based around item templates, project templates, and packaging templates. That is useful, but it can make the process look more complicated than it needs to be when your actual goal is simple:

I already have a full .NET solution structure I like. I want to reuse it.

You do not need to start with a strange folder structure such as working, content, and separate test folders just because a tutorial uses that approach.

You can start with a real solution.

Build the solution the way you actually want future projects to look.

For example:

TemplateSolution/
│
├── TemplateSolution.sln
│
├── src/
│   ├── TemplateSolution.Api/
│   ├── TemplateSolution.Application/
│   ├── TemplateSolution.Domain/
│   └── TemplateSolution.Infrastructure/
│
├── tests/
│   ├── TemplateSolution.UnitTests/
│   └── TemplateSolution.ArchitectureTests/
│
└── .template.config/
    └── template.json

The important part is this:

.template.config/template.json

That folder should sit at the root of your template, next to the .sln file.

Microsoft’s documentation confirms that the template.json file belongs inside a .template.config folder at the root of the template, and that the template source files can be whatever files and folders you want the template engine to use.

Step 1: Create your normal solution

Create your solution exactly how you like it.

But be careful.

A template should include repeatable foundations, not every random idea you once thought was clever.

Also remove junk before turning it into a template, or use .gitignore. Do not include secrets. Do not include real connection strings. Do not include environment-specific rubbish. That mistake is not “template engineering”; it is negligence.

Step 2: Use a clear placeholder name

Pick a source name that appears everywhere in the solution.

For example TemplateSolution. Then use it in:

TemplateSolution.sln
TemplateSolution.Api
TemplateSolution.Application
TemplateSolution.Domain
TemplateSolution.Infrastructure

This matters because the template engine can replace the configured sourceName with the name provided by the user when they run dotnet new. Microsoft documents sourceName as the value the template engine searches for in file names and file contents, replacing it with the name passed through -n or --name.

So later, when someone runs this:

dotnet new my-api -n Orders

The template engine can replace TemplateSolution with Orders.

That is the bit that saves you from painful renaming.

Step 3: Add .template.config/template.json

At the root of the solution, create this folder:

.template.config

Inside it, create this file:

template.json

Put the following in as a starting point. This is more complicated than the one in Microsoft documentation. I needed a few more salt and pepper such as the ability to restore NuGet packages, language name and template type:

{
{
  "$schema": "http://json.schemastore.org/template",
  "author": "Saeed Ghanavat",
  "classifications": [
    "Common",
    "WebApi",
    "ClassLibrary",
    "Clean Architecture"
  ],
  "name": "YourCompany API Solution",
  "description": "Whatever description you want.",
  "identity": "YourCompany.ApiSolution.Templates",
  "shortName": "my-api-template",
  "tags": {
    "language": "C#",
    "type": "solution"
  },
  "sourceName": "TemplateSolution",
  "preferNameDirectory": true,
  "primaryOutputs": [
    {
      "path": "YouSolution.Templates.sln"
    }
  ],
  "postActions": [
    {
      "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
      "description": "Restore NuGet packages required by this project.",
      "condition": "(!skipRestore)",
      "continueOnError": true,
      "manualInstructions": [
        {
          "text": "Run 'dotnet restore'"
        }
      ]
    }
  ]
}
}

The $schema value is useful because editors that support JSON schemas can provide validation and IntelliSense. This helped me a lot because I was not guessing every property manually. Microsoft also documents this schema field and explains that the full schema is available through JSON Schema Store.

The most important values are:

shortName
sourceName
identity
name

shortName is what you type in the CLI.

dotnet new my-api-template

sourceName is the placeholder name that gets replaced:

TemplateSolution

identity should be unique.

name is the friendly display name.

Step 4: Install the template locally

Open a terminal at the root of your solution, where the .sln file and .template.config folder are.

Then run:

dotnet new install ./

The dotnet new install command installs a template package from a path or NuGet package ID. For local development, installing from the current folder is enough.

Then check that your template is available:

dotnet new list

You should see your template listed with the short name you configured.

Step 5: Generate a new solution from your template

Now test it properly.

Go to a different folder and run:

dotnet new my-api-template -n OrdersService

That should generate a new solution based on your template.

Then build it, please. You don’t want a broken code generated off of your template.

dotnet build

Step 6: Improve the template over time

Your first version will probably not be perfect.

That is fine.

Use it. Find what feels wrong. Remove unnecessary things. Add missing things. Keep improving the baseline. This is exactly what I did.

When you change the template and need to reinstall it, you can uninstall and install it again:

dotnet new uninstall ./
dotnet new install ./

This is where the value starts to show.

Instead of improving one project and forgetting to copy the improvement elsewhere, you improve the template. Then every new project starts from a better place.

That is it.

The hard part is not the tooling. The hard part is deciding what your standard solution should look like.

And that is exactly why templates are useful. They force you to stop rebuilding the same foundation again and again, and they turn your repeated decisions into something reusable.

essential