Sample probe pages

How to build and maintain Blazor sample pages that validate SDK wire-to-model mapping against the live FreeAgent API.

Audience: contributors implementing or retrofitting SDK endpoints.

Purpose

The sample app is a developer workbench, not a product UI. Each probe page should answer:

  1. Does the SDK call succeed against a real sandbox account?
  2. Does every model property map correctly to the wire JSON FreeAgent returns?
  3. Can a contributor spot mismatches, missing fields, and read-only attributes quickly?

Reference implementations:

Pattern Sample routes SDK surface
Single-resource GET /company CompanyService.GetCompanyAsync()
List + per-row mapping (paginated) /contacts ContactService.ListAsync(...)
List + per-row mapping (full collection) /categories CategoryService.ListAsync(...)
CRUD + seed data /contacts/detail ContactService create/get/update/delete
CRUD + write-variant selector /categories/detail CategoryService documented create/update variants

Read those pages before adding a new resource.

Shared building blocks

Reuse these sample-only components and services. Do not duplicate probe logic on new pages.

Asset Location Role
EndpointProbeHeader Components/Shared/EndpointProbeHeader.razor Page title, call under test, API docs link, environment, endpoint path
ModelProbeResults Components/Shared/ModelProbeResults.razor Mapping summary chips, mapping table, all-fields table, raw JSON
ModelWireDiagnostics Services/ModelWireDiagnostics.cs Builds ModelProbeSnapshot with SDK-aware equivalence checks
ApiDiagnosticsService Services/ApiDiagnosticsService.cs Fetches raw wire JSON for a path after an SDK call
ApiErrorDiagnostics Components/Shared/ApiErrorDiagnostics.razor Shows raw error payload on API failures

Wire-to-model comparison rules

ModelWireDiagnostics compares wire JSON to the deserialised SDK model by:

  1. Deserialising each wire field through the same property type the model uses.
  2. Using JsonNumberHandling.AllowReadingFromString so decimal fields returned as strings (for example account_balance) do not false-positive.
  3. Classifying each property as Match, Mismatch, Missing in model, Model-only, or Not returned.

Only add model properties that exist on the FreeAgent API docs. Do not invent wire fields (for example contact_name is not part of the contacts API).

Page patterns

Single GET (one resource)

Follow Components/Pages/Company.razor:

  1. Render EndpointProbeHeader with the SDK method, /v2/... path, and DocsUrl pointing at the official FreeAgent docs page for the resource.
  2. On load (or button click), call the SDK service.
  3. Fetch the matching raw payload via ApiDiagnosticsService.
  4. Build ModelProbeResults with ModelWireDiagnostics.Build(model, rawPayload, envelopeProperty).
  5. Show ApiErrorDiagnostics when the call fails.

List GET (collection)

Follow Components/Pages/Contacts.razor when the API paginates, or Components/Pages/Categories.razor when it returns a complete collection:

  1. Load through the SDK (ListAsync for paginated or full-collection lists; ListAutoPagingAsync when probing auto-pagination).
  2. Fetch the list wire payload for the same query.
  3. For each row, offer Inspect mapping using ModelWireDiagnostics.TryGetArrayItem (or the matching wire array for grouped collections) to pass the array item element into ModelProbeResults.
  4. Expose filters the SDK supports (view, sort, updated_since, sub_accounts, and so on).
  5. Link to a detail/CRUD page with a documented identifier (?id= or ?nominal_code=).

CRUD (create, read, update, delete)

Follow Components/Pages/ContactDetail.razor for a single create/update shape, or Components/Pages/CategoryDetail.razor when the SDK exposes multiple write variants.

  1. Support ?id= deep links for get/update/delete.
  2. After create or update, fetch GET /resource/:id wire JSON so diagnostics reflect persisted state.
  3. Show MudProgressLinear while _loading is true (bulk seed operations can take several seconds).
  4. Disable action buttons during in-flight requests.

When the SDK exposes multiple write variants for the same resource (for example CreateIncomeCategoryAsync and CreateCostOfSalesCategoryAsync), the probe page must be able to invoke each public write method. A variant selector on one CRUD page is sufficient; exercising only one variant is not adequate as the standard.

Optional seed helpers (when useful for testing):

  • Narrative demo data - Turpinverse canon is loaded on demand from the upstream Turpinverse repository (canon/ on GitHub; configure via Turpinverse:Repository and Turpinverse:CanonRef in the sample app). Each seed operation refreshes canon from GitHub before upserting contacts. No Turpinverse canon is bundled in this repository; sample seed buttons require network access to GitHub at runtime. Seed helpers live in samples/FreeAgent.Client.BlazorSample/Services/Turpinverse/. FreeAgent contacts are B2B company records: seed one contact per Turpinverse organisation, with the organisation tradingName and the primary contact persona’s name, email, and phone. Contact address fields are billing-oriented (they appear on invoices), so Turpinverse maps each organisation’s registeredOffice - not persona home addresses. If you previously seeded persona-based demo contacts, re-run organisation sync to update overlapping records by email; leftover persona-only contacts are not removed automatically. Load additional canon files (for example invoices.json) as SDK endpoints are implemented.
  • Full-field probe fixture - one contact with every writable attribute populated (SampleContactFixtures, SampleContactSeeder).
  • Upsert by stable key - match existing records by email (contacts) or an equivalent natural key; update when canon changes (ContactSeederSupport).
  • Register new routes in Components/Layout/MainLayout.razor.
  • Add the page to the table in samples/README.md.
  • Do not add sample UI for SDK endpoints that do not exist yet (see ADR-0006).

Checklist for a new resource

  • SDK models, service, and tests land in the same PR as the sample pages.
  • List probe page (if the API supports listing) with per-row mapping inspection.
  • Detail or CRUD probe page (if the API supports get/create/update/delete).
  • When multiple write variants exist, sample can invoke each SDK write method (variant selector or equivalent).
  • EndpointProbeHeader on each page with accurate CallUnderTest, EndpointPath, and DocsUrl.
  • ModelProbeResults after successful SDK calls; ApiErrorDiagnostics on failures.
  • Raw JSON panel present for full payload inspection.
  • Navigation group added in MainLayout.razor.
  • docs/reference/api-coverage.md and samples/README.md updated.

Copyright © Mark Heydon. Distributed under the MIT licence.

This site uses Just the Docs, a documentation theme for Jekyll.