Progress Credit Union Platform — Developer Handover Guide¶
Status: Complete | Last updated: 2026-03-23
1. Platform Overview¶
What Is This Platform?¶
The Progress Credit Union platform is a multi-tenant Umbraco CMS that powers 170+ credit union websites across Ireland, the UK, and the US. Each credit union gets its own database and subdomain but shares a single codebase. Client-specific customisation is limited to CSS themes and appsettings.json configuration — all views, services, and property editors come from shared NuGet packages.
Architecture¶
┌──────────────────────────────────────────────────────────────┐
│ Client Site (www) │
│ appsettings.json · Dockerfile · Program.cs │
│ Client-specific overrides only │
├──────────────────────────────────────────────────────────────┤
│ Progress.Baseline.Web (NuGet — Razor views, CSS) │
│ Progress.Baseline.Core (NuGet — services, interfaces) │
│ Progress.CustomPropertyEditors (NuGet — backoffice editors) │
│ Progress.LoanCalculator (NuGet — loan calc engine) │
└──────────────────────────────────────────────────────────────┘
│ Umbraco 17.1.0 │
│ .NET 10 / ASP.NET Core │
│ SQL Server │
└──────────────────────────────────────────────────────────────┘
Tech Stack¶
| Layer | Technology |
|---|---|
| Backend runtime | .NET 10, ASP.NET Core |
| CMS | Umbraco 17.1.0 |
| Frontend rendering | Razor views, Bootstrap 5.3.3, jQuery 3.6.0, Slick Carousel |
| Backoffice editors | TypeScript, Vite 5.4.11, Umbraco Backoffice SDK (v16 API) |
| Database | SQL Server |
| Containerisation | Docker |
| CI/CD | Azure DevOps |
| Package feed | Azure Artifacts (CUCloud-Web-Packages feed) |
2. Getting Started¶
Prerequisites¶
| Tool | Version |
|---|---|
| .NET SDK | 10.x |
| Node.js | 20+ (for custom property editors) |
| SQL Server | 2019+ or Docker SQL image |
| Docker Desktop | Latest (for containerised runs) |
| IDE | Visual Studio 2022 / Rider / VS Code with C# Dev Kit |
Clone & Build¶
# Clone the repository
git clone <repo-url> progress
cd progress
# Restore and build the full solution
dotnet restore dbl.Progress/dbl.Progress.sln
dotnet build dbl.Progress/dbl.Progress.sln -c Debug
# Build custom property editors (TypeScript)
cd dbl.Progress/src/Progress.CustomPropertyEditors
npm install
npm run build
Run Locally¶
The site will start on https://localhost:5001 (or the port configured in launchSettings.json). The Umbraco backoffice is at /umbraco.
First-Time Setup¶
- Ensure SQL Server is running and update the connection string in
dbl.Progress/src/www/appsettings.Development.json. - On first launch, Umbraco will run its install wizard to create the database schema.
- To work with migrated content, restore a client database backup instead of running the installer.
3. Project Structure¶
dbl.Progress/
├── dbl.Progress.sln # Solution file
├── src/
│ ├── Directory.Build.props # Shared build properties
│ ├── www/ # Host web application
│ │ ├── Program.cs # DI setup, Umbraco boot
│ │ ├── Views/ # Page templates (home, article, etc.)
│ │ ├── wwwroot/ # Static assets
│ │ └── appsettings.json # Client configuration
│ ├── Progress.Baseline.Core/ # Shared services & interfaces
│ │ ├── Services/ # ISiteSettingsService, IDictionaryService, etc.
│ │ ├── Interfaces/ # IWebsiteSettings, ISiteStyleSettings, etc.
│ │ ├── ViewModels/ # ArticleResultSet, etc.
│ │ ├── Extensions/ # AddProgressBaselineCore() DI registration
│ │ └── Security/ # 2FA, auth composers
│ ├── Progress.Baseline.Web/ # Shared views & static assets
│ │ ├── Views/ # All Razor partials (190+ files)
│ │ │ ├── Partials/blockgrid/ # BlockGrid rendering (row, area, items)
│ │ │ ├── Partials/blocklist/ # BlockList components
│ │ │ ├── Partials/SiteLayout/ # Header, footer, navigation
│ │ │ └── *.cshtml # Page templates (home, article, contactus, etc.)
│ │ └── wwwroot/
│ │ ├── cssCreditUnion/ # 172 client theme CSS files
│ │ ├── vendor/ # Bootstrap, jQuery, etc.
│ │ └── js/ # Shared JavaScript
│ ├── Progress.CustomPropertyEditors/ # Backoffice property editors
│ │ ├── src/ # TypeScript source (31 editors)
│ │ ├── vite.config.js # Vite build config
│ │ └── wwwroot/App_Plugins/ # Built output
│ └── Progress.LoanCalculator/ # Loan calculation engine
What Each Project Does¶
| Project | Purpose | Ships As |
|---|---|---|
| www | Host application. Contains Program.cs, appsettings.json, and any client-specific view overrides. One instance per deployed client. |
Docker image |
| Progress.Baseline.Core | All shared C# services, interfaces, view models, and DI registration. No views or static files. | NuGet package |
| Progress.Baseline.Web | All shared Razor views (190+), client CSS themes (172 files), JavaScript, and vendor libraries. | NuGet package |
| Progress.CustomPropertyEditors | TypeScript-based Umbraco backoffice property editors (31 editors for headings, testimonials, calculators, maps, FAQ, etc.). | NuGet package |
| Progress.LoanCalculator | Standalone loan calculation engine used by calculator views. | NuGet package |
4. For Designers: CSS Customisation¶
Client CSS Files¶
Each credit union has its own CSS file at:
There are 172 client CSS files in this directory. Examples:
ProgressIEStyle.css— Progress Credit Union (Ireland)ProgressUKStyle.css— Progress Credit Union (UK)KillarneyStyles.css— Killarney Credit UnionFirstTechStyles.css— First Tech (US)
CSS Structure¶
Each client CSS file follows a standard table of contents:
/* 1.0 GENERAL (notifications, base overrides) */
/* 2.0 HEADER (search, social, login buttons, back-to-top) */
/* 3.0 CONTENT (sliders, spotlights, news, counters) */
/* 4.0 CONTENT PAGES (sidebar, contact, FAQ, forms, gallery) */
/* 5.0 LOAN CALCULATORS */
/* 6.0 FOOTER */
/* 7.0 COOKIE CONSENT */
Theming with CSS Overrides¶
Client themes primarily work by overriding colour values, fonts, and spacing. The most common customisations are:
/* Brand colours — override these per client */
.navbar { background-color: #1a3c6e; }
.btn-primary { background-color: #e47d51; border-color: #e47d51; }
a { color: #1a3c6e; }
.footer { background-color: #1a3c6e; }
Bootstrap 5 Usage¶
The platform uses Bootstrap 5.3.3. All data attributes use the data-bs-* prefix:
<!-- Correct -->
<button data-bs-toggle="modal" data-bs-target="#myModal">Open</button>
<!-- Wrong (Bootstrap 4 — do NOT use) -->
<button data-toggle="modal" data-target="#myModal">Open</button>
Adding a New Client Theme¶
- Copy an existing CSS file that is visually closest to the new client's brand.
- Rename to
{ClientName}Styles.css. - Update brand colours, logo sizing, and any component-specific overrides.
- The CSS file is referenced in Umbraco via the
websiteConfigurationdocument type — set thesiteStyleSheetproperty to the new filename.
5. For Developers: View Customisation¶
Razor View Hierarchy¶
master.cshtml ← Master layout (head, body, scripts)
├── Partials/SiteLayout/header.cshtml ← Site header + navigation
├── Partials/SiteLayout/footer.cshtml ← Site footer
├── [Page Template].cshtml ← e.g., home.cshtml, article.cshtml
│ └── BlockGrid / BlockList ← Content blocks from Umbraco
│ ├── blockgrid/BootstrapGrid.cshtml ← Grid renderer
│ ├── blockgrid/items.cshtml ← Iterates block items
│ └── Components/*.cshtml ← Individual components
└── Partials/SiteLayout/SEO.cshtml ← SEO meta tags
View Directives¶
BlockGrid components (most common):
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridItem>
@using Umbraco.Cms.Core.Models.Blocks
Page templates / master layout:
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@inject ISiteSettingsService SiteSettings
@inject IDictionaryService Dictionary
Partials with IPublishedContent:
Service Injection¶
Services are injected into views using @inject:
@inject ISiteSettingsService SiteSettings
@inject IDictionaryService Dictionary
@inject IArticleService ArticleService
@{
var website = SiteSettings.Website;
var phoneNumber = website?.Value<string>("phoneNumber") ?? "";
var welcomeText = Dictionary.GetValue("Welcome");
}
All services are registered via AddProgressBaselineCore() in Program.cs:
// Program.cs
builder.Services.AddProgressBaselineCore();
// Which registers:
// - ISiteSettingsService → SiteSettingsService (Scoped)
// - IDictionaryService → DictionaryService (Scoped)
// - IArticleService → ArticleService (Scoped)
// - ICalculatorService → CalculatorService (Scoped)
// - IHeadlessContentService → HeadlessContentService (Scoped)
// - IGlobalNotificationsService → GlobalNotificationsService (Scoped)
// - IGalleryService → GalleryService (Scoped)
BlockGrid Component Development¶
To create a new BlockGrid component:
- Create the element type in Umbraco backoffice (Document Types > Element Types).
- Create the Razor view in
Progress.Baseline.Web/Views/Partials/blockgrid/Components/:
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridItem>
@using Umbraco.Cms.Core.Models.Blocks
@{
var content = Model.Content;
var title = content.Value<string>("title") ?? "";
var image = content.Value<MediaWithCrops>("image");
}
<div class="my-component">
<h2>@title</h2>
@if (image != null)
{
<img src="@image.MediaUrl()" alt="@title" class="img-fluid" />
}
</div>
- Register the view in Umbraco: Settings > Block Grid > Data Type > add the element type and set the view path.
Custom Property Editor Development¶
Custom editors are TypeScript-based, built with Vite, and follow the Umbraco backoffice extension pattern.
Progress.CustomPropertyEditors/
├── src/
│ ├── index.ts # Entry point — registers all 31 editors
│ ├── psHeading/ # Example editor
│ │ ├── manifest.ts # Umbraco manifest registration
│ │ └── psHeading.element.ts # Web component implementation
│ ├── psFaqAccordion/
│ ├── psLoanBoxes/
│ ├── psGlobalGoogleMap/
│ └── ... (31 editors total)
├── vite.config.js
└── package.json
Build commands:
cd dbl.Progress/src/Progress.CustomPropertyEditors
npm run build # Production build → wwwroot/App_Plugins/Progress/
npm run dev # Watch mode for development
Current editors include: psHeading, psTestimonial, psNews, psLoanBoxes, psCalculators, psGlobalAddress, psGlobalGoogleMap, psGlobalLeafLetMap, psFaqAccordion, psServices, psServicesImage, psSubMenu, psSocialFooter, psStickySocial, psDataProtectionDPO, psDataProtectionName, psDataProtectionSpecificStatements, psDataProtectionSummaryTable, psDataProtectionWeAre, psReleaseNotes, GridSettingsMarginEditor, GridSettingsPaddingEditor, psFaIconPicker, psOpeningSoon, psWelcomeDashboard, psEmbeddedMedia, psTestimonialsCollectionView, UrlRewriteManager, psTextStringDefaultValue, psTextAreaDefaultValue, psPosition.
6. Content Architecture¶
Document Types Hierarchy¶
Site Root
├── websiteConfiguration ← Global settings (logo, colours, phone, etc.)
├── siteStyleSettings ← Style config (CSS file reference)
├── menuSettings ← Navigation structure
├── footerSettings ← Footer content
├── seoSettings ← Meta tags, schema.org
├── googleTagManagerSettings ← Analytics
├── cookieSettings ← Cookiebot config
├── home ← Homepage (BlockGrid content)
├── standardPage ← Generic content pages
├── articleList ← News/blog listing
│ └── article ← Individual articles
├── contactUs ← Contact page
├── gallery / galleryList ← Image galleries
├── faq ← FAQ pages
└── login / tokenAuthentication ← Member areas
BlockGrid Structure (4-Level Hierarchy)¶
Umbraco's BlockGrid is used for all page content with a strict 4-level nesting:
Row Config ← Background colour/image, full-width/contained
└── Layout ← Column structure (e.g., 6+6, 4+4+4, 12)
└── Area ← Cell wrapper — carries padding, animations, bg
└── Content ← The actual component (heading, image, RTE, etc.)
The rendering chain:
| Level | View File | Purpose |
|---|---|---|
| Grid entry | blockgrid/BootstrapGrid.cshtml |
Wraps grid in <div class="container"> |
| Multi-section | blockgrid/_MultiSectionGrid.cshtml |
Handles row iteration |
| Row | blockgrid/default.cshtml |
Row-level settings (bg, breakout) |
| Area | blockgrid/area.cshtml |
Cell padding, animations |
| Items | blockgrid/items.cshtml |
Iterates and renders content blocks |
Media Management¶
Media is handled via Umbraco's MediaPicker3 with MediaWithCrops:
// Single image (when multiple: false on the data type)
var image = content.Value<MediaWithCrops>("heroImage");
if (image != null)
{
<img src="@image.MediaUrl()" alt="@(image.Value<string>("altText"))" />
}
// Multiple images
var images = content.Value<IEnumerable<MediaWithCrops>>("galleryImages");
MediaPicker3 Single-Pick Pattern
When "multiple": false, always use content.Value<MediaWithCrops>("alias") with .MediaUrl().
Do NOT use IEnumerable<MediaWithCrops> or IPublishedContent — both return null silently.
Dictionary Items¶
Translations and reusable labels use Umbraco Dictionary:
@inject IDictionaryService Dictionary
<button>@Dictionary.GetValue("ReadMore")</button>
<span>@Dictionary.GetValue("ContactUs")</span>
7. Key Services¶
All services are registered as Scoped via AddProgressBaselineCore().
ISiteSettingsService¶
Central access to all global configuration nodes. Replaces v8 XPath queries like Umbraco.ContentSingleAtXPath("//websiteConfiguration").
@inject ISiteSettingsService SiteSettings
@{
var website = SiteSettings.Website; // IWebsiteSettings
var styles = SiteSettings.Styles; // ISiteStyleSettings
var menu = SiteSettings.Menu; // IMenuSettings
var footer = SiteSettings.Footer; // IFooterSettings
var gtm = SiteSettings.GoogleTagManager; // IGoogleTagManagerSettings
var seo = SiteSettings.Seo; // ISeoSettings
var cookie = SiteSettings.Cookie; // ICookieSettings
var slider = SiteSettings.HomepageSlider; // IHomepageSliderSettings
var loanBoxes = SiteSettings.LoanBoxesConfiguration;
var calculators = SiteSettings.CalculatorsConfiguration;
var searchUrl = SiteSettings.GetSearchPageUrl(Model);
}
IDictionaryService¶
Localised string lookup. Returns the key itself if not found.
@inject IDictionaryService Dictionary
string label = Dictionary.GetValue("ReadMore"); // Current culture
string labelIrish = Dictionary.GetValue("ReadMore", "ga-IE"); // Specific culture
string? nullable = Dictionary.GetValueOrNull("MayNotExist"); // Null if missing
IArticleService¶
News/blog article queries with paging and category filtering.
@inject IArticleService ArticleService
var latest = ArticleService.GetLatestArticles(siteRoot);
var byCategory = ArticleService.GetLatestArticlesByCategory(siteRoot, "Loans");
var paged = ArticleService.GetLatestArticles(Model, Context.Request);
var categories = ArticleService.GetCategoriesInUse(Model);
Other Services¶
| Service | Purpose |
|---|---|
ICalculatorService |
Loan/savings calculator logic |
IGalleryService |
Image gallery queries |
IGlobalNotificationsService |
Site-wide notification banners |
IGlobalCookiesService |
Cookie consent state |
IHeadlessContentService |
Headless/API content delivery |
ILoanCalculatorEstimator |
Loan repayment estimation engine (registered separately in Program.cs) |
URL Rewriting¶
Custom URL rewrites are stored in the ProgressUrlRewrites database table and managed via the UrlRewriteManager custom property editor in the backoffice.
8. Common Tasks¶
Adding a New Page Template¶
- Create the document type in Umbraco backoffice.
- Create a Razor view in
Progress.Baseline.Web/Views/:
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@inject ISiteSettingsService SiteSettings
@inject IDictionaryService Dictionary
@{
Layout = "master.cshtml";
var title = Model.Value<string>("pageTitle") ?? Model.Name;
}
<div class="container">
<h1>@title</h1>
@await Html.GetBlockGridHtmlAsync(Model, "gridContent")
</div>
- The view filename must match the document type alias (e.g., alias
specialPage→specialPage.cshtml).
Creating a New BlockGrid Component¶
- Create an Element Type in Umbraco (Document Types > create with "Is Element" checked).
- Add properties (title, image, richText, etc.).
- Create the partial view in
Views/Partials/blockgrid/Components/{ComponentName}.cshtml. - Add the element type to the relevant BlockGrid data type configuration.
Adding a New Client¶
- CSS: Copy a similar client's CSS file to
cssCreditUnion/{ClientName}Styles.css, update colours. - Database: Create a new SQL Server database. Run the migration tool or restore from a template backup.
- appsettings: Create client-specific
appsettings.{Environment}.jsonwith the correct connection string. - Umbraco config: In the backoffice, set
websiteConfiguration > siteStyleSheetto the new CSS filename. - Docker: Build a Docker image with the client's appsettings baked in (or use environment variables).
Modifying Navigation¶
Navigation is driven by the Umbraco content tree. Pages with umbracoNaviHide = false appear in menus automatically. The navigation views are:
Partials/SiteLayout/navigationDynamic.cshtml— Primary navPartials/SiteLayout/navigationDynamicMenu2.cshtml— Mega-menu variantPartials/SiteLayout/navigationDynamicMenu4.cshtml— Alternative layout
Menu structure is configured via the menuSettings document type in the backoffice.
Working with Umbraco Forms¶
Umbraco Forms is installed and styled in the client CSS files (section 4.18). Forms are embedded in content pages using the Forms picker property. Custom form themes are in Progress.CustomPropertyEditors/Forms/.
9. Build & Deploy¶
Build Commands¶
# Full solution build
dotnet restore dbl.Progress/dbl.Progress.sln
dotnet build dbl.Progress/dbl.Progress.sln -c Release
# Custom property editors
cd dbl.Progress/src/Progress.CustomPropertyEditors
npm ci
npm run build
# Run tests
dotnet test double-migration-tool/UmbracoMigrator.sln
Docker Build¶
# Typical build (from repo root)
docker build -t progress-cu:latest -f dbl.Progress/src/www/Dockerfile .
docker run -p 8080:8080 progress-cu:latest
CI/CD Pipeline (Azure DevOps)¶
The project uses Azure DevOps (https://dev.azure.com/ProgressSystemsLtd, project: CUCloud.Web).
Pipeline stages:
- Build —
dotnet build+npm run buildfor property editors - Test —
dotnet teston migration tool tests - Pack — NuGet packages for Baseline.Core, Baseline.Web, CustomPropertyEditors, LoanCalculator
- Push — Packages pushed to internal NuGet feed
- Deploy — Docker image built and deployed per client
Environment Configuration¶
Configuration is layered via standard ASP.NET Core:
appsettings.json ← Base defaults
appsettings.Development.json ← Local dev overrides
appsettings.Production.json ← Production settings
Environment variables ← Docker/container overrides
Key configuration sections:
{
"ConnectionStrings": {
"umbracoDbDSN": "Server=...;Database=...;..."
},
"Umbraco": {
"CMS": {
"Global": { "Id": "..." },
"Content": { "MacroErrors": "Inline" },
"ModelsBuilder": { "ModelsMode": "InMemoryAuto" },
"DatabaseCleanup": { "Enabled": false },
"CacheCleanup": { "Enabled": false }
}
}
}
Commit Conventions¶
Use conventional commits: feat:, fix:, refactor:, docs:, chore:.
For Azure DevOps work item linking: feat: add login button #2219
Related Documentation¶
- Summary & Progress — Migration audit overview
- Grid Architecture — 4-level BlockGrid deep dive
- Full Migration Validation — File-by-file v8 to v17 mapping
- BlockPreview + NuGet RCL Plan — Future architecture