Skip to content

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

  1. Start a Stopwatch.
  2. await base.SendAsync(request, cancellationToken) — run the rest of the pipeline.
  3. Exceptions from downstream surface as HttpError content, not as thrown exceptions, by Web API convention.*
  4. Stop the stopwatch; capture ElapsedMilliseconds.
  5. Resolve ILogService and IEnvironmentService from request.GetDependencyScope(). Either may be null; every downstream use is null-guarded.
  6. Read HttpContextBase from the "MS_HttpContext" request property; derive userId from (httpContext?.User as WindowsPrincipal)?.Identity.UserId() and clientIP from httpContext?.Request?.UserHostAddress. All accesses are null-conditional; both values may be null.
  7. SAIF.Common.Extensions.UserId() is implemented in SAIF.Framework.Core; its internal steps could not be embedded — the package is not indexed for this run.**
  8. Assign MessageID = GenerateId() (BR-WEBAPI-005).
  9. 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. Assign MessageIDUrl = 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. If response.TryGetContentValue(out content) and !response.IsSuccessStatusCode and the content is an HttpError, enter error handling (steps 9–14); otherwise skip to step 15. - Non-HttpError failure bodies pass through unchanged. 9. Null out content; compute exceptionMessage = GetExceptionMessage(error, request.GetConfiguration().IncludeErrorDetailPolicy) (BR-WEBAPI-010). - If error.StackTrace is null/whitespace and the policy is not Always, returns the fixed developer-guidance text instead of the exception chain. 10. Build errorResponse = 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.
  1. If errorResponse.ErrorCode == 400, force response.StatusCode = HttpStatusCode.BadRequest (BR-WEBAPI-004).
  2. Rebuild the response via GetHttpResponse(request, response, errorResponse): request.CreateResponse(response.StatusCode, errorResponse) and copy every header from the original response (BR-WEBAPI-002).
  3. If logService != null and logService is ILogServiceForApi, call LogException(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.LogException is implemented in SAIF.Framework.Core; its internal steps could not be embedded — the package is not indexed for this run.**
  4. If logService != null, call the 4-arg ILogService.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.LogException is implemented in SAIF.Framework.Core; its internal steps could not be embedded — the package is not indexed for this run.**
  5. If logService != null, call logService.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 header Server-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 HttpError content passes through the handler unchanged — no normalization occurs.
  • ILogService/IEnvironmentService unavailability 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.

Sources

  • src/SAIF.Web.API/ApiResponseHandler.cs