dotskit does the same and also adds flags to services already generated, keeping the team's changes. This page is the template on its own.
The template lives in the template folder of this repository, next to its .template.config:
dotnet new install /path/to/DotNetSolutionKit/templateAfter pulling changes, install it again over the old one:
dotnet new install /path/to/DotNetSolutionKit/template --forceThe first run generates the shared Common projects, the root solution file and the first service.
Pass --Solution for it:
dotnet new DotNetSolutionKit -N MyCompany -P MyProduct -S Orders --SolutionEvery further service is added from the solution's root:
dotnet new DotNetSolutionKit -N MyCompany -P MyProduct -S BillingIt generates the service folder and, after asking to run a command (--allow-scripts yes runs it without
asking), adds the service's projects to All.sln, in a solution folder named after the last segment of the
service's name. It leaves the files already in the solution as they are: Common gets nothing new, so a
service takes only the flags whose part Common already has, and the database, the deployment and the
names are passed again as the solution has them.
A service generated without --Solution leaves the root src/Directory.Packages.props alone. In a solution generated by this version
of the template it already holds every version a service needs, with any -H and --Messaging; the flags
that add files to Common are chosen once for the solution, see parameters. In an older
solution the build stops with NU1010 and names the packages that have no version; copy their
PackageVersion lines from the template's Directory.Packages.props
and, for a flag, from its file in src/package-versions/.
| Parameter | Default | What it does |
|---|---|---|
-N, --NamespaceRoot |
MyCompany |
Organization name, the root namespace. May be dotted. |
-P, --ProductName |
Product |
Product name. May be dotted. |
-S, --ServiceNameOrCustom |
Service |
Service name. May be dotted. |
--Solution |
false |
Generates the solution: Common projects, All.sln and the root files, with the first service. Without it, only the service folder, into a solution already there. -M false of earlier versions still means the same. |
--Database |
postgres |
Database: postgres or mssql (SQL Server). One value for the solution: pass it with --Solution and to every service. |
-H, --Hangfire |
true |
Background jobs on Hangfire. Per service, also without --Solution. |
--Messaging |
none |
Message bus: outbox or direct. Per service. |
-I, --Infisical |
false |
Secrets from Infisical. |
--Vault |
false |
Secrets from HashiCorp Vault. |
--DiffApi |
false |
API contract diff on pull requests. |
--GitHubCiCd |
false |
CI on GitHub Actions: build, tests on real servers, coverage on demand, secret scan. With --Solution: the workflows cover every service in All.sln. |
-FF, --FeatureFlags |
false |
Feature flags. |
--HierarchyRules |
false |
Access rules over a tenant tree. |
--Storage |
false |
Object storage, S3-compatible. |
-CH, --ClickHouse |
false |
ClickHouse: connections, schema check, readiness. |
--MongoDB |
false |
MongoDB beside the main database: the client, the service's database, readiness. |
--Notify |
none | Channels of notifications: email - through SMTP or Graph API, with a sandbox outside Production; with --Messaging other services ask for it by a bus command. |
--TestFramework |
nunit |
Test framework of the service's tests, nunit or xunit (v3); per service. Common's own tests stay on NUnit. See testing. |
--Audit |
false |
Audit journal of entity changes; takes effect only with --Messaging outbox; without it the generation says it was not applied. |
--ApiGateway |
false |
An API gateway on YARP in place of a service. Without --Solution only; with it the generation says it was not applied. |
--Deploy |
compose |
Deployment files: compose, k8s or none. One value for the solution: pass the same to every service generated later. |
--Agent |
claude |
Rules and skills for an AI agent: claude, opencode or none. With --Solution. |
--HttpPort |
free port | Port in launchSettings.json and on the host under compose; without it, a free port from 5000-5999 on the generating machine, so services generated one after another do not share a port. |
-I, --Vault, --DiffApi, --FeatureFlags, --HierarchyRules, --Storage, --ClickHouse and --Audit add files to Common,
and --MongoDB and --Notify add a project to src/capabilities, so they have to be passed with --Solution,
when the shared code is generated. Pass -I, --Vault, --DiffApi, --FeatureFlags, --Storage, --ClickHouse, --MongoDB, --Notify and --Audit again to
each service generated later that should use them: they change the service's code too, and the service
then wires what Common and src/capabilities already have. A service generated without them leaves them out.
Any of -N, -P and -S may contain dots:
dotnet new DotNetSolutionKit -N Acme.Corp -P Shop.Online -S Sales.Orders --Solutiongenerates Acme.Corp.Shop.Online.Sales.Orders.API and the rest of the projects under that name. A dotted
service name lets a domain be split into small services, such as Sales.Orders and Sales.Invoicing,
instead of one shared project for the whole of Sales. A dotted product name keeps the origin in every
namespace of a fork or a downloaded copy, where the repository name is gone.
Where a dot cannot go, the name is derived from the service name:
| Where | Form | Example |
|---|---|---|
| Namespaces, projects, folders | as given | Acme.Corp.Shop.Online.Sales.Orders |
| C# identifiers | without dots | SalesOrdersDbContext |
| Database schema, Infisical folder, queue names | lower case, dots replaced by underscores | sales_orders |
Name a service after its area, not after its main aggregate: -S Basket with a class Basket in it makes
Basket both a namespace and a type, and C# refuses the type where the namespace is in scope (CS0118).
Baskets, Shopping or Sales.Baskets avoid it.
A generated service has a model and no migrations, so its database starts without tables. Add the first migration before the first run; the command needs no database:
cd src/services/MyCompany.MyProduct.Orders
dotnet ef migrations add Initial \
-p MyCompany.MyProduct.Orders.Infrastructure \
-s MyCompany.MyProduct.Orders.Infrastructure \
-o EntityFramework/MigrationsThe migration also creates the tables of the outbox, when the service was generated with
--Messaging outbox. A service started without migrations logs a fatal line with this command, and every
query to a missing table fails.
appsettings.json and appsettings.Local.json ship with the required values empty and a _comment_*
key next to each one saying what goes there. Put your local values into appsettings.Secrets.json in the
same folder. It is read only in the Local environment, the generated .gitignore keeps it out of the
repository, and environment variables override it.
In other environments, pass the same keys as environment variables, for example
ConnectionStrings__DefaultConnection.
The local launch profile sets ASPNETCORE_ENVIRONMENT=Local, so a plain dotnet run reads the secrets
file:
dotnet run --project src/services/MyCompany.MyProduct.Orders/MyCompany.MyProduct.Orders.APIA misconfigured service stops at startup rather than at the first request; see startup checks.
To run the service before its database, broker or secret store exist, switch them off in configuration,
for example Database__Enabled=false; see switching dependencies off.