Skip to content

Installation

  • .NET 10 SDK or later (C# 14 support required)
  • An IDE with Roslyn / source-generator support: Rider, Visual Studio 2022+, or VS Code with the C# Dev Kit
Terminal window
dotnet new webapi -n MyService
cd MyService

The simplest starting point is a Web API with actions, endpoints, and EF Core persistence:

Terminal window
# Composition host (required — ties everything together)
dotnet add package Pragmatic.Composition.Host --version 0.8.0-preview.*
# Domain actions + HTTP endpoints
dotnet add package Pragmatic.Actions --version 0.8.0-preview.*
dotnet add package Pragmatic.Endpoints --version 0.8.0-preview.*
# Persistence (pick EFCore for relational)
dotnet add package Pragmatic.Persistence.EFCore --version 0.8.0-preview.*
# Validation
dotnet add package Pragmatic.Validation --version 0.8.0-preview.*
# The unified source generator (runs at build time)
dotnet add package Pragmatic.SourceGenerator --version 0.8.0-preview.*

Each NuGet carries its own analyzer/generator references. Pragmatic.SourceGenerator is the one unified generator — you add it once and it activates the features it detects in the compilation (see Feature Detection).

Program.cs:

using Pragmatic.Composition.Hosting;
await PragmaticApp.RunAsync(args, app =>
{
// All Use*() calls are optional — each module ships a working default.
// Add strategy calls as you need specific behaviour.
});

That’s it. dotnet run starts the host; endpoints discovered at compile time are already mapped.

using Pragmatic.Actions.Abstractions;
using Pragmatic.Actions.Attributes;
using Pragmatic.Endpoints;
using Pragmatic.Endpoints.Attributes;
using Pragmatic.Result;
using Pragmatic.Validation.Attributes;
[DomainAction]
[Endpoint(HttpVerb.Post, "/greet")]
[Validate]
public partial class Greet : DomainAction<string>
{
[Required, MinLength(1)]
public required string Name { get; init; }
public override Task<Result<string, IError>> Execute(CancellationToken ct = default)
=> Task.FromResult<Result<string, IError>>($"Hello, {Name}!");
}

Build and send a request:

Terminal window
curl -X POST http://localhost:5000/greet -H 'Content-Type: application/json' -d '{"Name":"Alice"}'
# "Hello, Alice!"

No manual endpoint registration, no DI ceremony, no validator wiring.

Pragmatic ships 40 NuGets. The families you likely need:

FamilyPackagesWhen
FoundationPragmatic.Result, Pragmatic.Ensure, Pragmatic.AbstractionsAlways (transitive via most modules)
CorePragmatic.Actions, Pragmatic.Endpoints, Pragmatic.Composition.Host, Pragmatic.EventsBuilding a service with a domain
PersistencePragmatic.Persistence.EFCore, Pragmatic.MigrationsRelational database
MessagingPragmatic.Messaging, Pragmatic.Messaging.EFCore (outbox), .Channels, .RabbitMQAsync messaging
Identity & AuthPragmatic.Identity, Pragmatic.Identity.Local, Pragmatic.AuthorizationUser identity and permissions
ObservabilityPragmatic.Logging, Pragmatic.ResilienceProduction hardening
DocumentsPragmatic.Documents.Pdf, .Docx, .Xlsx, .CsvGenerating documents
Medium BlocksPragmatic.Comments, Pragmatic.Tags, Pragmatic.AttachmentsEntity traits (opt-in)

See the module catalogue in the sidebar for the full list, each with its own Overview page, Concepts, Getting Started, and API reference.

For multi-project solutions, use Directory.Packages.props to pin versions once:

<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Pragmatic.Composition.Host" Version="0.8.0-preview.*" />
<PackageVersion Include="Pragmatic.Actions" Version="0.8.0-preview.*" />
<PackageVersion Include="Pragmatic.Endpoints" Version="0.8.0-preview.*" />
<PackageVersion Include="Pragmatic.Persistence.EFCore" Version="0.8.0-preview.*" />
<PackageVersion Include="Pragmatic.Validation" Version="0.8.0-preview.*" />
<PackageVersion Include="Pragmatic.SourceGenerator" Version="0.8.0-preview.*" />
</ItemGroup>
</Project>

Then each .csproj references without versions:

<ItemGroup>
<PackageReference Include="Pragmatic.Composition.Host" />
<PackageReference Include="Pragmatic.Actions" />
...
</ItemGroup>

To see what the generator produces:

<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)/Generated</CompilerGeneratedFilesOutputPath>
</PropertyGroup>

After dotnet build, you’ll find:

obj/Debug/net10.0/Generated/
└── Pragmatic.SourceGenerator/
└── Pragmatic.SourceGenerator.PragmaticSourceGenerator/
├── Greet.Invoker.g.cs
├── _Boundary.Default.Endpoints.g.cs
├── _Infra.Actions.Registration.g.cs
└── …

Read them. They are standard C#, formatted and commented.

Nothing is generated after dotnet build. You likely didn’t reference Pragmatic.SourceGenerator. It is the analyzer package — without it, no generation runs. Verify with dotnet list package | grep SourceGenerator.

IDE doesn’t see generated types. The IDE caches analyzer output. In Rider: File → Invalidate Caches / Clear Cache. In VS: restart. A full dotnet build almost always fixes it.

PRAG#### errors at build time. The generator detected a misuse. See the Diagnostics reference for the meaning and fix.

“Module A is active but I didn’t add it.” Pragmatic meta-packages bring transitive dependencies. Check dotnet list package --include-transitive. Pragmatic modules only activate if a marker type is reachable — if you see generation you didn’t ask for, a reference is pulling it in.

  • Architecture — how the 3-tier model fits together
  • Pick a module from the sidebar and read its Overview / Concepts
  • Showcase — full reference app composing 30+ modules
  • Samples — every module has a runnable samples/ project