Getting Started
This guide walks through creating a typed API client for a Pragmatic-based service.
Prerequisites
Section titled “Prerequisites”- .NET 10 SDK
- A Pragmatic API server with a
PragmaticManifest.json(generated automatically by the Pragmatic.Endpoints source generator)
Step 1: Create the Client Project
Section titled “Step 1: Create the Client Project”dotnet new classlib -n MyApp.ApiClientStep 2: Add Package References
Section titled “Step 2: Add Package References”In MyApp.ApiClient.csproj:
<ItemGroup> <PackageReference Include="Pragmatic.Client" /> <PackageReference Include="Pragmatic.Client.SourceGenerator" OutputItemType="Analyzer" ReferenceOutputAssembly="false" /></ItemGroup>Step 3: Provide the Manifest
Section titled “Step 3: Provide the Manifest”Option A: Copy the Manifest File
Section titled “Option A: Copy the Manifest File”Copy PragmaticManifest.json from the server project output and add it as an additional file:
<ItemGroup> <AdditionalFiles Include="PragmaticManifest.json" /></ItemGroup>Option B: Reference the Server Assembly
Section titled “Option B: Reference the Server Assembly”If both projects are in the same solution, reference the server assembly directly:
<ProjectReference Include="..\Showcase.Booking\Showcase.Booking.csproj" Private="false" ReferenceOutputAssembly="true" />Private="false" ensures the server DLL is only used at compile time and is not copied to the client output.
Step 4: Build and Inspect
Section titled “Step 4: Build and Inspect”dotnet buildAfter building, the source generator produces files under obj/. For a manifest with "assembly": "Showcase.Booking", you get:
IBookingClient.g.cs— typed interfaceBookingHttpClient.g.cs— HTTP client implementationCreateGuestRequest.g.cs— request DTOGuestDto.g.cs— response DTOGuestStatus.g.cs— enumConflictError.g.cs— typed errorBookingClientExtensions.g.cs— DI registration
Step 5: Register in DI
Section titled “Step 5: Register in DI”// In your Blazor/MAUI/Console appbuilder.Services.AddBookingClient("https://api.example.com");With custom configuration:
builder.Services.AddBookingClient("https://api.example.com", client =>{ client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);});Step 6: Inject and Use
Section titled “Step 6: Inject and Use”public class GuestService(IBookingClient client){ public async Task<GuestDto?> GetGuestOrNull(Guid id, CancellationToken ct = default) { var result = await client.GetGuest(id, ct); return result.IsSuccess ? result.Value : null; }
public async Task<Guid> CreateGuest(string firstName, string lastName, CancellationToken ct = default) { var request = new CreateGuestRequest { FirstName = firstName, LastName = lastName };
var result = await client.CreateGuest(request, ct);
if (result.IsSuccess) return result.Value;
// Typed error matching if (result.Error is ConflictError conflict) throw new InvalidOperationException($"Guest already exists: {conflict.Code}");
throw new InvalidOperationException($"API error: {result.Error.Title}"); }
public async Task<bool> DeleteGuest(Guid id, CancellationToken ct = default) { var result = await client.DeleteGuest(id, ct); return result.IsSuccess; }}Step 7: Error Handling Patterns
Section titled “Step 7: Error Handling Patterns”Pattern matching on typed errors
Section titled “Pattern matching on typed errors”var result = await client.CreateGuest(request);
var message = result.Error switch{ ConflictError => "A guest with this email already exists.", ApiError { StatusCode: 401 } => "You are not authorized.", ApiError { StatusCode: 403 } => "You do not have permission.", ApiError api => $"Unexpected error: {api.Title} ({api.Code})", _ => "Unknown error"};Fallback to ApiError
Section titled “Fallback to ApiError”When the server returns a ProblemDetails response with an unrecognized error code, the client deserializes it into ApiError:
if (result.Error is ApiError apiError){ Console.WriteLine($"Code: {apiError.Code}"); Console.WriteLine($"Status: {apiError.StatusCode}"); Console.WriteLine($"Title: {apiError.Title}"); Console.WriteLine($"Detail: {apiError.Detail}");
// Access raw ProblemDetails extensions if (apiError.Extensions?.TryGetValue("traceId", out var traceId) == true) Console.WriteLine($"Trace: {traceId}");}Step 8: Filtering by Boundary (Optional)
Section titled “Step 8: Filtering by Boundary (Optional)”If the manifest contains endpoints from multiple boundaries and you only need some:
<PropertyGroup> <PragmaticClientBoundaries>Booking</PragmaticClientBoundaries></PropertyGroup>Multiple boundaries separated by ; or ,:
<PragmaticClientBoundaries>Booking;Billing</PragmaticClientBoundaries>Using with Blazor WebAssembly
Section titled “Using with Blazor WebAssembly”var builder = WebAssemblyHostBuilder.CreateDefault(args);builder.Services.AddBookingClient(builder.HostEnvironment.BaseAddress);
await builder.Build().RunAsync();@* GuestList.razor *@@inject IBookingClient BookingClient
@code { private GuestDto? _guest;
protected override async Task OnInitializedAsync() { var result = await BookingClient.GetGuest(GuestId); if (result.IsSuccess) _guest = result.Value; }}Using with MAUI
Section titled “Using with MAUI”builder.Services.AddBookingClient("https://api.myapp.com", client =>{ client.Timeout = TimeSpan.FromSeconds(15);});Using with Console / Background Services
Section titled “Using with Console / Background Services”var services = new ServiceCollection();services.AddBookingClient("https://api.example.com");
var provider = services.BuildServiceProvider();var client = provider.GetRequiredService<IBookingClient>();
var result = await client.GetGuest(Guid.Parse("..."));