---
title: SAIF.Services.Web.Proxies business rules
summary: Enumerated business rules with per-rule trigger, enforcement, and outcomes.
status: draft
owner: SAIF.Core Documentation Team
last_reviewed: 2026-09-24
library: SAIF.Services.Web.Proxies
reference_type: package
---

# SAIF.Services.Web.Proxies business rules

This page captures the 25 business rules enforced by this library. All rules originate in SAIF.Services.Web.Proxies; none is inherited from a dependency library.

## WP-BR-001 Required arguments must be non-null or non-empty

| Field | Value |
| --- | --- |
| **Statement** | A service URL, and each required string argument, must be non-null and non-empty (or non-whitespace). The arguments checked are `userId` in `GetDefaultPrinter`, `package`/`xml` in `PreviewPdf`, and `database` in `PdfServiceProxy`. |
| **Trigger** | Construction of any wrapper, or a call to a method that requires a string argument |
| **Enforcement** | Each wrapper's constructor; `ActiveDirectoryServiceProxy.GetDefaultPrinter`; `DocumentServiceProxy.PreviewPdf`; `PdfServiceProxy..ctor`, using `Enforce` from SAIF.Framework.Core* |
| **Outcome** | An argument exception is thrown |
| **Source** | `Services/SAIF.Services.Web.Proxies/ActiveDirectoryServiceProxy.cs`, `Services/SAIF.Services.Web.Proxies/DocumentServiceProxy.cs`, `Services/SAIF.Services.Web.Proxies/PDFServiceProxy.cs` |

---

## WP-BR-002 Access check selects internal or external cache

| Field | Value |
| --- | --- |
| **Statement** | An access check uses the external-domain cached operation when `useExternalDomain` is true, and the internal cached operation otherwise. |
| **Trigger** | A call to `HasAccess` |
| **Enforcement** | `ActiveDirectoryServiceProxy.HasAccess` |
| **Outcome** | `HasAccessCacheExternal` or `HasAccessCache` is invoked; the boolean result is returned unchanged |
| **Source** | `Services/SAIF.Services.Web.Proxies/ActiveDirectoryServiceProxy.cs` |

---

## WP-BR-003 Null AD attributes become empty strings

| Field | Value |
| --- | --- |
| **Statement** | Null Active Directory user attributes are presented as empty strings in the resulting `Contact`. |
| **Trigger** | A call to `GetContact` |
| **Enforcement** | `ActiveDirectoryServiceProxy.GetContact` |
| **Outcome** | Empty strings replace null fields; there is no null guard on `userId`, and a null `userInfo` throws `NullReferenceException` |
| **Source** | `Services/SAIF.Services.Web.Proxies/ActiveDirectoryServiceProxy.cs` |

---

## WP-BR-004 Document calls identify as xPression Design

| Field | Value |
| --- | --- |
| **Statement** | Document service calls identify the application as `"xPression Design"`. DocCenter distribution uses `docConfig.ApplicationName` when it is set, falling back to `"xPression Design"`. |
| **Trigger** | Any call to `PreviewPdf`, `DistributeDocument`, `DistributeDocumentDocCenter`, or `PrintDocument` |
| **Enforcement** | `DocumentServiceProxy.ApplicationName` (const); `DistributeDocumentDocCenter` |
| **Outcome** | The application name is sent to the wire operation |
| **Source** | `Services/SAIF.Services.Web.Proxies/DocumentServiceProxy.cs` |

---

## WP-BR-005 Document categories and packages are sorted ascending

| Field | Value |
| --- | --- |
| **Statement** | Document categories and packages are returned sorted ascending. |
| **Trigger** | A call to `GetCategories` or `GetPackages` |
| **Enforcement** | `DocumentServiceProxy.GetCategories` / `DocumentServiceProxy.GetPackages` |
| **Outcome** | The result array is sorted ascending before it is returned |
| **Source** | `Services/SAIF.Services.Web.Proxies/DocumentServiceProxy.cs` |

---

## WP-BR-006 Plex scale and paper tray enums map by name

| Field | Value |
| --- | --- |
| **Statement** | Plex, scale, and paper-tray enums are mapped by name between the SAIF.Core and wire enums. An unmatched name fails at run time. |
| **Trigger** | A distribute or print call |
| **Enforcement** | `Helpers.MapEnumByName`, called from `DocumentServiceProxy` |
| **Outcome** | The mapped wire enum is returned, or an `ArgumentNullException` is thrown for an unmatched name |
| **Source** | `Services/SAIF.Services.Web.Proxies/Helpers.cs`, `Services/SAIF.Services.Web.Proxies/DocumentServiceProxy.cs` |

