Post-process an API response¶
ApiResponseHandler.SendAsync times the downstream pipeline call, resolves request-scoped services, and — for failed responses — rewrites the response body into a normalized ErrorResponse, while logging a trace entry (for both success and failure paths) when ILogService is resolved, and assigning correlation values (MessageID/MessageIDUrl) used in logs, traces, and the normalized error body.
| Field | Value |
|---|---|
| Operation ID | OP-01 |
| Trigger | Every HTTP request flowing through the Web API message-handler pipeline where ApiResponseHandler is registered. |
| Inputs | HttpRequestMessage request, CancellationToken cancellationToken, request-scoped ILogService/IEnvironmentService (may be null), ambient HttpContextBase |
| Outputs | HttpResponseMessage (original with Server-Name header, or rebuilt with ErrorResponse body) |
| Primary components | ApiResponseHandler.SendAsync, ApiResponseHandler.BuildApiResponse |
| Entry point | src/SAIF.Web.API/ApiResponseHandler.cs |
Steps¶
- Start a
Stopwatch. await base.SendAsync(request, cancellationToken)— run the rest of the pipeline.- Exceptions from downstream surface as
HttpErrorcontent, not as thrown exceptions, by Web API convention.* - Stop the stopwatch; capture
ElapsedMilliseconds. - Resolve
ILogServiceandIEnvironmentServicefromrequest.GetDependencyScope(). Either may benull; every downstream use is null-guarded. - Read
HttpContextBasefrom the"MS_HttpContext"request property; deriveuserIdfrom(httpContext?.User as WindowsPrincipal)?.Identity.UserId()andclientIPfromhttpContext?.Request?.UserHostAddress. All accesses are null-conditional; both values may benull. SAIF.Common.Extensions.UserId()is implemented inSAIF.Framework.Core; its internal steps could not be embedded — the package is not indexed for this run.**- Assign
MessageID = GenerateId()(BR-WEBAPI-005). - This step delegates to a locally-implemented operation.
Origin: Step 6 is implemented in Generate a log correlation identifier (
SAIF.Web.API) and executed on behalf of this operation. 7. AssignMessageIDUrl = BuildMessageURL(envService)(BR-WEBAPI-006). - This step delegates to a locally-implemented operation.Origin: Step 7 is implemented in Build the Splunk error-lookup URL (
SAIF.Web.API) and executed on behalf of this operation. 8. Ifresponse.TryGetContentValue(out content)and!response.IsSuccessStatusCodeand the content is anHttpError, enter error handling (steps 9–14); otherwise skip to step 15. - Non-HttpErrorfailure bodies pass through unchanged. 9. Null outcontent; computeexceptionMessage = GetExceptionMessage(error, request.GetConfiguration().IncludeErrorDetailPolicy)(BR-WEBAPI-010). - Iferror.StackTraceis null/whitespace and the policy is notAlways, returns the fixed developer-guidance text instead of the exception chain. 10. BuilderrorResponse = GetErrorResponse(response.StatusCode, error)(BR-WEBAPI-002, BR-WEBAPI-003). - This step delegates to a locally-implemented operation.
> **Origin**: Step 10 is implemented in [Map a Web API error to an ErrorResponse](op-04-map-a-web-api-error-to-an-errorresponse.md) (`SAIF.Web.API`) and executed on behalf of this operation.
- If
errorResponse.ErrorCode == 400, forceresponse.StatusCode = HttpStatusCode.BadRequest(BR-WEBAPI-004). - Rebuild the response via
GetHttpResponse(request, response, errorResponse):request.CreateResponse(response.StatusCode, errorResponse)and copy every header from the original response (BR-WEBAPI-002). - If
logService != nullandlogService is ILogServiceForApi, callLogException(applicationName, relativePath, traceMessage(debugMode:true), new Exception($"{error.Message} {error.ExceptionMessage}"), MessageID)(BR-WEBAPI-007, BR-WEBAPI-008).- Skipped when the resolved log service is not an API log service.
ILogServiceForApi.LogExceptionis implemented inSAIF.Framework.Core; its internal steps could not be embedded — the package is not indexed for this run.**
- If
logService != null, call the 4-argILogService.LogException(applicationName, relativePath, traceMessage(debugMode:true), exception)— unconditionally, in addition to step 13 (BR-WEBAPI-007, BR-WEBAPI-008).- Results in the error being logged twice when the service implements
ILogServiceForApi. ILogService.LogExceptionis implemented inSAIF.Framework.Core; its internal steps could not be embedded — the package is not indexed for this run.**
- Results in the error being logged twice when the service implements
-
If
logService != null, calllogService.TraceInfo(BuildTraceMessage(request, response, elapsedMS, userId, clientIP, exceptionMessage, debugMode: logService.IsThresholdAtDebug))(BR-WEBAPI-009, BR-WEBAPI-007).- Runs for success and failure responses.
- This step delegates to a locally-implemented operation.
Origin: Step 15 is implemented in Compose the trace and log message (
SAIF.Web.API) and executed on behalf of this operation. 16. Add response headerServer-Name: {Environment.MachineName}(BR-WEBAPI-001). 17. Return the response.
Additional notes:
- Step 9's guidance text is placed into the logged/traced exception text, not into the client payload.
- Steps 13–14 compute the logged path as request.RequestUri.AbsolutePath.Replace($"/{request.RequestUri.ApplicationName()}/", string.Empty) — the application segment is stripped from the absolute path.
- The SAIF.Framework.Core package operations referenced above (SAIF.Common.Extensions.UserId(), ILogService.LogException/TraceInfo, ILogServiceForApi.LogException) could not be embedded as sub-steps: the Legends CLI index does not contain the SAIF.Framework.Core 2.0.x package surface consumed here (only a same-named, unrelated BOF library was found), and the analysis's zero-result rule was applied and exhausted.**
Process flow¶
flowchart TD
Start([Request received]) --> Base["Run downstream pipeline<br>via base.SendAsync"]
Base --> Resolve["Resolve ILogService /<br>IEnvironmentService"]
Resolve --> Ids["Assign MessageID and<br>MessageIDUrl"]
Ids --> IsError{"Non-success HttpError<br>content?"}
IsError -->|No| Trace["TraceInfo — success path"]
IsError -->|Yes| Msg["GetExceptionMessage"]
Msg --> ErrResp["GetErrorResponse"]
ErrResp --> Force400{"ErrorCode == 400?"}
Force400 -->|Yes| SetStatus["response.StatusCode =<br>400"]
Force400 -->|No| Rebuild
SetStatus --> Rebuild["Rebuild response with<br>ErrorResponse body"]
Rebuild --> LogErr["Log exception, possibly<br>twice"]
LogErr --> Trace
Trace --> Header["Add Server-Name header"]
Header --> End([Return response])
Failure and edge-case behavior¶
- A downstream exception that is not surfaced as
HttpErrorcontent passes through the handler unchanged — no normalization occurs. ILogService/IEnvironmentServiceunavailability degrades gracefully here (unlike Authorize a request against SAIF roles, which fails closed).
Note: Statements marked with * were inferred from code structure, naming conventions, or contextual evidence where direct confirmation was not available in source artifacts. Statements marked with ** could not be confirmed; treat as unknown until verified.
Related documentation¶
Sources¶
src/SAIF.Web.API/ApiResponseHandler.cs