Skip to content

Report 20: v8 to v17 Migration Comparison Index

Last Updated: 2026-02-27 Project: Progress Credit Union -- Umbraco v8 to v17 Migration

This is the master index for the comparison report series (Reports 20-29). Each report provides a detailed side-by-side analysis of a specific migration category, showing exactly how v8 patterns were transformed for v17. Developers should read this index first to understand the 12 core transformation patterns (the "Rosetta Stone"), then navigate to specific reports for full file-by-file comparisons.


Dashboard Statistics

Category v8 Count v17 Count Coverage Report
Page templates 48 .cshtml 46 (2 obsolete) 96% Report 21
Component views 91 DTGE + 40 sub-partials 121 blockgrid + 49 sub-partials 93% Report 22
Layout / shared partials 20 SiteLayout + 5 BlockList + 79 Forms 18 SiteLayout + 7 blocklist + 13 Forms 85% Report 23
Controllers / Services 8 SurfaceControllers + 14 Services 7 ViewComponents + 16 Services 100% Report 24
Security / 2FA 21 files (plugins + core) 8 C# files (Security/) 100% Report 25
Custom editors 52 App_Plugins dirs 32 TS editor dirs (70 .ts files) 100% Report 26
Static assets 44 JS + 377 CSS 58 JS + 233 CSS 95% Report 27
Configuration 12 Web.config + transforms 39 appsettings*.json 100% Report 28
Models 249 generated + 16 ViewModels auto-generated + 5 ViewModels 100% Report 29

Overall migration completeness: ~95%


Twelve Transformation Patterns ("Rosetta Stone")

Every file in the migration uses one or more of these patterns. Master these 12 and you can read any migrated file.


P1 -- View Inheritance

The base class and model binding changed for every Razor view in the project.

v8 (psCreditUnion/Progress.Web/Views/Partials/Grid/Editors/DocTypeGridEditor/heroItem.cshtml):

