Skip to content

Getting Started

This guide walks through creating a typed API client for a Pragmatic-based service.

  • .NET 10 SDK
  • A Pragmatic API server with a PragmaticManifest.json (generated automatically by the Pragmatic.Endpoints source generator)
Terminal window
dotnet new classlib -n MyApp.ApiClient

In MyApp.ApiClient.csproj:

<ItemGroup>
<PackageReference Include="Pragmatic.Client" />
<PackageReference Include="Pragmatic.Client.SourceGenerator"
OutputItemType="Analyzer"
ReferenceOutputAssembly="false" />
</ItemGroup>

Copy PragmaticManifest.json from the server project output and add it as an additional file:

<ItemGroup>
<AdditionalFiles Include="PragmaticManifest.json" />
</ItemGroup>

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.

Terminal window
dotnet build

After building, the source generator produces files under obj/. For a manifest with "assembly": "Showcase.Booking", you get:

  • IBookingClient.g.cs — typed interface
  • BookingHttpClient.g.cs — HTTP client implementation
  • CreateGuestRequest.g.cs — request DTO
  • GuestDto.g.cs — response DTO
  • GuestStatus.g.cs — enum
  • ConflictError.g.cs — typed error
  • BookingClientExtensions.g.cs — DI registration
// In your Blazor/MAUI/Console app
builder.Services.AddBookingClient("https://api.example.com");

With custom configuration:

builder.Services.AddBookingClient("https://api.example.com", client =>
{
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
});
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;
}
}
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"
};

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}");
}

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>
Program.cs
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;
}
}
MauiProgram.cs
builder.Services.AddBookingClient("https://api.myapp.com", client =>
{
client.Timeout = TimeSpan.FromSeconds(15);
});
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("..."));