Skip to content

Report 29: Models & Libraries Comparison (V8 vs V17)

Generated: 2026-02-27 | Scope: UmbracoModels, ViewModels, ViewPages, Progress.Baseline.Core, ProgressLoanCalculator


1. Overview

Component V8 V17 Migration Approach
UmbracoModels (auto-generated) 249 .generated.cs files 0 (ModelsBuilder disabled) No manual migration -- models regenerated by Umbraco
ViewModels 11 files 5 files 6 removed (deprecated/consolidated), 2 new
ViewPages 2 custom base classes 0 Replaced by @inject in Razor views
Progress.Baseline.Core N/A 20 files (new library) New adapter-pattern architecture
ProgressLoanCalculator N/A (inline in SurfaceController) 9 files (new library) Extracted from v8 controller logic
CommonBondRange model 1 NPoco model 0 Likely moved to service layer or deprecated

2. UmbracoModels: Auto-Generated (No Manual Migration)

2.1 V8 Setup

V8 had ModelsBuilder configured in LiveAppData mode, generating 249 strongly-typed C# models into Progress.Core/UmbracoModels/. Each file follows the same auto-generated pattern:

// V8: AccordionButtonItem.generated.cs (typical example)
//------------------------------------------------------------------------------
// <auto-generated>
//   This code was generated by a tool.
//   Umbraco.ModelsBuilder.Embedded v8.18.15
//   Changes to this file will be lost if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

using Umbraco.Core.Models;
using Umbraco.Core.Models.PublishedContent;
using Umbraco.Web;
using Umbraco.ModelsBuilder.Embedded;

namespace Umbraco.Web.PublishedModels
{
    [PublishedModel("accordionButtonItem")]
    public partial class AccordionButtonItem : PublishedElementModel
    {
        public new const string ModelTypeAlias = "accordionButtonItem";
        // ... properties auto-generated from document type ...
    }
}

2.2 V17 Setup

V17 has ModelsBuilder set to "Nothing" mode:

"Umbraco": {
  "CMS": {
    "ModelsBuilder": {
      "ModelsMode": "Nothing"
    }
  }
}

This means: - No auto-generated model files exist in the v17 codebase - Views use IPublishedContent with .Value<T>("alias") instead of strongly-typed properties - The 249 models are not manually migrated -- they would be regenerated if ModelsBuilder were re-enabled - This is intentional: the v17 architecture uses the adapter pattern (see Section 4) instead

2.3 Impact

The 249 generated models map 1:1 with Umbraco document types. They include content types for: - Page templates (Homepage, ContentPage, NewsArticle, etc.) - Grid/BlockGrid element types (HeroItem, Spotlight, Calculator, etc.) - Nested content / BlockList items (AccordionItem, TabItem, etc.) - Settings nodes (WebsiteConfiguration, SiteStyle, etc.)

If ModelsBuilder is re-enabled later (e.g., SourceCodeManual mode for the planned RCL NuGet packages), it will generate .NET 10 equivalents automatically based on the v17 document types.


3. ViewModels Mapping

3.1 Complete V8 to V17 Mapping

# V8 ViewModel V17 ViewModel Status Notes
1 ArticleResultSet.cs ArticleResultSet.cs Migrated Updated to .NET 10 conventions (initializers, PascalCase)
2 CookiesList.cs CookiesList.cs Migrated Minimal changes
3 notificationList.cs NotificationItem.cs Migrated Renamed, PascalCase, simplified
4 TweetSearchResponse.cs TweetSearchResponse.cs Migrated Kept for Twitter/X feed display
5 GalleryResultSet.cs -- Removed Gallery feature not ported (or uses different pattern)
6 HelpArticleIdList.cs -- Removed Moved to HeadlessContent.cs consolidated model
7 HelpList.cs HeadlessContent.cs Consolidated HelpItem class within HeadlessContent
8 Instagram.cs -- Removed Instagram API scraping deprecated (258 lines of API response models)
9 MobileTermsConditionsList.cs HeadlessContent.cs Consolidated MobileTermsConditionsItem within HeadlessContent
10 PrivacyList.cs HeadlessContent.cs Consolidated PrivacyItem class within HeadlessContent
11 TermsConditionsList.cs HeadlessContent.cs Consolidated TermsConditionsItem class within HeadlessContent

3.2 V17 New ViewModels

New V17 ViewModel Purpose
HeadlessContent.cs Consolidates HelpItem, PrivacyItem, TermsConditionsItem, MobileTermsConditionsItem -- all content fetched from Umbraco Heartcore headless CMS
NotificationItem.cs Clean rewrite of notificationList.cs with PascalCase and string.Empty defaults

3.3 Migration Quality Example

V8 ArticleResultSet.cs:

using Umbraco.Core.Models.PublishedContent;

namespace Progress.Core.ViewModels
{
    public class ArticleResultSet
    {
        public IEnumerable<IPublishedContent> Results { get; set; }
        public List<string> Categories { get; set; }
        public int totalItemCount { get; set; }        // camelCase
        public string selectedCategory { get; set; }    // camelCase, nullable
    }
}