---

## WP-BR-007 Email CC and BCC are sent only when present

| Field | Value |
| --- | --- |
| **Statement** | Email CC and BCC are sent only when present. A null `MailMessage` means no email is sent. |
| **Trigger** | A distribute call |
| **Enforcement** | `DocumentServiceProxy.MapMailMessage` |
| **Outcome** | An `EmailInfoClass` is produced, or null when the source message is null |
| **Source** | `Services/SAIF.Services.Web.Proxies/DocumentServiceProxy.cs` |

---

## WP-BR-008 Standard distribution never sends fax info

| Field | Value |
| --- | --- |
| **Statement** | Standard document distribution never sends fax info; its `faxInfo` argument is ignored (suppressed via CA1801). |
| **Trigger** | A call to `DistributeDocument` |
| **Enforcement** | `DocumentServiceProxy.DistributeDocument` |
| **Outcome** | The fax parameter sent to the wire operation is always null |
| **Source** | `Services/SAIF.Services.Web.Proxies/DocumentServiceProxy.cs` |

---

## WP-BR-009 DocCenter distribution sends fax info only when present

| Field | Value |
| --- | --- |
| **Statement** | DocCenter document distribution sends fax info only when a destination fax number is present. |
| **Trigger** | A call to `DistributeDocumentDocCenter` |
| **Enforcement** | `DocumentServiceProxy.MapFaxMessage` |
| **Outcome** | A wire `FaxInfoClass` is produced only when `ToFaxNumber` is non-empty; a null `faxInfo` argument throws `NullReferenceException` because `MapFaxMessage` dereferences it without a guard |
| **Source** | `Services/SAIF.Services.Web.Proxies/DocumentServiceProxy.cs` |

---

## WP-BR-010 Fax calls use Windows transport credentials

| Field | Value |
| --- | --- |
| **Statement** | Fax calls authenticate with the caller's Windows credentials over `TransportCredentialOnly`. |
| **Trigger** | Construction of `FaxServiceProxy` |
| **Enforcement** | `FaxServiceProxy..ctor` |
| **Outcome** | The generated client uses a `BasicHttpSecurityMode.TransportCredentialOnly` binding with `HttpClientCredentialType.Windows` |
| **Source** | `Services/SAIF.Services.Web.Proxies/FaxServiceProxy.cs` |

---

## WP-BR-011 PDF image retrieval requests immediate caching with retry

| Field | Value |
| --- | --- |
| **Statement** | PDF image retrieval asks for immediate caching (priority 10000) under data user `"IMG"`, and waits up to *n* one-second retries (default 10). |
| **Trigger** | A call to `GetImage` |
| **Enforcement** | `PdfServiceProxy.GetImage` |
| **Outcome** | Image bytes are returned once cached; a null `result` from the wire call throws |
| **Source** | `Services/SAIF.Services.Web.Proxies/PDFServiceProxy.cs` |

---

## WP-BR-012 Connection string requests carry the process user and pool code

| Field | Value |
| --- | --- |
| **Statement** | Connection-string requests carry the process OS user (`Environment.UserName`) and the caller's SAIF pool code, not an Oracle schema. |
| **Trigger** | A call to `GetOracleConnectionString` or `GetSqlServerConnectionString` |
| **Enforcement** | `Legacy.DbConnectionServiceProxy.GetOracleConnectionString` / `Legacy.DbConnectionServiceProxy.GetSqlServerConnectionString` |
| **Outcome** | The `ConnectRequestInfo` request carries `OSUserid = Environment.UserName` and `Code = userName` |
| **Source** | `Services/SAIF.Services.Web.Proxies/Legacy/DbConnectionsServiceProxy.cs` |

---

## WP-BR-013 Oracle connection strings decode the ZSPECIALCHAR token

| Field | Value |
| --- | --- |
| **Statement** | The token `ZSPECIALCHAR` in a returned Oracle connection string is replaced with the BEL character `(char)7`. |
| **Trigger** | An Oracle connection string is returned by `GetOracleConnectionString` |
| **Enforcement** | `Legacy.DbConnectionServiceProxy.GetOracleConnectionString` |
| **Outcome** | The decoded connection string is returned to the caller |
| **Source** | `Services/SAIF.Services.Web.Proxies/Legacy/DbConnectionsServiceProxy.cs` |

---

## WP-BR-014 SimpleCertifiedMail credentials default from configuration

