Skip to Content
image description
image description

Case Study: Modernizing ACLS Online

At a glance

The project at a glance

Client ACLS Online (AMRI), online ACLS, BLS and PALS certification for healthcare professionals
Engagement Full application modernization, discovery through cutover
Timeline Scoping April 2026 · first commit May 14 · production launch October 14, 2026
Before ASP.NET MVC 4, .NET Framework 4.7, an old proprietary CMS (no releases since ~2017), jQuery 1.8, Knockout 2.2, Amazon S3, hand-built Windows hosting
After .NET 10 LTS, Umbraco 17, Linux App Service, Azure SQL, Azure Blob, Azure Service Bus + Functions, Azure Communication Services, everything defined in Bicep
Delivered 518 merged pull requests · 4,480+ automated tests · 12 Bicep infrastructure modules · 531 legacy URLs mapped
The challenge

A revenue platform built on abandoned software

ACLS Online sells certification exams to doctors, nurses and paramedics. Every order, exam, certificate and renewal reminder runs through one web application, and that application hadn’t really changed in a decade.

The worst of it was the CMS, an old proprietary product. It hadn’t shipped a release since about 2017, parts of it were encrypted so nobody could modify them, and no security patches were coming. Editors couldn’t work around it either. A new page or a typo fix became a developer ticket.

Underneath, ASP.NET MVC 4 on .NET Framework 4.7 was out of mainstream support, which limited hosting, tooling, hiring and security posture. The front end ran jQuery 1.8 and Knockout 2.2, both over ten years old with known vulnerabilities. Azure resources had been built by hand in the portal. Media sat in Amazon S3 behind the abandoned CMS. Nothing could be rebuilt from source.

And the business couldn’t stop for any of this. Customers had to keep buying exams, passing them and downloading certificates the whole way through, and search rankings on 531 public URLs had to survive the move. A redesign was off the table.

Our approach

How we approached it

We wrote the plan and ran the discovery spikes before writing application code. One rule held the whole way: change the platform underneath and leave the customer experience alone.

  1. Plan on paper first. Five planning documents covered scope, requirements, architecture, roadmap and test strategy, with phases, estimates and risks for each. When the plan changed later, we logged the change as a numbered delta with its hours impact.
  2. Spike the risky parts. More than a dozen Phase 0 spikes went after the unknowns: NHibernate 5 on .NET 10, the PayFlow Pro payment gateway, PDF certificate generation, and a full inventory of 396 Razor views and 531 URLs. The Razor sample turned out to be the biggest underestimate in the plan. We caught it during discovery and rebudgeted then, not halfway through the build.
  3. Put every piece of data in exactly one place. Editorial content went into the CMS. A handful of editor accounts became CMS members. Every transactional record (orders, exams, certificates, users) stayed in the existing app database. A CMS is the wrong place to keep orders.
  4. Port the design pixel for pixel. The goal was for customers not to notice. Playwright tests and human review compared each ported page against golden-master screenshots of the legacy site.
  5. Fix cheap debt on the way through. If a piece of debt took under 30 minutes to fix during the port, we fixed it. Code that worked but was merely unfamiliar we left alone.
  6. Let evidence pick the stack. We’d planned on Umbraco 15. Unpatched CVEs in 15.x moved us to Umbraco 17 on .NET 10 LTS. The PDF spike tried QuestPDF, and we kept ABCpdf as the safer fit for certificate output.

The build ran on Spec-Driven Development, with Claude Code as the AI coding agent. Plans, architecture decision records and 70+ written findings served as the source of truth for each piece of work. That discipline is how the team merged 518 reviewed pull requests in five months.

What we built

What we built

One ASP.NET Core application with the CMS running inside it, two databases split by purpose, and an email pipeline that stores no credentials.

ACLS Online architecture: customers and editors use one .NET 10 app on Azure App Service, backed by Azure SQL, Blob Storage, Service Bus, Functions and Communication Services, all defined in Bicep

Because the CMS is co-hosted rather than headless, the existing HTML, CSS and JavaScript ported as-is. Customers still pay through PayPal PayFlow Pro and get the same certificate PDFs and printable cards.

Editors now work in Umbraco 17, which replaced the old CMS for pages, blog, FAQs, group FAQs, navigation and media. The schema lives in source control through uSync. Seven admin screens moved into the Umbraco backoffice, and the rest were rebuilt as modern MVC screens on the same data. jQuery and Knockout came up from their decade-old versions, and every page still looks identical.

Some of the work is less visible:

  • Customers can sign in with Google or Microsoft as well as a password. We never link accounts by matching email, which closes a common account-takeover path.
  • Order, exam and reminder emails go through Azure Service Bus and Functions to Azure Communication Services, authenticated by managed identity. There’s no password to leak or rotate.
  • Login, registration and password reset are rate limited. Password hashing is stronger, and authorization checks stop users from editing anyone else’s records.
