Validation Client
The validation client is a typed HttpClient. It calls the provider's address validation endpoint and returns the API response — nothing else.
Below is an example of a validation client that sends a POST request to the provider API and deserializes the result:
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by DI container")]
internal sealed class MyAddressValidationClient
{
private readonly HttpClient _httpClient;
private readonly IOptions<MyServiceOptions> _options;
public MyAddressValidationClient(HttpClient httpClient, IOptions<MyServiceOptions> options)
{
_httpClient = httpClient ?? throw new ArgumentNullException(nameof(httpClient));
_options = options ?? throw new ArgumentNullException(nameof(options));
}
public Task<ApiResponse?> ValidateAddressAsync(ApiRequest request, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(request);
return ValidateAddressInternalAsync(request, cancellationToken);
}
private async Task<ApiResponse?> ValidateAddressInternalAsync(ApiRequest request, CancellationToken cancellationToken)
{
Uri requestUri = new(_options.Value.EndpointUri, "/v1/address/validate");
using HttpRequestMessage httpRequest = new(HttpMethod.Post, requestUri);
httpRequest.Content = JsonContent.Create(request, MyApiRequestJsonSerializerContext.Default.ApiRequest);
using HttpResponseMessage response = await _httpClient.SendAsync(httpRequest, cancellationToken).ConfigureAwait(false);
if ( response.IsSuccessStatusCode )
{
return await response.Content.ReadFromJsonAsync(MyApiResponseJsonSerializerContext.Default.ApiResponse, cancellationToken)
.ConfigureAwait(false);
}
ApiErrorResponse? errorResponse = await response.Content.ReadFromJsonAsync(MyApiResponseJsonSerializerContext.Default.ApiErrorResponse, cancellationToken)
.ConfigureAwait(false);
if ( errorResponse is not null )
{
return new ApiResponse { Errors = errorResponse.Errors };
}
return null;
}
}
Note
The JsonContent.Create(...) and ReadFromJsonAsync(...) calls reference source-generated JSON serializer contexts. Create one internal sealed partial class per DTO direction, annotated with [JsonSerializable(...)]:
[JsonSerializable(typeof(ApiRequest))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
internal sealed partial class MyApiRequestJsonSerializerContext : JsonSerializerContext { }
[JsonSerializable(typeof(ApiResponse))]
[JsonSerializable(typeof(ApiErrorResponse))]
[JsonSourceGenerationOptions(PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
internal sealed partial class MyApiResponseJsonSerializerContext : JsonSerializerContext { }
Source-generated contexts are required for correct behavior in trimmed and native AOT deployments.
Note
Unlike the Authentication Client, the validation client does not implement an interface. It is referenced directly by its concrete type from the request adapter.
Important
Redact the Authorization header from HTTP logs. Configure this during service registration via RedactLoggedHeaders(["Authorization"]) on the IHttpClientBuilder — there is nothing to configure in the client itself. Without this configuration, bearer tokens appear in structured logs.
Note
Mark the validation client internal. This reduces the public API surface if you redistribute the integration as a library.
Request Adapter
The request adapter bridges the public request to the validation client. It maps the incoming request model to the provider DTO using the request mapper, then forwards it to the typed HTTP client. Implement IApiRequestAdapter<TRequest, TApiResponse>.
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by DI container")]
internal sealed class ApiRequestAdapter : IApiRequestAdapter<MyAddressValidationRequest, ApiResponse>
{
private readonly MyAddressValidationClient _client;
private readonly IApiRequestMapper<MyAddressValidationRequest, ApiRequest> _requestMapper;
public ApiRequestAdapter(MyAddressValidationClient client,
IApiRequestMapper<MyAddressValidationRequest, ApiRequest> requestMapper)
{
_client = client ?? throw new ArgumentNullException(nameof(client));
_requestMapper = requestMapper ?? throw new ArgumentNullException(nameof(requestMapper));
}
public Task<ApiResponse?> ExecuteAsync(MyAddressValidationRequest request, CancellationToken cancellationToken)
{
ApiRequest apiRequest = _requestMapper.Map(request);
return _client.ValidateAddressAsync(apiRequest, cancellationToken);
}
}
Note
Mark the request adapter internal. This reduces the public API surface if you redistribute the integration as a library.