Skip to content

Report 28: Configuration & Infrastructure Comparison (V8 vs V17)

Generated: 2026-02-27 | Scope: Web.config, appsettings, Startup/Program.cs, CI/CD


1. Overview

Aspect V8 V17 Change Impact
Configuration format Web.config (XML) appsettings.json (JSON) Complete rewrite
Config transforms 353 Web.*.config files 6 appsettings.*.json files 345+ transforms need client-specific JSON
Startup Startup.cs (OWIN) Program.cs (minimal API) Complete rewrite
DI framework Current.Factory (Lightinject) builder.Services (MS DI) All registrations rewritten
Build pipeline MSBuild + NuGet + VSBuild dotnet build + Docker Windows -> Linux containers
Deploy pipeline Azure WebApps (IIS) Docker + SSH deploy IIS -> Kestrel/Docker
CI/CD scripts 15 PowerShell scripts Dockerfile + 1 YAML pipeline Simplified

2. Configuration: Web.config vs appsettings.json

2.1 V8 Web.config Structure

The v8 Web.config is a monolithic XML file containing:

  • ASP.NET configuration sections (compilation, httpRuntime, authentication)
  • Umbraco-specific config references (umbracoSettings, ClientDependency, ImageProcessor)
  • AppSettings key-value pairs (Umbraco core, ModelsBuilder, notifications, captcha)
  • Connection strings
  • IIS system.webServer configuration (modules, handlers, URL rewrite rules)
<!-- V8 Web.config excerpt -->
<appSettings>
  <add key="Umbraco.Core.ConfigurationStatus" value="8.18.15" />
  <add key="Umbraco.Core.Path" value="~/office" />
  <add key="Umbraco.ModelsBuilder.ModelsMode" value="LiveAppData" />
  <add key="notificationsCountry" value="Ireland" />
  <add key="notificationsCuName" value="ProgressCreditUnion" />
  <add key="FriendlyCaptchaSecret" value="A1VK6UTS..." />
  <add key="OpenModulesUrl" value="https://api-g1.progress.ie/open-modules/test2" />
</appSettings>
<connectionStrings>
  <add name="umbracoDbDSN" connectionString="Server=cb-sql.double.pt,7071;..." />
</connectionStrings>

2.2 V17 appsettings.json Structure

The v17 configuration uses the ASP.NET Core hierarchical JSON pattern:

{
  "Serilog": {
    "MinimumLevel": { "Default": "Debug" }
  },
  "ConnectionStrings": {
    "umbracoDbDSN": "Server=sql.double.pt,1433;Database=dbl_progress_cms_staging;..."
  },
  "Umbraco": {
    "CMS": {
      "ModelsBuilder": { "ModelsMode": "Nothing" },
      "Global": { "UseHttps": true },
      "Security": { "AllowConcurrentLogins": true }
    }
  },
  "OpenModulesUrl": "https://api-g1.progress.ie/open-modules/test2",
  "FriendlyCaptcha": {
    "Secret": "A1VK6UTS...",
    "SiteKey": "FCMHJ1UO5UEIAM2H",
    "ApiUrl": "https://global.frcapi.com/api/v2/captcha/siteverify"
  },
  "ProgressSettings": {
    "GoogleMapsApiKey": "AIzaSyBK6...",
    "Notifications": {
      "OnlineBanking": "true",
      "Country": "Ireland",
      "CuName": "Progress"
    }
  }
}

2.3 Side-by-Side Comparison

Setting V8 (Web.config) V17 (appsettings.json)
DB connection <connectionStrings><add name="umbracoDbDSN" .../> "ConnectionStrings": { "umbracoDbDSN": "..." }
Umbraco path <add key="Umbraco.Core.Path" value="~/office"/> Not needed (v17 uses /umbraco/)
ModelsBuilder <add key="Umbraco.ModelsBuilder.ModelsMode" value="LiveAppData"/> "Umbraco:CMS:ModelsBuilder:ModelsMode": "Nothing"
HTTPS <add key="Umbraco.Core.UseHttps" value="false"/> "Umbraco:CMS:Global:UseHttps": true
Notifications Flat keys: notificationsCountry, notificationsCuName Nested: ProgressSettings:Notifications:Country
Captcha Flat keys: FriendlyCaptchaSecret, FriendlyCaptchaSiteKey Nested: FriendlyCaptcha:Secret, FriendlyCaptcha:SiteKey
uSync <add key="uSync.ImportAtStartup" value="false"/> "uSync:Sets:Default:Enabled": false
Logging log4net in config/log4net.config Serilog in appsettings.json

