---
title: SAIF.Web.API.Clients architecture
summary: Architectural responsibilities, boundaries, and interaction patterns.
status: draft
owner: bendy
last_reviewed: 2026-09-15
library: SAIF.Web.API.Clients
reference_type: package
---

# SAIF.Web.API.Clients architecture

`SAIF.Web.API.Clients` is the client-side integration layer for the SAIF JSON Web API contract: it wraps RestSharp so a consuming application can invoke a SAIF Web API with uniform NTLM (or custom) authentication, caller-identity headers, retry-on-transient-failure, JSON serialization/deserialization, and translation of HTTP failures into typed SAIF exceptions. It depends on [SAIF.Web.API.Core](../saif-web-api-core/overview.md) for the shared `ApiResponse<T>` envelope and `SAIF.API.Exceptions.*` types, and has no reference back to the server-side `SAIF.Web.API` package. Its source files are also shared with the [SAIF.Web.API.Clients.35](../saif-web-api-clients-35/overview.md) sibling project, which compiles the same `.cs` files for a `net35` target.

## Responsibilities
- Provide a single client type, `JsonHttpClient`, exposing GET/POST/PUT/PATCH/DELETE verb methods that route every call through one shared HTTP execution funnel (`GetJsonResponse`).
- Authenticate outbound requests, defaulting to Windows NTLM (`RestSharp.Authenticators.NtlmAuthenticator`) when no `IAuthenticator` is supplied.
- Attach caller-identity headers (`Client-Name`, `Application-ID`, `Client-User`) from a caller-supplied `ClientInfo` on every request, and optionally forward the ambient ASP.NET user's identity via an `OnBehalfOfUser` header (`SetOnBehalfOfUserHeaders`).
- Retry transient transport/gateway failures (`RetryOnStatusCodes`, default `{0, 401, 502, 503}`) with a fixed delay before surfacing them as errors.
- Serialize request bodies and normalize response payloads (array, object, or scalar JSON shapes) into a uniform `T[]`/`T` result via `JsonSerializer` and `GetContentArray<T>`.
- Translate non-2xx/3xx HTTP responses into typed `SAIF.API.Exceptions.*` exceptions (`ExceptionHandler`), so consumers catch domain exceptions rather than transport or RestSharp types.
- Normalize a forwarded Windows identity to a bare account name (`HttpContextExtensions.DomainlessUser`).

## Component boundaries
- `JsonHttpClient`: the sole public entry point. Owns per-instance immutable target configuration (`_serviceBaseUri`, `_clientInfo`, `_authenticator`) and mutable behavioural switches (timeout override, date-time-zone handling, retry policy, success enforcement, on-behalf-of-user forwarding). Contains the single HTTP execution funnel (`GetJsonResponse`) through which every public verb method passes, so header injection, authentication, retry, and success-enforcement cannot be bypassed per call.
- `JsonSerializer`: an adapter implementing `RestSharp.Serializers.ISerializer` over `Newtonsoft.Json.JsonSerializer`, used to serialize request bodies with a configurable `DateTimeZoneHandling`.
- `ExceptionHandler`: a stateless static boundary component that guards response success and maps HTTP status codes and response content to specific `SAIF.API.Exceptions.*` subclasses (`NoMatchingDataException`, `UnauthorizedAccessException`, `InvalidDataException`, `InternalServerError`).
- `ClientInfo`: a plain, unvalidated DTO (`MachineName`, `ApplicationId`, `UserId`) supplied by the consuming application and copied into per-request headers.
- `JsonResponse` (internal): the transport-level response envelope produced by `GetJsonResponse` before it is projected into the public `ApiResponse<T>`.
- `HttpContextExtensions.DomainlessUser`: an extension method on `System.Web.HttpContext` that strips a `DOMAIN\` prefix or `@domain` suffix from the ambient user's identity name.
- `EnvironmentSetting`: a public, immutable key/value DTO with no consumer anywhere in the repository — dormant public surface, not part of the active component model.
- `SAIF.Web.API.Core` (external boundary, project reference): supplies `ApiResponse<T>` and the `SAIF.API.Exceptions.SAIFException` hierarchy that this library projects into and throws, respectively. This library does not own or modify those types.

## Interaction model
- A consuming application constructs a single `JsonHttpClient` per target service (base URI, `ClientInfo`, optional `IAuthenticator`/timeout), then calls one of the seven public verb methods (`GetResult<T>`, `GetResults<T>`, `GetResponse<T>`, `PostJson<T>`, `PutJson<T>`, `PatchJson<T>`, `DeleteJson<T>`).
- Every verb method delegates to the private `GetJsonResponse` funnel, which builds a new `RestSharp.RestClient` per call, assigns the authenticator, clears default content handlers (accepting only `application/json`), applies the configured timeout, serializes the request body via `JsonSerializer`, attaches identity headers, optionally attaches the `OnBehalfOfUser` header, executes the request synchronously (`client.Execute`, blocking, no `async`/`await`), and retries on a configured status-code set before evaluating success.
- On success, the transport-level `JsonResponse` is projected by `GetApiResponse<T>` into the public `ApiResponse<T>` envelope, using `GetContentArray<T>` to normalize the JSON payload shape (array, object, or scalar) into `T[]`.
- On failure (when `EnsureSuccessfulResponse` is `true`, the default), `ExceptionHandler.EnsureSuccessfulResponse` selects the best available error text and calls `ExceptionHandler.ToKnownError`, which maps the HTTP status to a `SAIF.Web.API.Core` exception type and throws it — the funnel never returns a failed result to the caller in this mode.
- All interaction is synchronous and stateless across calls beyond the immutable per-instance target configuration; there is no connection reuse, caching, or ambient mutable state maintained by the client between calls.
- When `SetOnBehalfOfUserHeaders` is enabled, the client couples itself to an ambient ASP.NET request pipeline (`System.Web.HttpContext.Current`) — a leaky dependency that assumes a web host and has no guard for a non-web caller (console/service/test), where it throws `NullReferenceException`.

## Architecture diagram
The following diagrams show the high-level component interactions within this library and its immediate dependency boundary, split by concern so each renders at a readable width.

The first diagram shows request construction and outbound transport:

```mermaid
flowchart TD
	App["Consumer application"] --> Client["JsonHttpClient"]
	Client --> Serializer["JsonSerializer<br>RestSharp.Serializers.<br>ISerializer)"]
	Serializer --> Newtonsoft["Newtonsoft.Json.<br>JsonSerializer"]
	Client --> Rest["RestSharp RestClient /<br>IAuthenticator"]