@inherits UmbracoViewPage<ContentModels.HeroItem>
@using ContentModels = Umbraco.Web.PublishedModels;
@using Umbraco.Web.Models
@{
    var heading = "";
    var link = Model.Value<IEnumerable<Link>>("link");

v17 (dbl.Progress/src/www/Views/Partials/blockgrid/Components/heroItem.cshtml):

@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage<Umbraco.Cms.Core.Models.Blocks.BlockGridItem>
@using Umbraco.Cms.Core.Models
@using Umbraco.Cms.Core.Models.PublishedContent
@{
    var content = Model.Content;
    var heading = content.Value<string>("heading") ?? "";

Files affected: 170+ (all views) Referenced in: Reports 21, 22, 23


P2 -- Media Picker (IPublishedContent to MediaWithCrops)

v8 used IPublishedContent for media; v17 uses MediaWithCrops with .MediaUrl().

v8:

var image = "";
if (Model.Value<IPublishedContent>("firstCurve") != null)
{
    firstCurve = Model.Value<IPublishedContent>("firstCurve").Url.ToString();
}

v17:

// MediaPicker3 single-pick returns MediaWithCrops (not IPublishedContent)
var firstCurveMedia = content.Value<MediaWithCrops>("firstCurve");
var firstCurve = firstCurveMedia?.MediaUrl() ?? "";

Files affected: 20+ component partials Referenced in: Reports 21, 22


P3 -- XPath to Dependency Injection

v8 used Umbraco.ContentSingleAtXPath() to query content nodes. v17 uses @inject with cached services.

v8 (psCreditUnion/Progress.Web/Views/master.cshtml):

@inherits Umbraco.Web.Mvc.UmbracoViewPage
@{
    var settings = Umbraco.ContentSingleAtXPath("//websiteConfiguration");
    var gtsettings = Umbraco.ContentSingleAtXPath("//googleTagManagerConfiguration");
    var stylesSettings = Umbraco.ContentSingleAtXPath("//siteStyleConfiguration");
    var menuSettings = Umbraco.ContentSingleAtXPath("//menuConfiguration");
    var menuStyle = menuSettings.Value<string>("menuStyle");

v17 (dbl.Progress/src/www/Views/master.cshtml):

@inherits Umbraco.Cms.Web.Common.Views.UmbracoViewPage
@inject ISiteSettingsService SiteSettings
@inject IDictionaryService Dictionary
@{
    var website = SiteSettings.Website;
    var styles  = SiteSettings.Styles;
    var menu    = SiteSettings.Menu;
    var gtm     = SiteSettings.GoogleTagManager;
    var menuStyle = menu?.MenuStyle?.ToLowerInvariant();

Files affected: 15+ (all templates that queried settings nodes) Referenced in: Reports 21, 24


v8 used the ClientDependency framework for CSS/JS bundling with priority ordering. v17 uses standard HTML <link> tags with explicit ordering.

v8 (psCreditUnion/Progress.Web/Views/master.cshtml):

@using ClientDependency.Core.Mvc
@{
    Html.RequiresCss("~/vendor/bootstrap/css/bootstrap.min.css", 0);
    Html.RequiresCss("~/vendor/menu4/css/style.css", 2);
    Html.RequiresCss("~/vendor/slick/slick.css", 3);

v17 (dbl.Progress/src/www/Views/master.cshtml):

@* Priority 0: Bootstrap base *@
<link rel="stylesheet" href="~/vendor/bootstrap/css/bootstrap.min.css" />
<link rel="stylesheet" href="~/vendor/bootstrap/css/bootstrap-select.css" />

@* Priority 2: Menu styles *@
<link rel="stylesheet" href="~/vendor/menu4/css/style.css" />

Files affected: 6 layout templates (master, MasterLogin, MasterThirdParty, etc.) Referenced in: Reports 21, 23, 27


P5 -- Grid to BlockGrid Rendering

v8 used Html.GetGridHtml() helper to render the entire grid. v17 uses BlockGridModel with partial rendering through a multi-section grid partial.

v8 (psCreditUnion/Progress.Web/Views/contactus.cshtml):

@inherits Umbraco.Web.Mvc.UmbracoViewPage
@using Progress.Core.Extensions
<div class="container content-page">
    @Html.GetGridHtml(Model, "grid", "ContactUsGrid", true, false)
</div>

v17 (dbl.Progress/src/www/Views/contactus.cshtml):

@using Umbraco.Cms.Core.Models.Blocks
<div class="container content-page">
    @{
        var grid = Model.Value<BlockGridModel>("grid");
        if (grid != null)
        {
            @await Html.PartialAsync("blockgrid/_MultiSectionGrid", grid,
                new ViewDataDictionary(ViewData) {
                    { "UseContainer", true },
                    { "ContainerClass", "container" }
                }, true)
        }
    }
</div>

Files affected: 45 page templates Referenced in: Reports 21, 22, 12, 13


P6 -- SurfaceController to ViewComponent

v8 used SurfaceController (MVC controller) with ActionResult. v17 uses ViewComponent with async InvokeAsync().

v8 (psCreditUnion/Progress.Core/SurfaceControllers/CookieSurfaceController.cs):

using Umbraco.Web.Mvc;
public class CookieSurfaceController : SurfaceController
{
    public ActionResult CookiesActionResult()
    {
        var GlobalCookiesService = new GlobalCookiesService();
        List<CookiesItem> model = GlobalCookiesService.GetCookies();
        return PartialView("~/Views/Partials/cookies/_main.cshtml", model);
    }
}

v17 (dbl.Progress/src/www/ViewComponents/CookiesViewComponent.cs):

using Microsoft.AspNetCore.Mvc;
public class CookiesViewComponent : ViewComponent
{
    private readonly IGlobalCookiesService _globalCookiesService;
    public CookiesViewComponent(IGlobalCookiesService globalCookiesService)
    {
        _globalCookiesService = globalCookiesService;
    }
    public async Task<IViewComponentResult> InvokeAsync()
    {
        List<CookiesItem> model = await _globalCookiesService.GetCookies();
        return View("~/Views/Partials/cookies/_main.cshtml", model);
    }
}

Files affected: 8 controllers migrated to 7 ViewComponents Referenced in: Reports 24


P7 -- Service Instantiation to Constructor Injection

v8 used new to instantiate services. v17 uses constructor injection via DI.

v8:

public ActionResult CookiesActionResult()
{
    var GlobalCookiesService = new GlobalCookiesService();
    model = GlobalCookiesService.GetCookies();
}

v17:

private readonly IGlobalCookiesService _globalCookiesService;

public CookiesViewComponent(IGlobalCookiesService globalCookiesService)
{
    _globalCookiesService = globalCookiesService;
}

Files affected: 14 services + 8 controllers Referenced in: Reports 24


P8 -- DropDown.Flexible JSON Wrapping

v8 stored dropdown values as plain strings ("medium"). v17's FlexibleDropdownPropertyValueConverter expects JSON arrays (["medium"]). Migrated data may be plain strings, so every read is wrapped in try-catch.

v8:

var height = Model.Value<string>("height");
if (height != null)
{
    // always a plain string like "medium"
}

v17 (dbl.Progress/src/www/Views/standardPageWow.cshtml):

// height is Umbraco.DropDown.Flexible - migrated v8 data may be a plain string
// instead of JSON array
var height = "medium";
try { height = Model.Value<string>("height") ?? "medium"; } catch { }

Files affected: 20+ files with dropdown properties (gradient, buttonStyle, imageLocation, layout, layoutStyle, flip, display, height, zoomLevel) Referenced in: Reports 22, 29


P9 -- ColorPicker Double-Hash Fix

v8 stored color values WITHOUT # prefix (e47d51). v8 templates used #@color in style strings. After migration, EyeDropper stores WITH # prefix, producing ##e47d51. Fix: TrimStart('#') on every color read.

v8 (psCreditUnion/Progress.Web/Views/contactus.cshtml):

var backgroundColorNew = Model.Value<String>("headerColour");
if (backgroundColorNew != "")
{
    backgroundColor = backgroundColorNew;
}
style = "background-color:#" + backgroundColor;

v17 (dbl.Progress/src/www/Views/contactus.cshtml):

var backgroundColorNew = Model.Value<string>("headerColour") ?? "";
if (!string.IsNullOrEmpty(backgroundColorNew))
{
    backgroundColor = backgroundColorNew.TrimStart('#');
}
style = $"background-color:#{backgroundColor}";

Files affected: 20+ files (15 page templates, 5 video partials, 3 accordion/FAQ partials) Referenced in: Reports 21, 22


v8 Multi-URL Picker returned IEnumerable<Link>. v17 single-link properties return Umbraco.Cms.Core.Models.Link directly.

v8 (psCreditUnion/Progress.Web/Views/Partials/Grid/Editors/DocTypeGridEditor/heroItem.cshtml):

@using Umbraco.Web.Models
var link = Model.Value<IEnumerable<Link>>("link");
if (link != null && link.Any())
{
    var firstLink = link.First();
    linkText = firstLink.Name;
}

v17 (dbl.Progress/src/www/Views/Partials/blockgrid/Components/buttonWidgetControl.cshtml):

var link = content.Value<Umbraco.Cms.Core.Models.Link>("link");
if (link != null)
{
    <a href="@link.Url" target="@link.Target">@link.Name</a>
}

Files affected: 30+ component views with link properties Referenced in: Reports 22, 23


P11 -- Bootstrap 4 to Bootstrap 5 Attributes

Bootstrap 5 renamed all data-* attributes to data-bs-* and changed several utility classes.

v8:

<a data-toggle="collapse" data-parent="#jag-theme-5"
   href="#collapse5_@i" aria-expanded="false"
   aria-controls="collapse5_@i">
<span class="sr-only">Toggle</span>

v17:

<a data-bs-toggle="collapse" data-bs-parent="#jag-theme-5"
   href="#collapse5_@i" aria-expanded="false"
   aria-controls="collapse5_@i">
<span class="visually-hidden">Toggle</span>

Key replacements: data-toggle to data-bs-toggle, data-target to data-bs-target, data-dismiss to data-bs-dismiss, data-parent to data-bs-parent, data-slide to data-bs-slide, sr-only to visually-hidden, ml-/mr- to ms-/me-, pl-/pr- to ps-/pe-, float-left/float-right to float-start/float-end.

Files affected: 1,142 replacements across 159 files Referenced in: Reports 15, 17, 18, 22, 23, 27


P12 -- Async Partial Rendering

v8 used synchronous Html.CachedPartial(). v17 uses await Html.CachedPartialAsync() with TimeSpan cache duration.

v8 (psCreditUnion/Progress.Web/Views/master.cshtml):

@Html.CachedPartial("SiteLayout/headerMenu4", Model, 3600, true)
@Html.CachedPartial("SiteLayout/headerMenu3", Model, 3600, true)
@Html.CachedPartial("SiteLayout/header", Model, 3600, true)

v17 (dbl.Progress/src/www/Views/master.cshtml):

@await Html.CachedPartialAsync("SiteLayout/headerMenu4", Model,
    TimeSpan.FromHours(1), cacheByPage: true)
@await Html.CachedPartialAsync("SiteLayout/headerMenu3", Model,
    TimeSpan.FromHours(1), cacheByPage: true)
@await Html.CachedPartialAsync("SiteLayout/header", Model,
    TimeSpan.FromHours(1), cacheByPage: true)

Files affected: 6 layout templates with cached partials Referenced in: Reports 21, 23


Report Navigation

# Report Description
20 This document Master index, dashboard stats, 12 transformation patterns
21 Page Template Comparison Side-by-side comparison of all 48 v8 page templates to their v17 equivalents. Covers header images, grid rendering, color handling, and layout changes.
22 Component View Comparison All 91 DTGE component views mapped to 121 blockgrid components. Covers BlockGridItem model binding, MediaWithCrops, dropdown try-catch, and link model changes.
23 Layout & Shared Partial Comparison SiteLayout partials (headers, footers, navigation), BlockList components, and Forms theme partials. Covers ClientDependency removal and async rendering.
24 Controller & Service Comparison SurfaceController to ViewComponent migration, service interface extraction, and DI registration in Program.cs.
25 Security & 2FA Comparison v8 AngularJS 2FA plugins (21 files) consolidated to 8 C# files using Umbraco's built-in 2FA provider architecture.
26 Custom Editor Comparison 52 AngularJS App_Plugins directories replaced by 32 TypeScript/Lit editor directories using the Umbraco Backoffice management API.
27 Static Asset Comparison JavaScript and CSS file inventory. Bootstrap 4.4.1 to 5.3.3 upgrade, jQuery plugin audit, and CSS consolidation.
28 Configuration Comparison Web.config XML hierarchy flattened to appsettings.json. Connection strings, SMTP, custom app settings, and Umbraco-specific configuration sections.
29 Model Comparison 249 ModelsBuilder-generated models (now auto-generated at build) and 16 ViewModels reduced to 5 hand-maintained ViewModels.

Known Remaining Gaps

The migration is approximately 95% complete. The following items remain open and are detailed in their respective reports:

  • ~20 component views still need final review for missing sub-partials (Report 22)
  • Calculator slider UI needs JavaScript refactoring for Bootstrap 5 compatibility (Reports 22, 18)
  • Forms theme partials reduced from 79 to 13; remaining 66 are Umbraco Forms defaults that ship with the package (Report 23)
  • Hardcoded API keys have been externalized to appsettings.json but need Azure Key Vault integration for production (Report 28)
  • Content usage audit needed for DataType 2236 and other low-usage types to determine if views are needed (Report 22)
  • Settings deduplication deferred -- per-datatype element types are confirmed correct (Reports 11, 16)
  • ModelsBuilder SourceCodeManual mode planned for Progress.Baseline.Core NuGet RCL (Report 29)

For a full prioritized list, see Report 00: Master Summary.


Cross-Reference: Patterns by Report

Pattern 21 22 23 24 25 26 27 28 29
P1 View Inheritance X X X
P2 Media Picker X X
P3 XPath to DI X X
P4 ClientDependency X X X
P5 Grid to BlockGrid X X
P6 SurfaceController X
P7 Service DI X
P8 DropDown.Flexible X X
P9 ColorPicker X X
P10 Link Model X X
P11 Bootstrap 5 X X X
P12 Async Partials X X
Migration documentation by Double for Progress Credit Union