2.4 V17 appsettings Files (13 total)

File Purpose
appsettings.json Base configuration (shared defaults)
appsettings.Development.json Local development overrides
appsettings.progress.json Progress CU production config
appsettings.clontarffc.json Clontarf FC client-specific config
appsettings.cuisosolutions.json CU ISO Solutions client config
appsettings.Migration.json Migration tool runtime config
appsettings.TestMigration.json Test migration config
appsettings.CuisoMigration.json CU ISO migration-specific config
appsettings-schema.json JSON schema for validation
appsettings-schema.Umbraco.Cms.json Umbraco CMS schema
appsettings-schema.Umbraco.Forms.json Umbraco Forms schema
appsettings-schema.Umbraco.Licenses.json Licensing schema
appsettings-schema.usync.json uSync schema

3. Startup.cs vs Program.cs

3.1 V8 Startup.cs (OWIN)

The v8 startup is minimal because most DI registration happened via Umbraco's composer system and static Current.Factory:

// V8: Startup.cs (8 lines of actual logic)
[assembly: OwinStartup(typeof(Progress.Web.Startup))]
namespace Progress.Web
{
    public partial class Startup : UmbracoDefaultOwinStartup
    {
        public override void Configuration(IAppBuilder app)
        {
            OwinConfiguration.ConfigureAuth(app);
            ServicePointManager.ServerCertificateValidationCallback += (s, c, ch, e) => true;
            base.Configuration(app);
        }
    }
}

3.2 V17 Program.cs (Minimal API)

The v17 startup is the complete application composition root:

// V17: Program.cs (97 lines)
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

// Pre-startup services
DatabaseCleanupService.CleanupDatabase(builder.Configuration);  // conditional
CacheCleanupService.CleanupCache(builder.Configuration);        // conditional

// DI registrations
builder.Services.AddProgressBaselineCore();  // ISiteSettingsService
builder.Services.AddScoped<IDictionaryService, DictionaryService>();
builder.Services.AddScoped<IArticleService, ArticleService>();
builder.Services.AddScoped<ICalculatorService, CalculatorService>();
builder.Services.AddScoped<ILoanCalculatorEstimator, LoanCalculatorEstimator>();
builder.Services.AddScoped<IHeadlessContentService, HeadlessContentService>();
builder.Services.AddScoped<IGlobalNotificationsService, GlobalNotificationsService>();
builder.Services.AddHttpClient();
builder.Services.AddControllers();
builder.Services.AddSingleton<IBackOfficeTwoFactorOptions, TwoFactorUserGroupOptions>();

// Umbraco pipeline
builder.CreateUmbracoBuilder().AddBackOffice().AddWebsite().AddComposers().Build();

WebApplication app = builder.Build();

// Middleware
app.UseForwardedHeaders(...);
app.BootUmbracoAsync();

// URL rewriting (replaces IIS URL rewrite rules)
app.UseRewriter(new RewriteOptions()
    .AddRewrite(@"^news/category/(.*)/?$", "news-events?newsCategory=$1", true)
    .AddRewrite(@"^sitemap\.xml$", "sitemap", true));

// Umbraco middleware + endpoints
app.UseUmbraco()
    .WithMiddleware(u => { u.UseBackOffice(); u.UseWebsite(); })
    .WithEndpoints(u => { u.UseBackOfficeEndpoints(); u.UseWebsiteEndpoints();
                          u.EndpointRouteBuilder.MapControllers(); });

await app.RunAsync();

3.3 Key DI Registration Differences

