EnterpriseModularMonolith.Templates 2.0.0

dotnet new install EnterpriseModularMonolith.Templates@2.0.0
                    
This package contains a .NET Template Package you can call from the shell/command line.

Enterprise Modular Monolith — dotnet new templates

dotnet new templates for an ASP.NET Core 10 Enterprise Modular Monolith:

  • Clean Architecture modules (Domain / Application / Infrastructure) with a per-module Contracts project as its public API; layering and module isolation enforced by NetArchTest architecture tests
  • CQRS mediator with FluentValidation in the pipeline (failures become RFC-7807 problem responses) and per-module authorization policies
  • Wolverine + RabbitMQ & Kafka service bus (both transports wired; route per message)
  • Marten event sourcing on PostgreSQL
  • EF Core (schema per module, tenant-scoped entities, optimistic concurrency → 409)
  • Marten event sourcing: an item's change history as an event stream (optional)
  • Per-module settings and health checks (Modules:<Module>, /health/ready)
  • OpenIddict PKCE/OAuth2 with cookie-based 2FA, impersonation and multi-tenancy
  • An Angular SPA client (optional)
  • A provider-adapter integration example (domain service → router → factory → adapter → resilient IIntegrationHttpClient)
  • A full unit / integration / architecture test suite

Package: EnterpriseModularMonolith.Templates

Templates

Template Short names What it does
Enterprise Modular Monolith enterprise-modular, emm Generates the full solution.
Enterprise Modular Monolith — Add Module enterprise-modular-module, emm-module Adds a new module to an existing solution and wires it in (.slnx, host and unit-test references, Angular route/nav/i18n). Run from the solution root.

Install

From NuGet (once published):

dotnet new install EnterpriseModularMonolith.Templates

Or install the locally built package:

dotnet new install ./nupkg/EnterpriseModularMonolith.Templates.<version>.nupkg

Usage

Create a new solution:

dotnet new emm -n MyApp
# API-only (no Angular client):
dotnet new emm -n MyApp --IncludeUi false

Optional features

Each of these is on by default; set the flag to false to leave it out:

Flag Default Off behaviour
--IncludeUi true No Angular client (API-only).
--IncludeMultiTenancy true Single-tenant: removes the Tenant entity, per-user TenantId, tenant endpoints/directory/validator, ICurrentTenant, and tenant-scoped admin queries.
--IncludeRabbitMq true Omits the Wolverine RabbitMQ transport (package, wiring, RabbitMq config, compose service).
--IncludeKafka true Omits the Wolverine Kafka transport (package, wiring, Kafka config, compose service).
--IncludeProviderAdapter true Removes the 3rd-party provider-adapter example and the shared HTTP resilience infrastructure.
# Lean API: no UI, single-tenant, Kafka only, no integration example
dotnet new emm -n MyApp \
  --IncludeUi false --IncludeMultiTenancy false \
  --IncludeRabbitMq false --IncludeProviderAdapter false

With both --IncludeRabbitMq false and --IncludeKafka false, Wolverine still runs and routes messages locally.

dotnet new does not prompt for options — it applies defaults unless you pass flags. For an interactive experience (prompts for the project name and whether to include the Angular UI), use the wrapper script instead:

pwsh -ExecutionPolicy Bypass -File create.ps1

In Visual Studio / Rider, the Include Angular UI option is shown as a checkbox in the New Project wizard automatically.

Add a module (run from the solution root; project name is auto-detected from the .slnx):

dotnet new emm-module -n Billing --allow-scripts yes

The template generates the module plus a one-off wire-Billing.ps1 script and runs it as a post-action (requires PowerShell 7, pwsh). Without --allow-scripts yes, dotnet new asks for confirmation first. If the script did not run, run it yourself from the solution root:

pwsh -ExecutionPolicy Bypass -File wire-Billing.ps1

Each new module comes with a CRUD slice (list/get/create/update/delete) behind its own <Module>.Read / <Module>.Write policies, validators, an integration event plus handler in its Contracts project, unit and integration tests, and (optionally) an Angular page.

Flag Default Effect
--IncludeUi true Angular page, service, route, nav link and i18n entries.
--IncludeMultiTenancy true Tenant-scoped entity and queries. Must match the solution.
--IncludeEventSourcing false Marten event stream per item, exposed as GET /<module>/{id}/history.
--IncludeProviderAdapter false The 3rd-party provider-adapter example. Requires a solution created with it.

The script verifies these match the solution before wiring anything. The script checks the module fits the solution first (a PascalCase name that is not taken or reserved, matching provider-adapter option); if not, it removes the generated files and tells you what to change, so you can simply re-run dotnet new. It deletes itself once wiring succeeds; if a later step fails it stays in place and can be re-run safely.

How modules plug in

