Example: .NET Framework 4 MVC screen document¶
Doc gen produces one screen document per non-utility screen in a .NET Framework
4 ASP.NET MVC 5 front end that uses Razor views, written to
docs/screens/<slug>.md from the controller action, its Razor view and its
ViewModel. This is a different document from the
ASP.NET Core Razor Pages screen and the
BOF WebForms screen: a screen is a compiled controller
action that returns a full view, and its interface is actions and routes rather
than page handlers or .aspx controls.
What reviewers should check¶
- Screens are compiled actions that return a full view. One document per
View()action with a layout, not one per controller. Partial views, dialogs, grid, JSON and remote-validator actions are not screens, anddocs/screens/index.mdis reconciled against the compiled actions. - The Route table uses the real route table. The URL is what
RouteConfigproduces for the controller and action, not an invented path, with one row per verb when GET and POST share an action name. - Operations use the MVC request form. The
Requestcolumn isVERB Controller/Action, without theControllersuffix. A?handler=request or a[SecurityApiAuthorize]role means the Razor Pages vocabulary leaked in, and a postback,ViewStateor.aspxreference means the WebForms vocabulary did; both are defects. - Access is the effective
[SaifAuthorize]requirement. The roles come from the action attribute, otherwise the controller's, otherwise the localBaseController's, and each row names where it was declared. In-action checks such asCanUserUpdate()that change what the user sees are rows too. - Delegation is sourced. Every
Delegates tovalue is a resolvable link to a library operation or application workflow, a†-marked dotted library name, or—for screen-local actions. The delegated operation's steps are not restated. - Wiring stays off the screen page. The Autofac composition root lives in
the application companion project (here
SAIF.DependencyMaps.Web.PolicyRenewals) and is documented ondocs/application/architecture.md, not on screens. - AJAX rows link to
hosted-endpoints.md. Each row in## AJAX and grid actionslinks its entry ondocs/application/hosted-endpoints.md; partial views belong in## Dialogs and partials. - Sources are controller, view and ViewModel files. An
.aspxfile or a code-behind file as a source is a blocker. - Rule IDs are links to their anchor on
docs/application/business-rules.md. - A
## Screen flow diagramappears only when the screen has three or more outbound navigations. A cross-application target names the configuration key that holds its URL.
Example¶
The document below is a complete, correct renewal-quote.md for a fictional
application.
View rendered example — the same document as a real page, so you can compare rendering as well as source.
---
title: Renewal Quote
summary: Lets underwriting staff review a policy's renewal premium by class code, adjust the quote lines, and issue the renewal offer to the employer.
status: draft
owner: Policy Renewals Team
last_reviewed: 2026-09-09
---
# Renewal Quote
Renewal Quote is the main working screen of the Policy Renewals application.
Underwriting staff open it from the Renewal Queue for a policy that is
approaching expiration and use it to review the quote the rating library has
produced: the premium for each class code, the experience modifier, and the
renewal effective date. The user can adjust individual premium lines, give a
reason for the adjustment, save the quote, and issue the renewal offer to the
employer. The action loads the quote for the `policyNumber` route value and
returns a state view when none can be produced. From here the user returns to
the queue, opens the quote history, or moves to the policy summary in the
Policy Services Portal.
## Route
| Controller | Action | Verb | URL | Parameters | View | Layout |
| --- | --- | --- | --- | --- | --- | --- |
| `RenewalController` | `Quote` | GET | `/Renewal/Quote/{policyNumber}` | `policyNumber` | `Views/Renewal/Quote.cshtml` | `_Layout` |
| `RenewalController` | `Quote` | POST | `/Renewal/Quote/{policyNumber}` | `policyNumber`, `RenewalQuoteViewModel` | `Views/Renewal/Quote.cshtml` (redisplayed when validation fails) | `_Layout` |
## Access
| Requirement | Declared on | Effect |
| --- | --- | --- |
| `[SaifAuthorize(Roles = "Renewals.Read,Renewals.Underwriting")]` | `BaseController` | Only users holding one of the two roles can open any action of a controller that derives from `BaseController` |
| `[SaifAuthorize(Roles = "Renewals.Underwriting")]` | `RenewalController.IssueOffer` | Only underwriting staff can issue a renewal offer |
| `CanUserUpdate()` | `RenewalController.Quote` | Returns `true` only for users in `Renewals.Underwriting`; when `false`, `IsReadOnly` is set, the grid shows no edit commands, and **Save quote** and **Issue offer** are hidden |
## Entry conditions
- The user selects a policy in the Renewal Queue and follows the **Open quote**
link, which passes `policyNumber` in the route.
- The user opens a bookmarked or emailed URL of the form
`/Renewal/Quote/{policyNumber}`.
- The default route `Home/Index` redirects to the Renewal Queue, so the quote
screen is never the application start page.
- The action requires a policy number from the route or from `WebContext`. When
neither supplies one, it returns the `InvalidContext` state view.
## Fields
| Field | Binds to | Control | Purpose | Validation |
| --- | --- | --- | --- | --- |
| Policy number | `RenewalQuoteViewModel.PolicyNumber` | `Html.DisplayFor` | Identifies the policy being renewed | None; display only |
| Employer | `RenewalQuoteViewModel.EmployerName` | `Html.DisplayFor` | Insured employer of record | None; display only |
| Renewal effective date | `RenewalQuoteViewModel.EffectiveDate` | Kendo DatePicker (`Html.Kendo().DatePickerFor`) | Start of the renewal term | `[Required]` and `[Remote("ValidateEffectiveDate", "Renewal")]`; client-side yes |
| Experience modifier | `RenewalQuoteViewModel.ExperienceModifier` | Kendo NumericTextBox (`Html.Kendo().NumericTextBoxFor`) | Modifier applied to the manual premium | `[Range(0.50, 2.50)]`; client-side yes |
| Quote lines | `RenewalQuoteViewModel.QuoteLines` | Kendo Grid (`Html.Kendo().Grid<QuoteLineViewModel>()`) | Calculated and adjusted premium for each class code | `QuoteLineViewModelValidator` requires a non-negative adjusted premium; client-side no |
| Adjustment reason | `RenewalQuoteViewModel.AdjustmentReason` | `Html.TextAreaFor` | Explains why any line differs from the calculated premium | `[StringLength(500)]`; required by `RenewalQuoteViewModelValidator` when a reduction exceeds 25 percent; client-side no |
| Total premium | `RenewalQuoteViewModel.TotalPremium` | `Html.DisplayFor`, formatted as currency | Sum of the adjusted line premiums | None; display only |
| Save quote | None | `<button type="submit">` `btnSaveQuote` | Posts the adjusted quote | Hidden when `IsReadOnly` |
| Issue offer | None | `<button type="submit">` `btnIssueOffer`, in a form that posts to `IssueOffer` | Issues the renewal offer for the saved quote | Hidden when `IsReadOnly` |
## Operations
| Operation | Trigger | Request | Description | Delegates to |
| --- | --- | --- | --- | --- |
| load-quote | Page load | `GET Renewal/Quote` | Reads the renewal quote for the policy number and populates the header fields and the quote lines grid | `GetRenewalQuote` in `SAIF.Policy.Services`† |
| save-quote | `btnSaveQuote` click | `POST Renewal/Quote` | Re-rates the adjusted quote lines and saves the quote, then redisplays the screen with the new total | [RecalculateRenewalPremium](../libraries/saif-renewals-rating/operations/recalculate-renewal-premium.md) |
| issue-offer | `btnIssueOffer` click | `POST Renewal/IssueOffer` | Issues the renewal offer to the employer and redirects to the Renewal Queue | [Renewal offer issuance](../application/operations/renewal-offer-issuance.md) |
| open-quote-help | `lnkQuoteHelp` click | `GET Renewal/QuoteHelp` | Returns the quote help partial | — |
## Dialogs and partials
| Name | Opens as | Returned by | Purpose |
| --- | --- | --- | --- |
| `_QuoteHelp` | kendoWindow, content loaded from the action URL | `GET Renewal/QuoteHelp` | Explains each premium line type and the limits on adjusting a line |
## AJAX and grid actions
| Request | Kind | Called by | Returns |
| --- | --- | --- | --- |
| [`POST Renewal/QuoteLines_Read`](../application/hosted-endpoints.md#post-renewalquotelines_read) | grid read | `quoteLinesGrid` data source | `DataSourceResult` of `QuoteLineViewModel` |
| [`POST Renewal/QuoteLines_Update`](../application/hosted-endpoints.md#post-renewalquotelines_update) | grid update | `quoteLinesGrid` inline edit | `DataSourceResult` with the updated line, or model state errors |
| [`GET Renewal/ValidateEffectiveDate`](../application/hosted-endpoints.md#get-renewalvalidateeffectivedate) | remote validator | `EffectiveDate` field | JSON `true`, or an error message string |
## Client-side behavior
- **Script module:** `Scripts/app/renewal/quote.js`
- `quoteLinesGrid` is a Kendo Grid with inline editing; after each
`QuoteLines_Update` response the `quoteViewModel` `kendo.observable` view model
recomputes `totalPremium`.
- The `EffectiveDate` DatePicker uses the policy expiration date as its minimum
and relies on jQuery unobtrusive validation for the remote check.
- `lnkQuoteHelp` opens `_QuoteHelp` in a kendoWindow.
- Failed grid requests raise a toastr notification.
## States
| State view | Condition |
| --- | --- |
| `NoQuote` | `GetRenewalQuote` returns no quote because the policy is outside its renewal window |
| `InvalidContext` | No policy number can be resolved from the route or `WebContext` |
## Business rules
| Rule ID | Rule | Enforcement | Source |
| --- | --- | --- | --- |
| [BR-SCR-001](../application/business-rules.md#br-scr-001-effective-date-follows-expiration) | The renewal effective date must be the day after the current policy expiration date | `ValidateEffectiveDate` remote validator and `RenewalQuoteViewModelValidator` | `src/SAIF.Policy.Renewals.Web/Controllers/RenewalController.cs:131` |
| [BR-SCR-002](../application/business-rules.md#br-scr-002-large-reductions-need-a-reason) | A quote line cannot be reduced by more than 25 percent of its calculated premium without an adjustment reason | `RenewalQuoteViewModelValidator` | `src/SAIF.Policy.Renewals.Web/Validators/RenewalQuoteViewModelValidator.cs:22` |
| [BR-SCR-003](../application/business-rules.md#br-scr-003-offers-issued-by-underwriting-only) | Only underwriting staff can issue a renewal offer | `[SaifAuthorize]` on `IssueOffer` | `src/SAIF.Policy.Renewals.Web/Controllers/RenewalController.cs:98` |
| [BR-APP-002](../application/business-rules.md#br-app-002-quote-requires-renewal-window) | A renewal quote is available only for a policy inside its renewal window | `NoQuote` state view returned by `Quote` | `src/SAIF.Policy.Renewals.Web/Controllers/RenewalController.cs:58` |
## Navigation
| Target | Trigger | Parameters |
| --- | --- | --- |
| [Renewal Queue](renewal-queue.md) | **Back to queue** link; redirect after **Issue offer** | None |
| [Quote History](quote-history.md) | **History** link | `policyNumber` route parameter |
| Policy Summary in the Policy Services Portal | **Policy summary** link | `policyNumber` carried in the web context; the portal URL is held in the `PolicyPortalUrl` app setting |
## Screen flow diagram
```mermaid
flowchart TD
A[Renewal Quote] -->|Back to queue or Issue offer| B[Renewal Queue]
A -->|History| C[Quote History]
A -->|Policy summary| D[Policy Summary in the Policy Services Portal]
B -->|Open quote| A
```
---
> [!NOTE]
> Statements marked with `*` are inferred from code patterns. Statements marked with `**` could not be verified from available source. Statements marked with `†` are sourced from package documentation retrieved with the Legends CLI.
## Sources
- src/SAIF.Policy.Renewals.Web/Controllers/BaseController.cs
- src/SAIF.Policy.Renewals.Web/Controllers/RenewalController.cs
- src/SAIF.Policy.Renewals.Web/Views/Renewal/Quote.cshtml
- src/SAIF.Policy.Renewals.Web/Views/Renewal/_QuoteHelp.cshtml
- src/SAIF.Policy.Renewals.Web/Models/RenewalQuoteViewModel.cs
- src/SAIF.Policy.Renewals.Web/Models/QuoteLineViewModel.cs
- src/SAIF.Policy.Renewals.Web/Validators/RenewalQuoteViewModelValidator.cs
- src/SAIF.Policy.Renewals.Web/Scripts/app/renewal/quote.js