Pragmatic.Client
Compile-time typed API clients generated from Pragmatic manifests. Zero reflection, zero hand-written HTTP code, full Result<T, IError> integration.
⚠️ Known limitations (tracked for resolution before the beta)
manifest.assemblyrequires a dot: the boundary name is derived by splittingassemblyon.and taking the last segment."Showcase.Booking"→Booking, but"SampleBookingApi"→SampleBookingApi(whole string). Always author the manifest with a dotted assembly path.- Generated code is not
#nullable enable-clean: consumer projects with<TreatWarningsAsErrors>true</TreatWarningsAsErrors>need toNoWarnonCS8618 / CS8669 / CS8604 / CS8601until the SG emits explicit nullable directives.List<T>response types surface asobject: the response type resolver doesn’t walk collection generics through the manifest’s type table yet. Workaround: model collection responses as a DTO wrapper ({ Items: GuestDto[] }) until resolved.A runnable end-to-end demo lives in
samples/Pragmatic.Client.Samples.
The Problem
Section titled “The Problem”Consuming Pragmatic APIs from .NET clients (Blazor, MAUI, console apps, other services) requires:
- Writing boilerplate
HttpClientcalls for every endpoint - Manually deserializing responses and mapping errors
- Keeping client code in sync when the server API changes
- Handling ProblemDetails (RFC 7807) error responses consistently
This leads to fragile, duplicated code that drifts from the actual API contract over time.
The Solution
Section titled “The Solution”Pragmatic.Client uses a source generator that reads PragmaticManifest.json at compile time and generates:
| Generated artifact | Purpose |
|---|---|
I{Boundary}Client | Typed interface with Result<T, IError> return types |
{Boundary}HttpClient | Full HttpClient implementation with error mapping |
| Request DTOs | sealed record per endpoint with request body |
| Response DTOs | sealed record per entity/DTO in the manifest |
| Enums | Enum types from the manifest |
| Error types | IError-implementing records with typed error codes |
| DI registration | Add{Boundary}Client(baseUrl) extension method |
Two discovery modes ensure the manifest reaches the generator:
- Mode A (AdditionalFiles): drop a
manifest.jsonorPragmaticManifest.jsonin the project - Mode B (Assembly refs): reference the domain assembly with
Private="false"and the SG reads[PragmaticMetadata]attributes automatically
Mode A takes priority. Mode B enables zero-file-sync: just reference the server project and the client is always up to date.
Quick Start
Section titled “Quick Start”1. Install packages
Section titled “1. Install packages”<PackageReference Include="Pragmatic.Client" /><PackageReference Include="Pragmatic.Client.SourceGenerator" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />2. Add the manifest (Mode A)
Section titled “2. Add the manifest (Mode A)”Place a PragmaticManifest.json in your client project and register it as an additional file:
<ItemGroup> <AdditionalFiles Include="PragmaticManifest.json" /></ItemGroup>Or use Mode B by referencing the domain assembly directly:
<ProjectReference Include="..\Showcase.Booking\Showcase.Booking.csproj" Private="false" ReferenceOutputAssembly="true" />3. Register and use
Section titled “3. Register and use”services.AddBookingClient("https://api.example.com");
// Inject and callpublic class GuestPage(IBookingClient client){ public async Task LoadGuest(Guid id) { var result = await client.GetGuest(id);
if (result.IsSuccess) Console.WriteLine($"Guest: {result.Value.FirstName}"); else Console.WriteLine($"Error: {result.Error.Title}"); }}Features
Section titled “Features”- Compile-time generation — no runtime reflection, no dynamic proxies
- Result-based error handling — every method returns
Result<T, IError>orVoidResult<IError>, never throws - ProblemDetails error mapping — server errors are deserialized and matched to typed
IErrorrecords via error code switch - Typed error records with extensions — error-specific context (e.g.,
ConflictError.ConflictingId) flows through ProblemDetails extensions - Named HttpClient — uses
IHttpClientFactorypattern viaAddHttpClient<TInterface, TImpl> - Boundary filtering — generate clients for specific boundaries via
PragmaticClientBoundariesMSBuild property - Dual discovery — manifest from file (Mode A) or assembly attribute (Mode B)
- DTO generation — entities, DTOs, and enums from the manifest become local types in the client namespace
Configuration
Section titled “Configuration”Boundary Filtering
Section titled “Boundary Filtering”Generate clients only for specific boundaries:
<PropertyGroup> <PragmaticClientBoundaries>Booking;Billing</PragmaticClientBoundaries></PropertyGroup>This filters endpoints by operationId prefix, generating only matching clients.
Custom HttpClient Configuration
Section titled “Custom HttpClient Configuration”services.AddBookingClient("https://api.example.com", client =>{ client.DefaultRequestHeaders.Add("X-Api-Key", "my-key"); client.Timeout = TimeSpan.FromSeconds(30);});Packages
Section titled “Packages”| Package | Target | Description |
|---|---|---|
Pragmatic.Client | net10.0 | Runtime: ApiError, PragmaticClientException |
Pragmatic.Client.SourceGenerator | netstandard2.0 | Source generator: reads manifests, generates typed clients |
Dependencies
Section titled “Dependencies”| Package | Depends on |
|---|---|
Pragmatic.Client | Pragmatic.Abstractions, Pragmatic.Result |
Pragmatic.Client.SourceGenerator | System.Text.Json (compile-time only) |
Requirements
Section titled “Requirements”- .NET 10 (runtime)
- C# 14 / Roslyn 4.12+ (source generator)
- A
PragmaticManifest.json(Mode A) or domain assembly reference with[PragmaticMetadata](Mode B)
Project Structure
Section titled “Project Structure”Pragmatic.Client/├── src/│ ├── Pragmatic.Client/ # Runtime helpers│ │ ├── ApiError.cs # Generic ProblemDetails → IError record│ │ └── PragmaticClientException.cs # Exception for unmappable responses│ └── Pragmatic.Client.SourceGenerator/ # Compile-time generator│ └── PragmaticClientGenerator.cs # Manifest → typed client code└── tests/ └── Pragmatic.Client.Tests/ └── Generator/ # Snapshot tests for generated outputLicense
Section titled “License”Part of the Pragmatic.Design ecosystem — see Licensing. Pragmatic.Client is licensed under the PolyForm Small Business 1.0.0 license (free for small businesses; commercial license above the threshold).