```

The second diagram shows the caller-identity context `JsonHttpClient` attaches to every request:

```mermaid
flowchart TD
	Client["JsonHttpClient"] --> Info["ClientInfo<br>(caller identity<br>headers)"]
	Client --> CtxExt["HttpContextExtensions<br>(ambient user, optional)"]
```

The third diagram shows response projection and failure translation:

```mermaid
flowchart TD
	Client["JsonHttpClient"] --> Handler["ExceptionHandler<br>(failure translation)"]
	Handler --> Exceptions["SAIF.API.Exceptions.*<br>(SAIF.Web.API.Core)"]
	Client --> JsonResp["JsonResponse (internal)"]
	JsonResp --> ApiResp["ApiResponse&lt;T&gt;<br>(SAIF.Web.API.Core)"]
```

## Sequence diagram
The following diagram shows the primary request/response sequence for a GET call through the shared HTTP execution funnel, including the retry path.

```mermaid
sequenceDiagram
	participant App as Consumer application
	participant Client as JsonHttpClient
	participant Rest as RestSharp RestClient
	participant API as SAIF Web API
	App->>Client: GetResult<T>(methodUriSegment)
	Client->>Client: GetJsonResponse (build client, add headers, serialize body)
	Client->>Rest: Execute(request)
	Rest->>API: HTTP request (NTLM or custom auth)
	API-->>Rest: HTTP response
	Rest-->>Client: IRestResponse
	alt status in RetryOnStatusCodes and retries remain
		Client->>Client: Thread.Sleep(RetryDelayInMs), retry
	else EnsureSuccessfulResponse and status is failure
		Client->>Client: ExceptionHandler.ToKnownError (throws SAIFException)
	else success
		Client->>Client: GetApiResponse / GetContentArray (normalize JSON shape)
		Client-->>App: T / ApiResponse<T>
	end
```

## Design constraints
- **No asynchronous API.** All HTTP execution is synchronous and blocking (`client.Execute`, `Thread.Sleep` for retry delay); there is no `async`/`await` surface, which constrains callers on frameworks that expect non-blocking I/O.
- **Single execution funnel, no bypass.** Every public verb method routes through `GetJsonResponse`, so header injection, authentication, retry, and success-enforcement are applied uniformly and cannot be selectively disabled per call — only globally via the client's public properties.
- **Not substitutable by interface.** `JsonHttpClient` defines no interface and has no virtual members, so it cannot be mocked or substituted without wrapping it; the only defined interface point is the injectable `IAuthenticator` and the optional custom `Newtonsoft.Json.JsonSerializer`.
- **Ambient web-context coupling.** `SetOnBehalfOfUserHeaders = true` binds the client to `System.Web.HttpContext.Current`, which is `null` outside an ASP.NET request — an unguarded dependency that limits reuse in console/service/test hosts.
- **Silent parse-failure masking.** `GetContentArray<T>` swallows deserialization exceptions: for a `string` target type it substitutes the exception message as if it were response data, and for any other target type it silently returns an empty result — callers cannot distinguish "no data" from "a parse failure" (flagged as a defect risk — see [BR-CLI-016](business-rules.md#br-cli-016-deserialization-failure-masking)).
- **No validation, logging, or caching.** The library performs no input validation of `methodUriSegment`, `serviceBaseUri`, `requestBody`, or `ClientInfo`; no authorization decisions; no rate limiting, circuit breaking, or response caching; and no logging or auditing of any kind (verified absence).
- **Duplicated source, separate assembly.** [SAIF.Web.API.Clients.35](../saif-web-api-clients-35/overview.md) compiles the same source files via file-linking against a different target framework (`net35`) and older package versions, and excludes `HttpContextExtensions.cs`. Edits to this library's source directly affect that sibling project even though it is analyzed and documented separately.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Dependencies](dependencies.md)
- [Operations and processes](operations-processes.md)
- [Business rules](business-rules.md)
- [API reference](api/index.md)
- [SAIF.Web.API.Core index](../saif-web-api-core/index.md)

## Sources
- `src/SAIF.Web.API.Clients/JsonHttpClient.cs`
- `src/SAIF.Web.API.Clients/ExceptionHandler.cs`
- `src/SAIF.Web.API.Clients/JsonSerializer.cs`
- `src/SAIF.Web.API.Clients/JsonResponse.cs`
- `src/SAIF.Web.API.Clients/ClientInfo.cs`
- `src/SAIF.Web.API.Clients/EnvironmentSetting.cs`
- `src/SAIF.Web.API.Clients/Extensions/HttpContextExtensions.cs`
- `src/SAIF.Web.API.Clients/SAIF.Web.API.Clients.csproj`
- `src/SAIF.Web.API.Clients.35/SAIF.Web.API.Clients.35.csproj`
- `src/SAIF.Web.API.Core/ApiResponse.cs`
- `src/SAIF.Web.API.Core/SAIFException.cs`
