Skip to content

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

dotnet run --project dbl.Progress/src/www/www.csproj

The site will start on https://localhost:5001 (or the port configured in launchSettings.json). The Umbraco backoffice is at /umbraco.

First-Time Setup

  1. Ensure SQL Server is running and update the connection string in dbl.Progress/src/www/appsettings.Development.json.
  2. On first launch, Umbraco will run its install wizard to create the database schema.
  3. 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:

Progress.Baseline.Web/wwwroot/cssCreditUnion/{ClientName}Styles.css

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 Union
  • FirstTechStyles.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

  1. Copy an existing CSS file that is visually closest to the new client's brand.
  2. Rename to {ClientName}Styles.css.
  3. Update brand colours, logo sizing, and any component-specific overrides.
  4. The CSS file is referenced in Umbraco via the websiteConfiguration document type — set the siteStyleSheet property 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:

@using Umbraco.Cms.Core.Models.PublishedContent
@model 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:

  1. Create the element type in Umbraco backoffice (Document Types > Element Types).
  2. 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>
  1. 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

  1. Create the document type in Umbraco backoffice.
  2. 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>
  1. The view filename must match the document type alias (e.g., alias specialPagespecialPage.cshtml).

Creating a New BlockGrid Component

  1. Create an Element Type in Umbraco (Document Types > create with "Is Element" checked).
  2. Add properties (title, image, richText, etc.).
  3. Create the partial view in Views/Partials/blockgrid/Components/{ComponentName}.cshtml.
  4. Add the element type to the relevant BlockGrid data type configuration.

Adding a New Client

  1. CSS: Copy a similar client's CSS file to cssCreditUnion/{ClientName}Styles.css, update colours.
  2. Database: Create a new SQL Server database. Run the migration tool or restore from a template backup.
  3. appsettings: Create client-specific appsettings.{Environment}.json with the correct connection string.
  4. Umbraco config: In the backoffice, set websiteConfiguration > siteStyleSheet to the new CSS filename.
  5. 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 nav
  • Partials/SiteLayout/navigationDynamicMenu2.cshtml — Mega-menu variant
  • Partials/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:

  1. Builddotnet build + npm run build for property editors
  2. Testdotnet test on migration tool tests
  3. Pack — NuGet packages for Baseline.Core, Baseline.Web, CustomPropertyEditors, LoanCalculator
  4. Push — Packages pushed to internal NuGet feed
  5. 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


Migration documentation by Double for Progress Credit Union