| Field | Value |
| --- | --- |
| **Statement** | The SimpleCertifiedMail service URL comes from configuration. Credentials (`UserName`, `Password`, `ClientCode`, `PartnerKey`) come from configuration unless the caller supplies them. |
| **Trigger** | Construction, or a label request |
| **Enforcement** | `SimpleCertifiedMailServiceProxy..ctor` / `SimpleCertifiedMailServiceProxy.GetCertifiedLabel`, via `IEnvironmentService.GetConfigSetting`* |
| **Outcome** | The URL and any caller-omitted credential fields are populated from configuration (see [Configuration](configuration.md#secure-settings)) |
| **Source** | `Services/SAIF.Services.Web.Proxies/SimpleCertifiedMailServiceProxy.cs` |

---

## WP-BR-015 Default label size is set by page count

| Field | Value |
| --- | --- |
| **Statement** | The default certified label size is set by page count: 10 or fewer pages gives `EnvelopeSize10`, 11–19 gives `Small`, 20–76 gives `Large`, and more than 76 is rejected. The default group is `"ADMINISTRATION"`, format GIF, with no sender notification. |
| **Trigger** | A call to `GetDefaultLabelData` |
| **Enforcement** | `SimpleCertifiedMailServiceProxy.GetDefaultLabelData` |
| **Outcome** | Populated label data, or a thrown `InvalidNumberOfPagesException` for more than 76 pages |
| **Source** | `Services/SAIF.Services.Web.Proxies/SimpleCertifiedMailServiceProxy.cs` |

---

## WP-BR-016 GetLabelOnly discards the label image

| Field | Value |
| --- | --- |
| **Statement** | `GetLabelOnly` requests a label but does not return or persist the resulting image. |
| **Trigger** | A call to `GetLabelOnly` |
| **Enforcement** | `SimpleCertifiedMailServiceProxy.GetLabelOnly` |
| **Outcome** | The remote label request and any resulting exception are the only observable effects |
| **Source** | `Services/SAIF.Services.Web.Proxies/SimpleCertifiedMailServiceProxy.cs` |

---

## WP-BR-017 Sender address and recipient name are truncated

| Field | Value |
| --- | --- |
| **Statement** | Sender address lines 1–2 and the recipient name are truncated to 46 characters before the label request is sent. |
| **Trigger** | A label request |
| **Enforcement** | `SimpleCertifiedMailServiceProxy.GetCertifiedLabel`, using `Truncate` from SAIF.Framework.Common* |
| **Outcome** | Oversized fields are silently shortened to 46 characters |
| **Source** | `Services/SAIF.Services.Web.Proxies/SimpleCertifiedMailServiceProxy.cs` |

---

## WP-BR-018 A failed label response is logged and thrown

| Field | Value |
| --- | --- |
| **Statement** | A certified-label response with a status code other than 1 is a failure: it is logged and raised as `SimpleCertifiedException`. |
| **Trigger** | The wire `getCertifiedLabel` call returns `StatusCode != 1` |
| **Enforcement** | `SimpleCertifiedMailServiceProxy.GetCertifiedLabel` |
| **Outcome** | `ILogService.LogException("SimpleCertifiedMailService", "GetCertifiedLabel", "", ex)` is called, then the exception is thrown |
| **Source** | `Services/SAIF.Services.Web.Proxies/SimpleCertifiedMailServiceProxy.cs` |

---

## WP-BR-019 Label mode follows the caller flag or IsProduction

| Field | Value |
| --- | --- |
| **Statement** | Label mode is production or preview according to the caller's explicit flag. When the flag is not given, it follows `IEnvironmentService.IsProduction`. |
| **Trigger** | A label request |
| **Enforcement** | `SimpleCertifiedMailServiceProxy.GetModeOption` |
| **Outcome** | The resulting `ModeOptions` value is `production` or `preview` |
| **Source** | `Services/SAIF.Services.Web.Proxies/SimpleCertifiedMailServiceProxy.cs` |

---

## WP-BR-020 Google Maps base URI and API key come from configuration

| Field | Value |
| --- | --- |
| **Statement** | The Google Maps base URI and API key come from configuration and are read once, at construction. |
| **Trigger** | Construction of `GoogleMapsApiServiceProxy` |
| **Enforcement** | `GoogleMapsApiServiceProxy..ctor` |
| **Outcome** | The values are read via `IEnvironmentService.GetConfigSetting` (see [Configuration](configuration.md#secure-settings) for the API key) |
| **Source** | `Services/SAIF.Services.Web.Proxies/GoogleMapsApiServiceProxy.cs` |

---

## WP-BR-021 Distance queries are pipe delimited with fixed units and timeout

| Field | Value |
| --- | --- |
| **Statement** | Addresses are sent pipe-delimited, with `%3Cbr%3E` replaced by a space. Units are imperial and language is English. The request timeout is 5 seconds. Addresses are not URL-encoded. |
| **Trigger** | Any distance query |
| **Enforcement** | `GoogleMapsApiServiceProxy.QueryGoogleApi` |
| **Outcome** | An HTTP GET request is sent to the Google Distance Matrix API |
| **Source** | `Services/SAIF.Services.Web.Proxies/GoogleMapsApiServiceProxy.cs` |

---

## WP-BR-022 Miles are computed from meters at a fixed conversion factor

| Field | Value |
| --- | --- |
| **Statement** | Miles are computed as meters divided by 1609.34, rounded to one decimal place. |
| **Trigger** | A distance value is parsed from the API response |
| **Enforcement** | `GoogleMapsApiServiceProxy.ConvertMetersToMiles` |
| **Outcome** | A decimal miles value; a parse failure throws `Exception("Google Maps API Service Error - unable to calculate mileage.")` |
| **Source** | `Services/SAIF.Services.Web.Proxies/GoogleMapsApiServiceProxy.cs` |

---

## WP-BR-023 A non-OK element status sets an error message without throwing

| Field | Value |
| --- | --- |
| **Statement** | A non-`"OK"` element status in `GetDataBetweenTwoAddresses` is returned as an `ErrorMessage` on the result; it is not thrown. |
| **Trigger** | The first response element's status is not `"OK"` |
| **Enforcement** | `GoogleMapsApiServiceProxy.GetDataBetweenTwoAddresses` |
| **Outcome** | `ErrorMessage` is populated with the status text; `Miles`/`Distance`/`Time`/`Seconds` are left unset |
| **Source** | `Services/SAIF.Services.Web.Proxies/GoogleMapsApiServiceProxy.cs` |

---

## WP-BR-024 A failing element contributes zero miles

| Field | Value |
| --- | --- |
| **Statement** | In multi-address queries, an element that cannot be parsed contributes 0 miles to the result list rather than throwing. |
| **Trigger** | A parse failure for one element in a multi-address query |
| **Enforcement** | `GoogleMapsApiServiceProxy.GetDistancesFromAllResults` / `GoogleMapsApiServiceProxy.GetMilesBetweenMultipleAddresses` |
| **Outcome** | The failing position is recorded as 0 in the returned list |
| **Source** | `Services/SAIF.Services.Web.Proxies/GoogleMapsApiServiceProxy.cs` |

---

## WP-BR-025 Pairwise distance queries require equal counts

| Field | Value |
| --- | --- |
| **Statement** | Pairwise distance queries require equal numbers of origins and destinations; a mismatch throws before any query is sent. |
| **Trigger** | A call to `GetMilesBetweenMultipleAddresses` |
| **Enforcement** | `GoogleMapsApiServiceProxy.GetMilesBetweenMultipleAddresses` |
| **Outcome** | An `Exception` is thrown when the counts differ |
| **Source** | `Services/SAIF.Services.Web.Proxies/GoogleMapsApiServiceProxy.cs` |

---

> **Note:** Statements marked with `*` were inferred from code structure, naming
> conventions, or contextual evidence where direct confirmation was not available
> in source artifacts.

## Related documentation
- [Index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [API reference](api/index.md)

## Sources
- `Services/SAIF.Services.Web.Proxies/ActiveDirectoryServiceProxy.cs`
- `Services/SAIF.Services.Web.Proxies/DocumentServiceProxy.cs`
- `Services/SAIF.Services.Web.Proxies/FaxServiceProxy.cs`
- `Services/SAIF.Services.Web.Proxies/LoggingServiceProxy.cs`
- `Services/SAIF.Services.Web.Proxies/PDFServiceProxy.cs`
- `Services/SAIF.Services.Web.Proxies/WorkCalendarServiceProxy.cs`
- `Services/SAIF.Services.Web.Proxies/Legacy/DbConnectionsServiceProxy.cs`
- `Services/SAIF.Services.Web.Proxies/SimpleCertifiedMailServiceProxy.cs`
- `Services/SAIF.Services.Web.Proxies/GoogleMapsApiServiceProxy.cs`
- `Services/SAIF.Services.Web.Proxies/Helpers.cs`