Service V8 Registration V17 Registration
Site settings Current.Factory.GetInstance<>() (static) builder.Services.AddScoped<ISiteSettingsService, SiteSettingsService>()
Article service Current.Factory.GetInstance<IArticleService>() builder.Services.AddScoped<IArticleService, ArticleService>()
Calculator Inline in SurfaceController builder.Services.AddScoped<ILoanCalculatorEstimator, LoanCalculatorEstimator>()
Dictionary XPath queries in views builder.Services.AddScoped<IDictionaryService, DictionaryService>()
HTTP clients System.Net.WebClient builder.Services.AddHttpClient()
2FA N/A (no 2FA in v8) builder.Services.AddSingleton<IBackOfficeTwoFactorOptions, TwoFactorUserGroupOptions>()
Auth OWIN cookie auth Umbraco built-in + TwoFactorAuthComposer
Logging log4net Serilog (configured in appsettings.json)
URL rewrite IIS web.config <rewrite> rules app.UseRewriter() middleware

4. CI/CD Comparison

4.1 V8 Build Pipeline

Platform: Azure DevOps, Windows agent (vmImage: windows-latest)

# V8: azure-build-pipelines-webApps.yml
pool:
  vmImage: 'windows-latest'

steps:
  - task: gitversion/setup@3          # Semantic versioning
  - task: NuGetToolInstaller@1        # NuGet CLI
  - task: Cache@2                     # NuGet package cache
  - task: NuGetCommand@2              # NuGet restore
  - task: VSBuild@1                   # MSBuild via Visual Studio
    inputs:
      msbuildArgs: '/p:DeployOnBuild=true /p:WebPublishMethod=Package ...'

Key characteristics: - Full .NET Framework build using VSBuild (not dotnet build) - WebDeploy packaging for Azure App Service deployment - NuGet restore with caching - GitVersion for semantic versioning - Produces a .zip deployment package

4.2 V8 Deployment Scripts (15 PowerShell scripts)

Script Purpose
preUmbracoDeployment.ps1 Pre-deploy checks and preparation
postUmbracoDeployment.ps1 Post-deploy validation
RenameUmbracoFolderOffice.ps1 Rename /umbraco to /office (security)
RemoveUnusedConfigTransformFiles.ps1 Clean up unused config transforms
RemoveuSyncFolder.ps1 Clean uSync files post-deploy
Copy51DegreesFile.ps1 Copy device detection database
IncrementClientDependency.ps1 Bust client-side caches
UmbracoErrorPage.ps1 Set up custom error pages
CleanUp.ps1 General deployment cleanup
ExecRemoteScript.ps1 Execute scripts on remote server
addEnvironmentTags.ps1 Tag deployment with environment info
exportPipelineVariables.ps1 Export build variables
importPipelineVariables.ps1 Import build variables
setRunName.ps1 Set Azure DevOps run name
updateCreditUnionsList.ps1 Update client list for multi-tenant deploy

4.3 V17 Build & Deploy Pipeline

Platform: Azure DevOps, Linux agent (vmImage: ubuntu-latest) + Docker

# V17: azure-deploy-uat.yml
trigger:
  - main

stages:
  - stage: Build
    jobs:
      - job: Build
        pool:
          vmImage: ubuntu-latest
        steps:
          - task: Docker@2        # Build Docker image
            inputs:
              command: "build"
              Dockerfile: "**/Dockerfile"
          - task: Docker@2        # Push to registry
            inputs:
              command: push
              repository: $(imageUmbraco)
              containerRegistry: $(containerRegistry)
          - task: SSH@0           # Deploy via SSH
            inputs:
              inline: |
                cd /docker/clients/progress
                docker-compose pull
                docker-compose up -d

4.4 V17 Dockerfile

The Dockerfile is a multi-stage build:

# Stage 1: Build
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
# Install Node.js 18 for custom property editor TypeScript build
RUN apt-get install -y nodejs
# Restore, copy, publish
RUN dotnet publish src/www/www.csproj -c ${CONFIGURATION} -o /app/publish

# Stage 2: Runtime
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS final
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "www.dll"]

4.5 Pipeline Comparison Summary

