---
title: Execute request
summary: Build, authenticate, execute, and retry an outbound JSON HTTP request for one of the five supported verbs.
status: draft
owner: Framework
last_reviewed: 2026-10-02
library: SAIF.NetCore.HttpClients
reference_type: package
operation_id: OP-04
trigger: A consumer calls Delete, Get, Patch, Post, Put, or an async equivalent
---

# Execute request

This is the core request pipeline: validate initialization, build a RestSharp client and request from the instance's current state, execute the call, retry on configured failure statuses, and normalize the response into an `IHttpRequestResult`.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-04 |
| **Trigger** | A consumer calls `Delete`/`Get`/`Patch`/`Post`/`Put` or their `*Async` equivalents |
| **Inputs** | HTTP verb, resource path, optional JSON request body; current instance state (base URL, `ClientInfo`, authentication, headers, options) |
| **Outputs** | An `IHttpRequestResult` describing status, content, headers, response date, and any error information; also stored on `RequestResult` |
| **Primary components** | `HttpJsonClient.ValidateInitialization`, `HttpJsonClient.ExecuteRestCall`, `HttpJsonClient.CreateClient`, `HttpJsonClient.CreateRequest`, `HttpJsonClient.CreateRequestResult`, `HttpJsonClient.GetDateHeader`, `HttpJsonClient.PopulateRequestResultErrorContent` |
| **Entry point** | `src/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/HttpJsonClient/HttpJsonClient.cs` |

## Steps

1. Validate that both `ClientInfo` and the base URI are set.
   - On failure: throw `ClientNotInitializedException` before any network call ([BR-HTTP-01](../business-rules.md#br-http-01-client-initialization-required-before-any-request)).
2. Create the RestSharp `RestClient`: attach the configured authenticator (NTLM, if any), select the serializer based on `HttpClientOptions.Serializer`, and apply the stored timeout override when valid ([BR-HTTP-06](../business-rules.md#br-http-06-timeout-override-bounds), [BR-HTTP-10](../business-rules.md#br-http-10-ntlm-authentication%2C-explicit-or-current-identity), [BR-HTTP-11](../business-rules.md#br-http-11-serializer-selection)).
3. Create the RestSharp `RestRequest`: serialize the JSON body when provided, add the caller-identity headers (`Client-Name`, `Application-ID`, `Client-User`), add the `Authorization` header from `BearerToken` when non-empty, copy any custom `Headers`, then clear `Headers` unless `PersistHeadersAfterRequest` is true ([BR-HTTP-07](../business-rules.md#br-http-07-custom-headers-cleared-after-each-request-unless-persisted), [BR-HTTP-08](../business-rules.md#br-http-08-bearer-token-header), [BR-HTTP-12](../business-rules.md#br-http-12-caller-identity-headers-on-every-request)).
4. Execute the request synchronously through the RestSharp client (`client.Execute(request)`). `ExecuteRestCall` reads `response.StatusCode` immediately afterward, before any null check on the response, so a null response from RestSharp throws `NullReferenceException` at this point rather than continuing to step 5 or 6.
5. Check the response status against `RetryOnStatusCodes` (default 401, 502, 503):
   - If the status is retryable and retries remain (default 3 remaining, 4 attempts total): sleep `RetryDelayInMs` (default 1000 ms) with a blocking `Thread.Sleep`, then return to step 2 to rebuild the client and request from scratch ([BR-HTTP-04](../business-rules.md#br-http-04-retry-on-configured-response-status-codes), [BR-HTTP-05](../business-rules.md#br-http-05-delay-between-retry-attempts)).
   - If not retryable, or no retries remain, continue to step 6.
6. Build the `IHttpRequestResult` in `CreateRequestResult`:
   - `CreateRequestResult` itself contains a defensive check that would set `IsSuccessful = false` and content `"Rest response was null"` for a null RestSharp response ([BR-HTTP-13](../business-rules.md#br-http-13-null-response-handling)), but this branch is not reachable through the pipeline described in step 4, because the null response is dereferenced earlier.
   - For a non-null response, copy status code, content, response headers, and status description.
   - Parse `ResponseDate` from the `date` header using a case-insensitive `SingleOrDefault` match followed by `DateTime.TryParse`, leaving it null if the header is missing, is not a string value, or is unparseable; if more than one header named `date` is present, `SingleOrDefault` throws `InvalidOperationException` here instead of producing a null `ResponseDate` ([BR-HTTP-14](../business-rules.md#br-http-14-response-date-parsing)).
   - For an unsuccessful response, derive `ErrorMessage` in order: RestSharp's own message, then matching JSON error tokens, then raw content on parse failure, then a formatted status fallback ([BR-HTTP-15](../business-rules.md#br-http-15-error-message-derivation-order)).
     - If the status is in the 4xx range, also populate `ErrorException` ([BR-HTTP-17](../business-rules.md#br-http-17-error-exception-populated-only-for-4xx)).
7. Store the constructed result on the instance's `RequestResult` property and return it to the consumer.

Async verb methods wrap this synchronous pipeline in `Task.Run`, so the retry delay in step 5 blocks a thread-pool thread rather than awaiting asynchronously. The generic `*Async<T>` overloads cannot be called at all ([BR-HTTP-21](../business-rules.md#br-http-21-generic-async-verb-methods-are-disabled)).

## Process flow

The following diagram visualizes the request-build, execute, and retry pipeline, including the point where a null RestSharp response currently throws rather than reaching the defensive null-handling code.

```mermaid
flowchart TD
    Start(["Verb method called<br>e.g. Get/PostAsync"]) --> Validate[ValidateInitialization]
    Validate -->|"ClientInfo or baseUri null"| Err1(["ClientNotInitializedException"])
    Validate --> CreateClient["CreateClient:<br>authenticator, serializer, timeout"]
    CreateClient --> CreateRequest["CreateRequest:<br>body, identity headers,<br>bearer, custom headers, clear headers"]
    CreateRequest --> Exec["client.Execute(request)"]
    Exec --> StatusRead["Read response.StatusCode<br>(no null check first)"]
    StatusRead -->|"response is null"| Err2(["NullReferenceException"])
    StatusRead --> Retryable{"Status in RetryOnStatusCodes<br>and retries remain?"}
    Retryable -->|Yes| Sleep["Thread.Sleep(RetryDelayInMs)"] --> CreateClient
    Retryable -->|No| BuildResult["CreateRequestResult<br>(null-response branch unreachable here)"]
    BuildResult --> Store["Store on RequestResult<br>and return to consumer"]
```

## Failure and edge-case behavior

- A retryable status with default options discards custom headers (including Basic auth) on every retry, so a 401 retry against a Basic-only endpoint cannot succeed and only adds blocking latency.
- NTLM credentials and the bearer token are resent unchanged on every retry, so a genuine authentication failure produces up to 4 identical failed attempts.
- `ErrorException` is populated only for 4xx statuses; 5xx failures surface only through `ErrorMessage`/`Content`.
- A null RestSharp response throws `NullReferenceException` from the status-code read in step 4 rather than reaching `CreateRequestResult`'s defensive null handling ([BR-HTTP-13](../business-rules.md#br-http-13-null-response-handling)).

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

## Sources
- `src/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/SAIF.NetCore.HttpClients/HttpJsonClient/HttpJsonClient.cs`
