---
title: BolMiddleware business rules
summary: Business rules enforced by the BolMiddleware page model chain, services, and views.
status: draft
owner: SAIF IT Core Services
last_reviewed: 2026-10-12
library: BolMiddleware
reference_type: package
---

# BolMiddleware business rules

This page lists the behavioral rules that BolMiddleware enforces on every consumer Razor Page. Each rule has its own section with a Field/Value table. Rules are enforced by the abstract page model chain (`BolContextModel`, `BolMenuModel`, `BolPolicyModel`), the service facades, and the shared Razor views. The library holds no database tables, so no rule has a table or SQL source.

## BR-BOLMIDDLEWARE-001 Web context resolution precedence

| Field | Value |
| --- | --- |
| Statement | The BOL web context for a request is resolved from the first available source in this order: the `BolContext` cookie, a re-read of the cookie, the referring application, the `WebContextId` form value or `cid` query value, the session, and finally a newly created context. |
| Trigger | Every Razor Page handler selection on a page that derives from `BolContextModel`. |
| Enforcement | `BolContextModel.OnPageHandlerExecuting` calls `TryGetContext`, which calls `WebContextService.GetContext` and `NewContext` through the request helper. The cookie is read earlier, in `OnPageHandlerSelected`. |
| Outcome | `WebContext` is populated for the handler. When no source yields a context a new context is created, the middleware session cookie is added, and the `BolContext` cookie is set. |
| Source | src/BolMiddleware/Models/BolContextModel.cs |

---

## BR-BOLMIDDLEWARE-002 Context save condition

| Field | Value |
| --- | --- |
| Statement | The context is written to the cookie whenever it has content. It is saved through the remote BOL Context API only when the caller forces the save or the target application differs from the current application. It is always saved to the session. |
| Trigger | A consumer page calls `SaveContext`, or navigation calls it while moving to another application. |
| Enforcement | `BolContextModel.SaveContext(bool forceSave)` evaluates the cookie, API, and session writes independently. |
| Outcome | The API call is skipped for same-application requests unless `forceSave` is true; the session copy is always refreshed. |
| Source | src/BolMiddleware/Models/BolContextModel.cs |

---

## BR-BOLMIDDLEWARE-003 Context content detection

| Field | Value |
| --- | --- |
| Statement | A context has content when it has an Id. A context with no Id but with a session id is given a newly generated GUID as its Id and counts as having content. |
| Trigger | Any call to `HasContent` before saving or cookie writing. |
| Enforcement | `WebContextExtensions.HasContent` mutates the `ContextResult.Id` as a side effect. |
| Outcome | A check for content can mint an Id; a context with neither Id nor session id has no content and is not persisted. |
| Source | src/BolMiddleware/Services/Context/WebContextExtensions.cs |

---

## BR-BOLMIDDLEWARE-004 Context cookie lifetime and flags

| Field | Value |
| --- | --- |
| Statement | The `BolContext` cookie lives for ten seconds and is flagged HttpOnly. It carries no Secure or SameSite attribute. Its expiry is computed from the local server clock. |
| Trigger | Context creation and every context save that writes the cookie. |
| Enforcement | `CookieService.SetCookie` and `SetCookieObject` build `CookieOptions` with `HttpOnly = true` and an `Expires` value from `DateTime.Now`. |
| Outcome | The cookie works as a short hand-off between consecutive requests during cross-application navigation. It is sent over HTTP and cross-site requests if the host permits. |
| Source | src/BolMiddleware/Services/CookieService/CookieService.cs, src/BolMiddleware/Models/BolContextModel.cs |

---

## BR-BOLMIDDLEWARE-005 External user detection

| Field | Value |
| --- | --- |
| Statement | A user is external when the domain portion of the authenticated identity name is not `SAIF`. |
| Trigger | Any read of `IsUserExternal` or any call to `ISecurityService.IsUserExternal`. |
| Enforcement | `SecurityService.IsUserExternal` splits the identity name on the domain separator and compares the domain with `SAIF`. |
| Outcome | External users get the external user branch of context, policy header, and header link logic; internal users get the internal branch. |
| Source | src/BolMiddleware/Services/Security/SecurityService.cs |

---

## BR-BOLMIDDLEWARE-006 External user information lookup once