Engineering rigor

Nothing ships on trust

The new codebase has more test code (about 123,000 lines) than application code (about 97,000). That’s on purpose. A port can look identical and still break checkout in ways nobody sees until revenue drops.

Practice What it means in this codebase
Four-category testing Every behavior-changing method is tested for the happy path, the unhappy path, adversarial input (cross-account access, parameter tampering), and real wiring against a database or HTTP pipeline
4,480+ automated tests Unit, integration and end-to-end, across 10 test projects; 105 Playwright files compare rendered pages to the legacy screenshots
Every bug gets a regression test The test fails before the fix and passes after, so the same bug cannot quietly return
Warnings are errors StyleCop, Roslynator, Meziantou and the .NET analyzers fail the build on any warning; nullable reference types on throughout
Local gate before push A pre-push hook runs format checks, build, tests and Bicep compilation before code leaves a laptop
Infrastructure as code 12 Bicep modules define dev, staging and prod; PSRule for Azure and a what-if preview gate every infrastructure change; no portal hand-edits
Pipelines per environment Azure DevOps pipelines build once and promote the same artifact to dev, staging and production, for both the web app and the email Function App
Decisions on record Architecture decision records and dated findings explain why each non-obvious choice was made, for the next engineer who touches it

Testers reviewed every ticket on an auto-deployed cloud dev environment before it moved on, so QA always ran against real Azure services.

Migration and cutover

A rehearsed, reversible cutover

The new platform ran in parallel on its own domain for weeks before any customer traffic moved. Cutover itself was a written, ordered checklist with a rollback path at every step.

Content came over by pipeline. An Extract → Transform → Import job read every page, blog post, FAQ and SEO field out of the old CMS and wrote it into Umbraco. Because re-runs were idempotent, we could rehearse the import as many times as we liked. Media had its own import pipeline from Amazon S3 into Azure Blob Storage, with a coverage gate that listed anything missing.

Customers kept their passwords. We wrapped the legacy hashes in a stronger algorithm in place, so nobody had to reset and the weak hashes no longer exist at rest.

Search and marketing came through intact. We inventoried all 531 legacy URLs and mapped 37 redirects, then checked SEO titles and descriptions for parity across the full sitemap. Non-production hosts send a noindex header, so a test site can’t outrank the real one. Google Tag Manager and the Google, Microsoft and Meta conversion events fire on the same URLs as before.

For the switch, we added a site-wide transaction freeze to the legacy app. Purchases and exams paused while sign-in and certificate downloads kept working. Then came the final database copy (11.4 GB in about six minutes), verification and the DNS change. Written rollback plans covered the hours before and after go-live, down to how we’d reconcile orders taken on the new site back into the old one.

Results

Results

Editors at ACLS Online now publish without a developer. The platform underneath has vendor support into the late 2020s, and it runs on smaller Azure tiers than the system it replaced.

Area Before After
Framework support .NET Framework 4.7, out of mainstream support .NET 10 LTS + Umbraco 17, actively patched
Content editing Every change routed through a developer Editors publish in the Umbraco backoffice; seven admin screens moved there too
Web hosting Windows App Service, P2v2 Linux App Service, P1v3
App database Azure SQL S3 (100 DTU) Azure SQL S2 (50 DTU)
Email credentials SendGrid SMTP password to store and rotate Zero secrets: Azure Communication Services with managed identity
Infrastructure Built by hand in the portal Every environment rebuilt from Bicep in source control
Sign-in Email/password, legacy Facebook login Email/password plus Google and Microsoft sign-in
Release safety Hand-built environments, no visual-parity tests Gated pipelines, 4,480+ tests, staging slot before production

Launch also brought better visibility. A payment reconciliation screen compares each order against PayPal’s record, and application logs feed Application Insights alerts.

Is your application next?

Is your application next?

If two or more of these describe a system your business runs on, waiting is costing you more every year.

  • It runs on .NET Framework, an old PHP or Java version, or a framework whose vendor stopped shipping releases.
  • A CMS, plugin or library in the stack is abandoned, and nobody can patch it.
  • Small content or configuration changes need a developer.
  • Nobody could rebuild your production environment from source if it disappeared tomorrow.
  • You’ve put off upgrades because nobody can say what would break.

That’s the work we do. Discovery and spikes first, then a 1:1 port customers don’t notice, with tests to prove it and infrastructure you own as code. LoadSys has been shipping software for 20+ years, for 400+ clients.

image description
image description

Talk to an AI Engineer.

We’ll look at your stack and tell you plainly what’s aging and what modernizing it would take. Email info@loadsys.com.

Back to top