V17 ArticleResultSet.cs:

using Umbraco.Cms.Core.Models.PublishedContent;

namespace Progress.Core.ViewModels;

public class ArticleResultSet
{
    public IEnumerable<IPublishedContent> Results { get; set; } = Enumerable.Empty<IPublishedContent>();
    public List<string> Categories { get; set; } = new();
    public int TotalItemCount { get; set; }             // PascalCase
    public string SelectedCategory { get; set; } = string.Empty;  // Non-nullable
}

Key improvements: - File-scoped namespace (C# 10+) - Umbraco.Core -> Umbraco.Cms.Core namespace - Non-nullable initializers (= Enumerable.Empty<>(), = string.Empty) - PascalCase property names (C# convention)


4. Progress.Baseline.Core: New Architecture

4.1 Purpose

Progress.Baseline.Core is a new class library in v17 that replaces v8's XPath-based site settings access. In v8, views queried settings nodes using:

// V8 pattern (scattered across 50+ views)
var settings = Umbraco.ContentSingleAtXPath("//websiteConfiguration");
var siteName = settings.Value<string>("siteName");

In v17, this is replaced by a service-oriented adapter pattern:

// V17 pattern
@inject ISiteSettingsService SiteSettings
var siteName = SiteSettings.Website?.SiteName;

4.2 Architecture

The library has three layers:

Interfaces (8)          Adapters (8)              Services (2)
ICookieSettings    -->  CookieSettingsAdapter     ISiteSettingsService
IFooterSettings    -->  FooterSettingsAdapter     SiteSettingsService
IGtmSettings       -->  GoogleTagManagerAdapter
IHomepageSlider    -->  HomepageSliderAdapter
IMenuSettings      -->  MenuSettingsAdapter
ISeoSettings       -->  SeoSettingsAdapter
ISiteStyleSettings -->  SiteStyleSettingsAdapter
IWebsiteSettings   -->  WebsiteSettingsAdapter
                   -->  PublishedContentAdapter (base class)

Plus ServiceCollectionExtensions.cs for DI registration.

4.3 Key Design: PublishedContentAdapter

The base adapter wraps IPublishedContent and provides strongly-typed property access without requiring ModelsBuilder:

public abstract class PublishedContentAdapter
{
    protected readonly IPublishedContent? _content;

    protected T? GetValue<T>(string alias) where T : class
    {
        if (_content == null) return default;
        return _content.Value<T>(alias);
    }

    protected T? GetStructValue<T>(string alias) where T : struct { ... }
    protected T GetValueOrDefault<T>(string alias, T fallback) { ... }
}

Concrete adapters like WebsiteSettingsAdapter expose typed properties:

public class WebsiteSettingsAdapter : PublishedContentAdapter, IWebsiteSettings
{
    public string? SiteName => _content?.Name;
    public BlockListModel? SocialLinks => GetValue<BlockListModel>("socialLinks");
    public BlockListModel? CreditUnion => GetValue<BlockListModel>("creditUnion");
}

4.4 ISiteSettingsService

The central service interface provides access to all site-wide configuration:

public interface ISiteSettingsService
{
    IWebsiteSettings? Website { get; }
    ISiteStyleSettings? Styles { get; }
    IMenuSettings? Menu { get; }
    IGoogleTagManagerSettings? GoogleTagManager { get; }
    IFooterSettings? Footer { get; }
    ISeoSettings? Seo { get; }
    IHomepageSliderSettings? HomepageSlider { get; }
    ICookieSettings? Cookie { get; }
    IPublishedContent? LoanBoxesConfiguration { get; }
    IPublishedContent? GoogleMapConfiguration { get; }
    IPublishedContent? SearchPage { get; }
    string? GetSearchPageUrl(IPublishedContent currentPage);
}

4.5 Registration

One line in Program.cs registers the entire subsystem:

builder.Services.AddProgressBaselineCore();  // registers ISiteSettingsService -> SiteSettingsService (Scoped)

4.6 Why This Pattern

Concern V8 Approach V17 Approach
Discoverability Grep for XPath strings Interface contract
Testability Untestable (static factory) Mockable via DI
Caching Manual per-view caching Per-request scope (single query)
NuGet packaging Not possible (coupled to views) Ready for RCL extraction
Multi-tenant Same code, different DB Same interface, per-client adapter

5. ProgressLoanCalculator: Extracted Library

5.1 V8 Origin

In v8, loan calculation logic was inline in SurfaceControllers -- mixed with HTTP concerns, view rendering, and Umbraco API calls. There was no separate calculation library.

5.2 V17 Structure

The v17 ProgressLoanCalculator project is a standalone class library with 9 source files:

File Purpose
ILoanCalculatorEstimator.cs Interface for DI registration
LoanCalculatorEstimator.cs Main calculation engine
LoanCalculationResults.cs Result struct (weekly, bi-weekly, monthly, 4-weekly)
AprCalculator.cs APR (Annual Percentage Rate) calculation
InterestBo.cs Interest business object
InterestRateBo.cs Interest rate business object
ProgressMath.cs Mathematical utility functions
DateProvider.cs Date abstraction for testability
Frequency.cs / LoanFrequency.cs Payment frequency enums

5.3 Interface

public interface ILoanCalculatorEstimator
{
    LoanCalculationResults CalculateRepayments(
        decimal principal,
        int numberOfMonths,
        double annualInterestRate);
}

5.4 Result Structure

public struct LoanCalculationResults
{
    // Weekly
    public decimal WeeklyInterest, WeeklyRepayment, WeeklyTotal, WeeklyApr;
    // Bi-Weekly (Fortnightly)
    public decimal BiWeeklyInterest, BiWeeklyRepayment, BiWeeklyTotal, BiWeeklyApr;
    // Monthly
    public decimal MonthlyInterest, MonthlyRepayment, MonthlyTotal, MonthlyApr;
    // Four-Weekly
    public decimal FourWeeklyInterest, FourWeeklyRepayment, FourWeeklyTotal, FourWeeklyApr;
    // Error handling
    public bool Error;
    public string? ErrorReason;
}

5.5 Registration

// In Program.cs
builder.Services.AddScoped<ILoanCalculatorEstimator, LoanCalculatorEstimator>();

5.6 Benefits of Extraction

  • Testable: Pure calculation logic with no Umbraco dependencies
  • Reusable: Can be used by API controllers, Razor views, or external services
  • Separable: Ready for NuGet packaging for multi-project use

6. V8 ViewPages: Replaced by @inject

6.1 V8 ViewPages

V8 had two custom Razor view base classes that injected services via the static Current.Factory:

ProgressViewPage.cs (used by article-related views):

public abstract class ProgressViewPage<T> : UmbracoViewPage<T>
{
    public readonly IArticleService ArticleService;

    public ProgressViewPage() : this(
        Current.Factory.GetInstance<IArticleService>(),
        Current.Factory.GetInstance<ServiceContext>(),
        Current.Factory.GetInstance<AppCaches>()
    ) { }
}

ProgressGalleryViewPage.cs (used by gallery views):

public abstract class ProgressGalleryViewPage<T> : UmbracoViewPage<T>
{
    public readonly IGalleryService GalleryService;

    public ProgressGalleryViewPage() : this(
        Current.Factory.GetInstance<IGalleryService>(),
        Current.Factory.GetInstance<ServiceContext>(),
        Current.Factory.GetInstance<AppCaches>()
    ) { }
}

6.2 V17 Replacement

In v17, these base classes are unnecessary because ASP.NET Core Razor views support @inject:

// V17 pattern (no custom base class needed)
@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@inject IArticleService ArticleService
@inject ISiteSettingsService SiteSettings

This approach is: - Simpler: No custom base class inheritance chain - More flexible: Each view injects only what it needs - Standard: Uses built-in ASP.NET Core DI, not Umbraco-specific static factory

6.3 CommonBondRange Model

V8 had Progress.Core/Models/CommonBondRange.cs, an NPoco ORM model for postcode validation:

[TableName("CommonBondRange")]
[PrimaryKey("postcode")]
public class CommonBondRange
{
    [Column("postcode")]
    public string Postcode { get; set; }
}

This model is not present in v17. The common bond validation logic may have been moved to a service or an external API (Open Modules), or it may be pending migration.


7. Summary

Component V8 Files V17 Files Status
UmbracoModels 249 0 Not needed (ModelsBuilder=Nothing)
ViewModels 11 5 6 removed/consolidated, 2 new
ViewPages 2 0 Replaced by @inject
Progress.Baseline.Core 0 20 NEW -- adapter pattern for site settings
ProgressLoanCalculator 0 9 NEW -- extracted from SurfaceController
CommonBondRange 1 0 Likely moved to service layer

Architecture Evolution

V8 Architecture:
  Views --> Current.Factory.GetInstance<T>() --> Static service locator
  Views --> @inherits ProgressViewPage       --> Custom base classes
  Views --> ContentSingleAtXPath("//...")     --> XPath queries
  Controllers --> Inline calculations         --> Mixed concerns

V17 Architecture:
  Views --> @inject ISiteSettingsService      --> DI container
  Views --> @inherits UmbracoViewPage         --> Standard base class
  Services --> Adapter pattern               --> Interface-based, testable
  Libraries --> ProgressLoanCalculator       --> Separated concerns

Remaining Actions

Priority Action Effort
P2 Verify CommonBondRange functionality is covered (Open Modules API?) 1h
P3 Consider re-enabling ModelsBuilder in SourceCodeManual mode for type safety 2h
P3 Add unit tests for ProgressLoanCalculator 4h
P3 Document adapter interfaces for RCL NuGet package consumers 2h
Migration documentation by Double for Progress Credit Union