Table of Contents

Request Model

The request model is the public-facing type consumers pass to IAddressValidationService<TRequest>. Extend AbstractAddressValidationRequest to inherit all standard address fields (AddressLines, CityOrTown, StateOrProvince, PostalCode, Country) and add only provider-specific properties.

public sealed class MyAddressValidationRequest : AbstractAddressValidationRequest
{
    public int? MaximumResults { get; set; }
}
Note

If the provider needs no fields beyond the standard address properties, leave the class body empty. Still define the subclass — the DI container needs it to distinguish your integration's service from others.

Provider API Contracts

Provider API contracts are internal sealed DTO classes that mirror the JSON payload sent to and received from the provider API. By convention these are named ApiRequest and ApiResponse and placed in a Contracts namespace.

internal sealed class ApiRequest
{
    public AddressPayload? Address { get; set; }

    internal sealed class AddressPayload
    {
        public string[]? Lines { get; set; }
        public string? City { get; set; }
        public string? State { get; set; }
        public string? PostalCode { get; set; }
        public string? CountryCode { get; set; }
    }
}

internal sealed partial class ApiResponse
{
    public ResultPayload? Result { get; set; }

    [CustomResponseDataProperty]
    public string? TransactionId { get; set; }

    public ErrorPayload[]? Errors { get; set; }

    internal sealed partial class ResultPayload
    {
        public string[]? AddressLines { get; set; }
        public string? City { get; set; }
        public string? State { get; set; }
        public string? PostalCode { get; set; }

        [CustomResponseDataProperty]
        public bool IsResidential { get; set; }

        [CustomResponseDataProperty]
        [JsonPropertyName("DPV")]
        public string? DeliveryPointValidation { get; set; }
    }

    internal sealed class ErrorPayload
    {
        public string? Code { get; set; }
        public string? Message { get; set; }
    }
}

When a provider returns errors on non-2xx responses using a different JSON shape than the success body, define a peer ApiErrorResponse class alongside ApiResponse:

internal sealed class ApiErrorResponse
{
    public ApiResponse.ErrorPayload[]? Errors { get; set; }
}

The Validation Client deserializes this type separately on failure and copies its Errors into an ApiResponse for the pipeline to process.

Note

Only add [JsonPropertyName] when the provider's JSON field name differs from what the serializer produces for the C# property name. Most properties do not need it. For example, if a field is named "DPV" in JSON but the C# property is DeliveryPointValidation, the attribute is required; when names already match, omit it.

Note

The Validation Client uses a source-generated JSON serializer context. These types are its input and output. List each DTO type used with JsonContent.Create(...) or ReadFromJsonAsync(...) in a [JsonSerializable(...)] attribute on the serializer context. This includes ApiErrorResponse, which the client deserializes independently on non-2xx responses — give it its own entry alongside ApiResponse.

Custom Response Data

[CustomResponseDataProperty] marks a property on the response contract. The marked property appears in IAddressValidationResponse.CustomResponseData (IReadOnlyDictionary<string, object?>). Consumers can inspect provider-specific fields — residency classification, delivery point data, transaction identifiers, and so on — without casting to a provider-specific type.

The attribute has two forms:

  • [CustomResponseDataProperty]: the C# property name is converted to camelCase and used as the dictionary key (TransactionId"transactionId").
  • [CustomResponseDataProperty("customKey")]: the supplied string is used as the dictionary key as-is.

When a property needs both a different JSON name and a place in CustomResponseData, apply both attributes:

[CustomResponseDataProperty]          // key: "deliveryPointValidation"
[JsonPropertyName("DPV")]             // deserializes from JSON field "DPV"
public string? DeliveryPointValidation { get; set; }

The source generator bundled in VisusIO.AddressValidation generates a GetCustomResponseData() method at compile time on any type that has at least one decorated property. No runtime reflection is involved. See the Response Mapper page for how to call it and assign CustomResponseData on the unified response.

Any type that has at least one [CustomResponseDataProperty]-decorated property must be declared partial. When the decorated type is a nested class, every enclosing type in the chain must also be partial so the generator can emit the GetCustomResponseData() method inside the correct nested class hierarchy. Types with no decorated properties (such as ErrorPayload above) do not need to be partial.