| Field | Value |
| --- | --- |
| Statement | External user information is requested from the security client (`ISecurityClient.GetExternalUser`) only while the context cookie user state is New. After a successful lookup the state becomes Validated. The Policy Core API is not involved in this lookup; it is used only for policy access validation ([BR-BOLMIDDLEWARE-009](#br-bolmiddleware-009-policy-access-validation)). |
| Trigger | A request from an external user where the cookie user state is New. |
| Enforcement | `BolContextModel.OnPageHandlerExecuting` calls `GetExternalUserInformationForContext`, which calls `SecurityService.GetCurrentExternalUserAsync` once and updates `contextCookie.userState` and `userType`. Exceptions are swallowed. |
| Outcome | Later requests inside the cookie lifetime reuse the stored user type and state without calling the security client again. |
| Source | src/BolMiddleware/Models/BolContextModel.cs, src/BolMiddleware/Services/Security/SecurityService.cs |

---

## BR-BOLMIDDLEWARE-007 Menu source selection

| Field | Value |
| --- | --- |
| Statement | The menu is read from the session when present. Otherwise it is requested from the BOL Menu API for the current user and stored in the session. When the API returns nothing the menu stays empty. |
| Trigger | Handler execution on a page that derives from `BolMenuModel`. |
| Enforcement | `BolMenuModel.OnPageHandlerExecuting` checks the session before calling `MenuService.GetMenu`. |
| Outcome | `MenuData` is the session copy, the API copy, or the default empty menu. |
| Source | src/BolMiddleware/Models/BolMenuModel.cs, src/BolMiddleware/Services/Menu/MenuService.cs |

---

## BR-BOLMIDDLEWARE-008 Context value classification

| Field | Value |
| --- | --- |
| Statement | A posted context value whose length is less than seven or exactly nine characters is treated as a policy number. Any other length is treated as a claim number. |
| Trigger | A user submits the context textbox with a new value on a page with the context field. |
| Enforcement | `BolMenuModel.OnPageHandlerExecuting` inspects `ContextValue.Length` and stores it under the policy or claim context item. |
| Outcome | The context is updated with a policy number or claim number before the page continues. |
| Source | src/BolMiddleware/Models/BolMenuModel.cs |

---

## BR-BOLMIDDLEWARE-009 Policy access validation

| Field | Value |
| --- | --- |
| Statement | Access to a policy is validated for the current user through `IsValidPolicyForCurrentUser`. The result is read synchronously and drives the `AccessAllowed` flag. |
| Trigger | A policy header page resolves a policy number. |
| Enforcement | `BolPolicyModel.GetPolicyHeader` reads `Task.Result` of the validation call and sets `AccessAllowed` and `ErrorMessage`. |
| Outcome | Failed validation sets an error message so that the view renders the error view. Enforcement is soft: derived pages must check `AccessAllowed` themselves. |
| Source | src/BolMiddleware/Models/BolPolicyModel.cs, src/BolMiddleware/Services/Security/SecurityService.cs |

---

## BR-BOLMIDDLEWARE-010 Parent account reset

| Field | Value |
| --- | --- |
| Statement | `ParentAccount` is reset to zero, and the stored `ParentAccount` context value is set to `0`, only when the posted `contextvalue` (the policy select value from the context textbox) is non-empty, meaning a new policy was entered. When it is empty the reset does not occur. Afterwards `TryGetPolicyHeader` restores `ParentAccount` from the context value when that value is greater than zero, and derives it from the header ([BR-BOLMIDDLEWARE-011](#br-bolmiddleware-011-parent-account-derivation)) only while `ParentAccount` is zero. |
| Trigger | `BolPolicyModel.OnPageHandlerExecuting` with a non-empty posted `contextvalue`. |
| Enforcement | The handler assigns `ParentAccount = 0` and calls `WebContext.SetContextValue("ParentAccount", "0")` inside an `if (!string.IsNullOrEmpty(policyselect))` block, before header resolution. |
| Outcome | Entering a new policy discards the earlier parent account. Requests without a posted `contextvalue` keep any stored parent account greater than zero. |
| Source | src/BolMiddleware/Models/BolPolicyModel.cs |

---

## BR-BOLMIDDLEWARE-011 Parent account derivation

| Field | Value |
| --- | --- |
| Statement | The parent account is taken from the policy header relationship policy, or from the rate account when no relationship policy is present. |
| Trigger | A policy header is resolved for a valid policy. |
| Enforcement | `BolPolicyModel.GetPolicyHeader` reads `RelationshipPolicy` and `RateAccount` from the header. |
| Outcome | `ParentAccount` exposes the numeric parent account to the consumer page. |
| Source | src/BolMiddleware/Models/BolPolicyModel.cs |

---

## BR-BOLMIDDLEWARE-012 Policy dropdown visibility

| Field | Value |
| --- | --- |
| Statement | The dropdown is shown only when the header has at least one association and one of these conditions holds. External user: the web context `CustomerId` is non-empty and equals the header relationship policy or rate account, or the header has a `ManualAffiliation` association and `CustomerId` equals `ParentAccount`. Internal user: the header policy number equals the rate account or relationship policy, or `ParentAccount` equals the relationship policy or rate account. No minimum of two associations is required. |
| Trigger | After a policy header is resolved. |
| Enforcement | `BolPolicyModel.CanViewPolicyDropdown(isUserExternal)` sets `ShowPolicyDropdown`; `BolPolicyHeader` view renders either the select or a plain label. |
| Outcome | Eligible users can switch policy from the header; others see the policy number and name only. This is a presentation gate; a chosen policy is re-validated by policy access validation. |
| Source | src/BolMiddleware/Models/BolPolicyModel.cs, src/BolMiddleware/Pages/Components/BolPolicyHeader/default.cshtml |

---

## BR-BOLMIDDLEWARE-013 External association filtering

| Field | Value |
| --- | --- |
| Statement | The Policy Header API is called with the policy number and an external-user flag (true when the web context customer id is greater than zero). For an external user, user type code `POLCY` replaces the header associations with those returned by a second header lookup for the customer id; user type code `AGNCY` keeps only associations that contain a policy whose `AgencyId` equals the customer id. Any other user type code leaves the associations unchanged. |
| Trigger | The policy header is requested through `IPolicyHeaderService.GetHeader` with a customer id greater than zero. |
| Enforcement | `PolicyHeaderService.GetHeader` switches on the user type code after `IPolicyHeaderApiClient.GetHeader(policyNumber, isExternalUser)`. |
| Outcome | The dropdown offers the customer's policies for `POLCY`, the agency's policies for `AGNCY`, and the unfiltered associations otherwise. The customer id and type code come from the web context, not from the identity on each request. |
| Source | src/BolMiddleware/Services/Headers/Policy/PolicyHeaderService.cs |

---

## BR-BOLMIDDLEWARE-014 Session policy header reuse

| Field | Value |
| --- | --- |
| Statement | A policy header stored in the session is reused when it matches the requested policy number; otherwise the header is requested from the API and saved to the session. |
| Trigger | `TryGetPolicyHeader` on a policy header page. |
| Enforcement | `BolPolicyModel.TryGetPolicyHeader` compares the session header's policy number with the context policy number. |
| Outcome | API calls are avoided for repeated requests on the same policy. |
| Source | src/BolMiddleware/Models/BolPolicyModel.cs |

---

## BR-BOLMIDDLEWARE-015 Policy dropdown ordering

| Field | Value |
| --- | --- |
| Statement | Dropdown entries are ordered by policy number descending, with the currently selected policy moved to the first position. Each policy number may appear only once. |
| Trigger | The `BolPolicyHeader` view renders the dropdown. |
| Enforcement | `HeaderExtensions.GetPolicyAssociationDropdown` sorts, moves the current policy, and fills a dictionary using `Add`. |
| Outcome | A duplicate policy number across associations throws an `ArgumentException` while the view renders. |
| Source | src/BolMiddleware/Extensions/HeaderExtensions.cs |

---

## BR-BOLMIDDLEWARE-016 Null menu error view

| Field | Value |
| --- | --- |
| Statement | When no menu object is available the side menu view component returns the shared error view. |
| Trigger | `BolSideMenu` or `BolTopMenu` is invoked with a null menu. |
| Enforcement | The view component `InvokeAsync` methods return the `Error` view when the menu is null. |
| Outcome | The page shows an error banner in place of the menu. |
| Source | src/BolMiddleware/ViewComponents/BolSideMenuViewComponent.cs, src/BolMiddleware/ViewComponents/BolTopMenuViewComponent.cs |

---

## BR-BOLMIDDLEWARE-017 Error message error view

| Field | Value |
| --- | --- |
| Statement | When the policy model carries an error message the policy header view component returns the shared error view with that message. |
| Trigger | `BolPolicyHeader` is invoked with a non-empty error message. |
| Enforcement | `BolPolicyHeaderViewComponent.InvokeAsync` returns `View("Error", errorMessage)`. |
| Outcome | The page renders the message text in the error banner. Exception messages set by `BolPolicyModel` are shown as-is. |
| Source | src/BolMiddleware/ViewComponents/BolPolicyHeaderViewComponent.cs, src/BolMiddleware/Pages/Shared/Error.cshtml |

---

## BR-BOLMIDDLEWARE-018 External profile and logoff links

| Field | Value |
| --- | --- |
| Statement | Profile and logoff links in the page header appear only for external users. The target host is `account.saif.com` when the environment name contains `prod` and `account-qa.saif.com` otherwise. |
| Trigger | The `_BolPageHeaderPartial` renders for an external user. |
| Enforcement | The partial checks `Model.IsUserExternal` and `IWebHostEnvironment.EnvironmentName`. |
| Outcome | External users get profile and logoff navigation; internal users get none. An environment such as `preprod` also matches the production host. |
| Source | src/BolMiddleware/Pages/Shared/_BolPageHeaderPartial.cshtml |

---

## BR-BOLMIDDLEWARE-019 Context textbox visibility

| Field | Value |
| --- | --- |
| Statement | The context textbox in the layouts is rendered as a visible text input when the page sets `ChangeContextPrivilege`, and as a hidden input otherwise. This is a presentation behavior only, not server-side authorization. |
| Trigger | A layout renders the web context form. |
| Enforcement | The layouts test `ChangeContextPrivilege` on the page model to choose `type="text"` or `type="hidden"`. `BolMenuModel` reads the posted `contextvalue` without checking the flag. |
| Outcome | Hiding the input does not stop a crafted form post from changing the policy or claim context. The library never assigns the flag; the consuming application sets it. |
| Source | src/BolMiddleware/Pages/Shared/_PolicyLayout.cshtml, src/BolMiddleware/Pages/Shared/_ContextOnlyLayout.cshtml |

---

## BR-BOLMIDDLEWARE-020 Session identifier source selection

| Field | Value |
| --- | --- |
| Statement | The session id used for context lookups comes from a named cookie when the environment key `bolmiddleware:UseSessionCookie` is true, and from the ASP.NET Core session otherwise. |
| Trigger | Every request through `BolContextModel` that needs a session id. |
| Enforcement | `BolContextModel.GetSessionId` reads `IEnvironmentClient.GetSettingValue` for `bolmiddleware:UseSessionCookie` on every call, and reads `bolmiddleware:SessionCookieName` there only when `UseSessionCookie` is true. `SessionCookieName` is also read separately when a new context is created and `AddMiddlewareSessionCookie` appends the session cookie. |
| Outcome | Applications behind a shared session cookie can share a context. Missing-key behavior is unknown**. |
| Source | src/BolMiddleware/Models/BolContextModel.cs |

---

## BR-BOLMIDDLEWARE-021 Silent failure degradation

| Field | Value |
| --- | --- |
| Statement | Failures in the remote context, menu, and policy header services are logged and replaced by empty or default results instead of being thrown to the page. |
| Trigger | An API client exception inside `WebContextService`, `MenuService`, or `PolicyHeaderService`. |
| Enforcement | Each facade wraps client calls in try and catch blocks that log through `ISaifLogService` and return a default value. |
| Outcome | Pages render with a missing menu or context rather than failing; operators learn of faults only from the logs. |
| Source | src/BolMiddleware/Services/Context/WebContextService.cs, src/BolMiddleware/Services/Menu/MenuService.cs, src/BolMiddleware/Services/Headers/Policy/PolicyHeaderService.cs |

---

## Notes
- Rules 009 and 021 combine to give soft enforcement: a failed access check or a failed API call never stops the page from running.
- The `UserType` record and the `WebContextSaveKey` constant are defined but unused; no rule depends on them.

> **Note:** Statements marked with `**` could not be confirmed; treat
> as unknown until verified.

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

## Sources
- src/BolMiddleware/Models/BolContextModel.cs
- src/BolMiddleware/Models/BolMenuModel.cs
- src/BolMiddleware/Models/BolPolicyModel.cs
- src/BolMiddleware/Services/CookieService/CookieService.cs
- src/BolMiddleware/Services/Context/WebContextExtensions.cs
- src/BolMiddleware/Services/Context/WebContextService.cs
- src/BolMiddleware/Services/Menu/MenuService.cs
- src/BolMiddleware/Services/Headers/Policy/PolicyHeaderService.cs
- src/BolMiddleware/Services/Security/SecurityService.cs
- src/BolMiddleware/Extensions/HeaderExtensions.cs
- src/BolMiddleware/ViewComponents/BolPolicyHeaderViewComponent.cs
- src/BolMiddleware/ViewComponents/BolSideMenuViewComponent.cs
- src/BolMiddleware/ViewComponents/BolTopMenuViewComponent.cs
- src/BolMiddleware/Pages/Shared/_BolPageHeaderPartial.cshtml
- src/BolMiddleware/Pages/Shared/_PolicyLayout.cshtml
- src/BolMiddleware/Pages/Shared/_ContextOnlyLayout.cshtml
- src/BolMiddleware/Pages/Shared/Error.cshtml
- src/BolMiddleware/Pages/Components/BolPolicyHeader/default.cshtml
