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 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 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 noIAuthenticatoris supplied. - Attach caller-identity headers (
Client-Name,Application-ID,Client-User) from a caller-suppliedClientInfoon every request, and optionally forward the ambient ASP.NET user's identity via anOnBehalfOfUserheader (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[]/Tresult viaJsonSerializerandGetContentArray<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 implementingRestSharp.Serializers.ISerializeroverNewtonsoft.Json.JsonSerializer, used to serialize request bodies with a configurableDateTimeZoneHandling.ExceptionHandler: a stateless static boundary component that guards response success and maps HTTP status codes and response content to specificSAIF.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 byGetJsonResponsebefore it is projected into the publicApiResponse<T>.HttpContextExtensions.DomainlessUser: an extension method onSystem.Web.HttpContextthat strips aDOMAIN\prefix or@domainsuffix 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): suppliesApiResponse<T>and theSAIF.API.Exceptions.SAIFExceptionhierarchy 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
JsonHttpClientper target service (base URI,ClientInfo, optionalIAuthenticator/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
GetJsonResponsefunnel, which builds a newRestSharp.RestClientper call, assigns the authenticator, clears default content handlers (accepting onlyapplication/json), applies the configured timeout, serializes the request body viaJsonSerializer, attaches identity headers, optionally attaches theOnBehalfOfUserheader, executes the request synchronously (client.Execute, blocking, noasync/await), and retries on a configured status-code set before evaluating success. - On success, the transport-level
JsonResponseis projected byGetApiResponse<T>into the publicApiResponse<T>envelope, usingGetContentArray<T>to normalize the JSON payload shape (array, object, or scalar) intoT[]. - On failure (when
EnsureSuccessfulResponseistrue, the default),ExceptionHandler.EnsureSuccessfulResponseselects the best available error text and callsExceptionHandler.ToKnownError, which maps the HTTP status to aSAIF.Web.API.Coreexception 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
SetOnBehalfOfUserHeadersis 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 throwsNullReferenceException.
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:
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:
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:
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<T><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.
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.Sleepfor retry delay); there is noasync/awaitsurface, 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.
JsonHttpClientdefines no interface and has no virtual members, so it cannot be mocked or substituted without wrapping it; the only defined interface point is the injectableIAuthenticatorand the optional customNewtonsoft.Json.JsonSerializer. - Ambient web-context coupling.
SetOnBehalfOfUserHeaders = truebinds the client toSystem.Web.HttpContext.Current, which isnulloutside 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 astringtarget 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). - No validation, logging, or caching. The library performs no input validation of
methodUriSegment,serviceBaseUri,requestBody, orClientInfo; 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 compiles the same source files via file-linking against a different target framework (
net35) and older package versions, and excludesHttpContextExtensions.cs. Edits to this library's source directly affect that sibling project even though it is analyzed and documented separately.
Related documentation¶
- Index
- Overview
- Dependencies
- Operations and processes
- Business rules
- API reference
- SAIF.Web.API.Core index
Sources¶
src/SAIF.Web.API.Clients/JsonHttpClient.cssrc/SAIF.Web.API.Clients/ExceptionHandler.cssrc/SAIF.Web.API.Clients/JsonSerializer.cssrc/SAIF.Web.API.Clients/JsonResponse.cssrc/SAIF.Web.API.Clients/ClientInfo.cssrc/SAIF.Web.API.Clients/EnvironmentSetting.cssrc/SAIF.Web.API.Clients/Extensions/HttpContextExtensions.cssrc/SAIF.Web.API.Clients/SAIF.Web.API.Clients.csprojsrc/SAIF.Web.API.Clients.35/SAIF.Web.API.Clients.35.csprojsrc/SAIF.Web.API.Core/ApiResponse.cssrc/SAIF.Web.API.Core/SAIFException.cs