Skip to content

Introduction

Pragmatic Design is a .NET meta-framework powered by Source Generators. You declare your architecture with attributes and partial classes. At compile time, a unified generator detects what you wrote and emits the plumbing: repositories, endpoints, validators, mappers, DI registration, metadata. Zero reflection, zero runtime overhead, AOT-ready.

Every .NET service ends up re-implementing the same scaffolding:

  • repository classes that wrap DbContext<T>
  • endpoint registration and request binding
  • validation pipelines, authorization filters
  • DI AddScoped lists that drift out of sync
  • entity configuration, audit fields, soft-delete
  • outbox, saga, retry, cache-key generation
  • cross-module glue that silently rots

This code is repetitive, error-prone, easy to miss in review, and it isn’t yours — it’s infrastructure. Hand-writing it again for every service is the worst kind of friction: it slows the first week and keeps slowing you forever.

You write the declaration:

[Entity<Guid>, Auditable, SoftDelete]
[BelongsTo<ReservationsBoundary>]
public partial class Reservation
{
[LogicKey] public string Code { get; private set; } = "";
public DateTime CheckIn { get; private set; }
public DateTime CheckOut { get; private set; }
}
[DomainAction, BelongsTo<ReservationsBoundary>]
[Endpoint(HttpVerb.Post, "/reservations")]
[Validate]
public partial class CreateReservation : IDomainAction<ReservationResult>
{
public required DateTime CheckIn { get; init; }
public required DateTime CheckOut { get; init; }
public required string GuestId { get; init; }
}

The generator forges everything else:

  • Reservation.Create(...) factory with audit fields and soft-delete
  • IRepository<Reservation, Guid> with CRUD + logic-key lookup
  • EF Core entity configuration for Reservation
  • CreateReservationInvoker with DI resolution, validation, authorization, logging, telemetry
  • HTTP endpoint registration at POST /reservations with strongly-typed request/response
  • Metadata aggregation into the host so everything wires up on startup

You can read every generated file — it lives in obj/Generated/. No bytecode manipulation, no magic runtime behaviour.

  • Source Generator First — zero reflection, AOT-ready, errors at compile time, not at first request.
  • Composition by presence — add a NuGet, the generator detects it and wires up. Remove it, the generated code vanishes. No feature flags, no if statements in config.
  • Host decides — your app’s .csproj is the source of truth for what’s active. Module strategy (IPragmaticBuilder.Use*()) in Program.cs picks implementations.
  • Default always works — every module ships a working default (in-memory store, allow-all policy, passthrough cache). You override to go to production.
  • Result over exceptions — domain errors are values, not throws. See Pragmatic.Result.
  • No hidden reflection — if you think you need typeof(T).GetProperties(), ask the generator to produce the accessor instead.
┌─────────────────────┐
│ Product / App │ Showcase, your services
├─────────────────────┤
│ Medium Block │ Identity.Local, Comments, Tags, Attachments
├─────────────────────┤
│ Building Block │ Result, Actions, Persistence, Messaging, …
└─────────────────────┘
TierScopeExamples
Building BlockToolkit infrastructure. You use it to build your domain.Result, Actions, Persistence, Composition, Events, Messaging, Jobs, Migrations
Medium BlockCross-cutting packages that ship complete. Include, configure, use.Identity.Local, Comments, Tags, Attachments
Product / AppComposition of blocks into a running application.Showcase (E2E reference), your own services

See Architecture for the layering and how modules compose.

You probably want Pragmatic Design if:

  • you ship .NET services in production and spend energy fighting boilerplate
  • you value explicit, inspectable code over framework magic
  • you want AOT compilation as a real option, not an aspiration
  • you’ve used MediatR / MassTransit / Hangfire / AutoMapper / FluentValidation and like some, but want them composed instead of stitched together

You probably don’t want it (yet) if:

  • you need a framework that hides all the plumbing behind a single decorator
  • you depend on ecosystems (OpenTelemetry ingest, Stripe bindings) that aren’t first-class yet — several are planned
  • you can’t run .NET 10 preview or later

The current public preview is 0.8. Forty modules ship as NuGet packages, an end-to-end Showcase application demonstrates how they compose, and 42 console / web samples are available to run with dotnet run. APIs are stabilising but may still change before 1.0.