Wiring is deliberately small, because the solution discovers modules by convention:

  • Registration — every *.Modules.* project referenced by the host is a module. At build time the host project turns those references into [ModuleAssembly] attributes; at startup ModuleCatalog loads each module's single IModule implementation (e.g. BillingModule), calls its Register, and adds its assembly to Wolverine handler discovery. Mediator handlers (ICommandHandler/IQueryHandler) and endpoints (IEndpointBase) in the module are registered automatically — Register only sets up the module's own infrastructure (DbContext, options, adapters).
  • Database — modules read ConnectionStrings:<Module> and fall back to ConnectionStrings:Default, so a new module needs no configuration. Set the module key to move it to its own database.
  • Contracts — each module owns a <Project>.Modules.<Module>.Contracts project: the events, request/response messages and interfaces other modules may use. Modules reference each other's Contracts, never each other's implementation, and an architecture test enforces exactly that.
  • Authorization — modules declare their own policies with services.AddModulePolicy(...); integration tests re-register them against the test scheme automatically.
  • Settings — each module binds Modules:<Module> to its own options class, validated at startup (ValidateOnStart), so a bad value stops the boot instead of the first request.
  • Health — a module registers its own check; /health is liveness and /health/ready covers every module's database, so an instance only takes traffic once its modules can serve.
  • Tests — the architecture tests discover modules through the host the same way, so a new module is covered by the layering, isolation and convention rules without edits. Integration tests reach it through the host; unit tests reference the module project.

So Program.cs, appsettings.json, docker-compose.yml and the architecture tests never change when a module is added. To remove one, remove its project from the solution, the host and the unit tests, then delete its folders (and its Angular page, route and nav link, if any).

CI

.github/workflows/ci.yml tests the templates, not just the source:

  • source / client — build the template source (all features on) and run the .NET and Angular builds and tests.
  • package — packs and inspects the .nupkg: no bin/obj/.vs/node_modules, the dot-files NuGet likes to drop are present, and the package stays under its size limit.
  • templates — installs that package and generates solutions for five variants (all features, Windows, API-only single-tenant, one broker, no brokers), adds two or three modules to each, then builds and tests the result. A generated solution must build with zero warnings, leave no wire-*.ps1 behind and contain no PayFlow placeholder.
  • template-guardrails — checks the wiring script refuses modules that do not fit the solution (option mismatch, invalid or reserved name, name already taken) and leaves the solution byte-for-byte unchanged.

Build / pack locally

dotnet pack EnterpriseModularMonolith.csproj -o nupkg

This packages solution-full/ (the enterprise-modular template) and enterprise-module/ (the enterprise-modular-module template) into the NuGet package. Bump <PackageVersion> in EnterpriseModularMonolith.csproj for each release.

To try uncommitted template changes without packing, install from the repository root (not from enterprise-module/, which reads its content from ../solution-full):

dotnet new install . --force

Versioning and releases

The package version lives in EnterpriseModularMonolith.csproj and every change is recorded in CHANGELOG.md. The major version changes when a solution generated by an older version can no longer take modules from the newer one — dotnet new emm-module checks this and refuses, naming the template version to install instead.

To release:

# 1. bump <PackageVersion> in EnterpriseModularMonolith.csproj and add the CHANGELOG section
# 2. tag the commit; the tag must match the version
git tag v2.0.0 && git push origin v2.0.0

release.yml then runs the full CI suite, checks the tag matches <PackageVersion> and that the CHANGELOG documents it, publishes to NuGet and creates a GitHub release. It needs a NUGET_API_KEY secret on the nuget environment, which is also where you can require a reviewer before anything is published.

Troubleshooting

dotnet new emm-module says the module does not fit the solution. The module's options must match the solution it is added to (multi-tenancy, provider adapter) and the solution must come from templates 2.0 or later. The message names the flag to change; the generated files are removed, so just re-run with the corrected command.

The module was created but nothing was wired. The wiring runs as a post-action and needs PowerShell 7 (pwsh) plus --allow-scripts yes. Run it yourself from the solution root: pwsh -ExecutionPolicy Bypass -File wire-<Module>.ps1. Every step is idempotent, so re-running is safe.

Generating the module's first migration. Run the wiring script yourself with pwsh -File wire-<Module>.ps1 -WithMigration (before it deletes itself) to add an InitialCreate migration instead of relying on auto-create.

A module's tables are missing after adding an entity. Auto-create only builds a schema that does not exist yet. Once a module is deployed, add a migration instead:

dotnet ef migrations add AddThing --project src/modules/<Module>/<Project>.Modules.<Module>   --startup-project src/host/<Project>ApiHost.csproj

Removing the Sample module. It is a worked example, not a fixture — delete it once your own modules exist: remove src/modules/Sample/* (module and Contracts), tests/*/Sample/, the Angular page (client/src/app/pages/sample, core/services/sample.service.ts, its route, nav link and sample i18n entries), and the three project references (solution, host, unit tests). Nothing else points at it — the host discovers modules from its project references.

Repository layout

EnterpriseModularMonolith.csproj   # packaging project (PackageId, version, content globs)
CHANGELOG.md                       # what changed per version (checked by the release workflow)
solution-full/                     # the enterprise-modular template content
enterprise-module/                 # the enterprise-modular-module template: template.json + wire-Sample.ps1 only
.github/workflows/                 # ci.yml (source, package and template tests), release.yml

The module template has no copy of the module code. Its template.json sources the Sample module, its tests (tests/*/Sample/) and its Angular page directly from solution-full/, so the full solution's Sample module is the single source of truth: change it there and both templates pick it up. Those files keep the PayFlow project placeholder; wire-<Module>.ps1 renames it to the host solution's name when the module is added. Keep everything module-specific under a Sample folder (e.g. tests/PayFlow.UnitTests/Sample/) so it is included and renamed per module.

  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.0.0 111 9/18/2026
1.2.0 268 6/11/2026
1.1.16 261 6/7/2026
1.1.15 271 6/6/2026
1.1.11 254 6/6/2026
1.1.10 264 6/2/2026

See CHANGELOG.md.