A production-ready modular .NET framework for building enterprise applications.
Modular Monolith + Vertical Slice Architecture (VSA)
- BuildingBlocks (
src/BuildingBlocks/) — shared framework libraries (Core, Persistence, Web, Caching, Eventing, etc.) - Modules (
src/Modules/) — bounded contexts (Identity, Multitenancy, Auditing) - Playground (
src/Playground/) — sample host applications (API, AppHost) - Tests (
src/Tests/) — per-module test projects + architecture tests
Modules communicate through Contracts projects only. A module MUST NOT reference another module's runtime project.
Modules.Identity/ ← runtime (internal)
Modules.Identity.Contracts/ ← public API (commands, queries, events, DTOs, service interfaces)
Each feature is a vertical slice inside Features/v{version}/{Area}/{FeatureName}/:
Features/v1/Users/RegisterUser/
├── RegisterUserEndpoint.cs # Minimal API endpoint
├── RegisterUserCommandHandler.cs # CQRS handler
└── RegisterUserCommandValidator.cs # FluentValidation
Additional module folders: Domain/, Data/, Services/, Events/, Authorization/.
| Concern | Technology |
|---|---|
| Framework | .NET 10 / C# latest |
| Solution format | .slnx (XML-based) |
| Package management | Central (Directory.Packages.props) |
| CQRS / Mediator | Mediator 3.0.1 (source generator) |
| Validation | FluentValidation 12.x |
| ORM | Entity Framework Core 10.x |
| Database | PostgreSQL (Npgsql) |
| Auth | JWT Bearer + ASP.NET Identity |
| Multitenancy | Finbuckle.MultiTenant 10.x (claim/header/query strategies) |
| Caching | Redis (StackExchange) |
| Jobs | Hangfire |
| Resilience | Microsoft.Extensions.Http.Resilience (Polly v8) |
| Feature Flags | Microsoft.FeatureManagement with tenant overrides |
| Idempotency | Idempotency-Key header with cache-based replay |
| Webhooks | Tenant-scoped subscriptions with HMAC signing |
| Real-time | Server-Sent Events (SSE) |
| Logging | Serilog + OpenTelemetry (OTLP) |
| Object mapping | Mapster |
| API docs | OpenAPI + Scalar |
| API versioning | Asp.Versioning |
| Hosting | .NET Aspire (AppHost) |
| Testing | xUnit, Shouldly, NSubstitute, AutoFixture, NetArchTest |
# Build
dotnet build src/FSH.Starter.slnx
# Run API (from repo root)
dotnet run --project src/Playground/FSH.Starter.Api
# Run with Aspire
dotnet run --project src/Playground/FSH.Starter.AppHost
# Run tests
dotnet test src/FSH.Starter.slnxStatic extension methods on IEndpointRouteBuilder. Return RouteHandlerBuilder.
public static class RegisterUserEndpoint
{
internal static RouteHandlerBuilder MapRegisterUserEndpoint(this IEndpointRouteBuilder endpoints)
{
return endpoints.MapPost("/register", (RegisterUserCommand command,
IMediator mediator, CancellationToken cancellationToken) =>
mediator.Send(command, cancellationToken))
.WithName("RegisterUser")
.WithSummary("Register user")
.RequirePermission(IdentityPermissionConstants.Users.Create);
}
}- Commands/Queries → defined in
Modules.{Name}.Contracts(implementICommand<TResponse>/IQuery<TResponse>) - Handlers → defined in
Modules.{Name}/Features/(implementICommandHandler<T, TResponse>/IQueryHandler<T, TResponse>) - Handlers return
ValueTask<T>and use.ConfigureAwait(false)
FluentValidation validators are auto-registered by ModuleLoader. Name them {Command}Validator.
- Inherit from
DomainEvent(abstract record withEventId,OccurredOnUtc,CorrelationId,TenantId) - Entities implement
IHasDomainEventswith_domainEventslist - Integration events implement
IIntegrationEvent, handlers implementIIntegrationEventHandler<T>
BaseEntity—Id,CreatedAt,UpdatedAt,TenantIdAggregateRoot— extendsBaseEntitywith domain eventsIHasTenant,IAuditableEntity,ISoftDeletable— marker interfaces
Each module implements IModule with [FshModule(Order = n)] attribute:
[FshModule(Order = 1)]
public class IdentityModule : IModule
{
public void ConfigureServices(IHostApplicationBuilder builder) { ... }
public void MapEndpoints(IEndpointRouteBuilder endpoints) { ... }
}Endpoints are grouped under versioned API paths: api/v{version:apiVersion}/{module}.
Use framework exception types: CustomException (with HttpStatusCode), NotFoundException, ForbiddenException, UnauthorizedException. Global handler converts to ProblemDetails (RFC 9457).
Constants in Shared/Identity/IdentityPermissionConstants.cs. Applied via .RequirePermission() on endpoints.
Use Specification<T> base class from Persistence/Specifications/ for query composition. Default AsNoTracking = true.
- Namespace style: File-scoped (
namespace X;) - Indentation: 4 spaces
- Var usage: Prefer explicit types;
varonly when type is apparent from RHS - Null checks:
is null/is not null(not== null) - Pattern matching: Preferred over
is/ascasts - Switch expressions: Preferred
- Async:
ValueTask<T>for handlers,.ConfigureAwait(false)on all awaits - Guard clauses:
ArgumentNullException.ThrowIfNull(param)at method entry - Properties: Prefer auto-properties,
default!for required non-nullable strings - Records: Use for DTOs, events, and value objects
- Naming:
MethodName_Should_ExpectedBehavior_When_Condition - Pattern: Arrange-Act-Assert with
#regiongrouping (Happy Path, Exception, Edge Cases) - Assertions: Shouldly (
result.ShouldBe(...),result.ShouldNotBeNull()) - Mocking: NSubstitute (
Substitute.For<IService>()) - Test data: AutoFixture (
_fixture.Create<string>()) - Architecture tests: NetArchTest enforces module boundary rules
DO NOT modify BuildingBlocks without explicit approval. These are shared framework libraries consumed by all modules. Changes here have wide blast radius.
- Add command/query + response in
Modules.{Name}.Contracts/v1/{Area}/{Feature}/ - Add handler in
Modules.{Name}/Features/v1/{Area}/{Feature}/ - Add validator in the same feature folder
- Add endpoint in the same feature folder
- Wire endpoint in the module's
MapEndpoints()method - Add tests in
Tests/{Name}.Tests/
- Create
Modules.{Name}/andModules.{Name}.Contracts/projects undersrc/Modules/{Name}/ - Implement
IModulewith[FshModule(Order = n)] - Add DbContext extending from framework base
- Register in
Program.csmodule assemblies array - Add migration project if needed
- Add test project in
src/Tests/ - Add architecture test rules