Aspect V8 V17
Agent OS Windows Linux (Ubuntu)
Build tool MSBuild (VSBuild task) dotnet publish
Package format WebDeploy .zip Docker image
Registry Azure App Service Private Docker registry (registry.double.pt)
Deploy target Azure App Service (IIS) Docker Compose on Linux VM
Deploy method WebDeploy / Azure CLI SSH + docker-compose up -d
Scripts needed 15 PowerShell scripts 0 (all in Dockerfile)
Node.js build Not needed Required (Vite/TypeScript for custom editors)
Config transforms 353 Web.config transforms Environment variables / per-client appsettings

5. Per-Client Configuration Gap

5.1 The Problem

V8 has 353 Web.config transform files (Web.{ClientName}.{Environment}.config), each containing client-specific overrides for:

  • Database connection strings
  • Notification settings (CU name, country, county)
  • uSync import/export flags
  • Captcha keys
  • API endpoints
  • Feature flags

Example (Web.1stclasscu.Prod.config):

<appSettings xdt:Transform="Replace">
  <add key="notificationsCountry" value="United Kingdom" />
  <add key="notificationsCounty" value="Glasgow" />
  <add key="notificationsCuName" value="1stClassCreditUnion" />
  <add key="uSync.ImportAtStartup" value="true" />
  <!-- ... 60+ more keys ... -->
</appSettings>
<connectionStrings>
  <add name="umbracoDbDSN" connectionString="Server=...;Database=1stclasscu;..." />
</connectionStrings>

5.2 V17 Current State

Only 4 client-specific appsettings exist:

File Client
appsettings.progress.json Progress CU (IE)
appsettings.clontarffc.json Clontarf FC (IE)
appsettings.cuisosolutions.json CU ISO Solutions
appsettings.CuisoMigration.json CU ISO (migration variant)

5.3 Migration Path

Each of the 353 transforms needs to become a per-client appsettings.{client}.json file. The pattern is:

{
  "ConnectionStrings": {
    "umbracoDbDSN": "Server=...;Database={client_db};..."
  },
  "ProgressSettings": {
    "Notifications": {
      "Country": "United Kingdom",
      "County": "Glasgow",
      "CuName": "1stClassCreditUnion"
    }
  }
}

The ASPNETCORE_ENVIRONMENT variable selects which appsettings file is loaded. In Docker deployments, this is set per-container in docker-compose.yml.

Gap: ~170 client configs not yet created

This is a mechanical task that can be automated: parse each Web.{client}.{env}.config XML file, extract the key-value pairs, and generate the equivalent appsettings.{client}.json. A script for this should be part of the Migration Orchestrator plan.


6. Summary

Area Migration Status Notes
Base config (Web.config -> appsettings.json) DONE Core settings migrated
Startup (Startup.cs -> Program.cs) DONE All DI registrations, middleware, URL rewrite
Build pipeline (MSBuild -> Docker) DONE Multi-stage Dockerfile with Node.js
Deploy pipeline (WebDeploy -> Docker Compose) DONE SSH-based docker-compose deploy
Per-client configs (353 transforms) 4/~170 DONE Major gap -- needs automation
PowerShell scripts (15 scripts) OBSOLETE No longer needed with Docker
IIS URL rewrite rules DONE Converted to app.UseRewriter()
Logging (log4net -> Serilog) DONE Configured in appsettings.json

Key Architectural Wins in V17

  1. Containerized deployment: No more IIS dependency; runs on any Docker host
  2. Simplified build: 1 Dockerfile replaces MSBuild + NuGet + 15 PowerShell scripts
  3. Structured configuration: Hierarchical JSON with schema validation replaces flat XML keys
  4. Native DI: Microsoft.Extensions.DependencyInjection replaces static Current.Factory calls
  5. Cross-platform: Linux-based build agent and runtime (was Windows-only)

Remaining Actions

Priority Action Effort
P1 Generate ~170 client-specific appsettings.{client}.json from v8 transforms 4h (scripted)
P2 Create per-client docker-compose.yml templates 2h
P3 Add health check endpoint for container orchestration 1h
P3 Add staging/production pipeline variants 2h
Migration documentation by Double for Progress Credit Union