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:
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¶
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 |