Case Study
Multi-Tenant ISV Platform Modernisation
Modernising a legacy .NET monolith to .NET 8 on Azure App Service — with full Azure DevOps CI/CD, zero-downtime deployment slot swapping, and production-safe rollback across six live instances.
The Challenge
Legacy Code, Risky Deployments
A multi-tenant independent software vendor ran a centralised API platform serving dozens of distributor tenants — B2B ERP workflows, branded B2C microsite storefronts, payment processing, and desktop sync clients — from a single legacy .NET codebase on Azure App Service.
The platform had grown to 400+ API endpoints but was still carrying patterns from its .NET Framework origins: legacy package references, ad-hoc HttpClient instantiation causing socket exhaustion under load, and no build-time guardrails against async deadlocks. The Azure App Service runtime stack had not been consistently configured for the modernised target framework.
Deployments were manual and risky. Six production App Service instances sat behind public load-balanced domains, yet there was no blue-green process, no automated health verification after release, and no way to roll back cleanly if one instance updated while another failed. SQL schema changes ran separately from application deploys. Production releases required confidence the entire fleet would stay on the same version.
Codebase Work
.NET 8 Runtime & Reliability Fixes
Before pipelines could be trusted in production, the application itself needed to run correctly on the modern stack and behave reliably under tenant load.
Runtime stack upgrade
The API was brought onto .NET 8 with the ASP.NET Core minimal hosting model. Every deploy now enforces the correct Azure App Service runtime stack (DOTNETCORE|8.0) on both the staging slot and production — verified again immediately after each slot swap so a misconfigured instance cannot reach live traffic.
Tenancy remains unchanged at the architecture level: one shared application resolves the current tenant from JWT claims or public microsite request keys, then routes to that tenant’s isolated SQL database at runtime.
Bug fixes & hardening
- Replaced
new HttpClient()anti-patterns across payment and integration flows with IHttpClientFactory, eliminating socket exhaustion and stale DNS under concurrent tenant traffic. - Added build-time async safety analyzers that fail the build on blocking calls (
.Result,.Wait()) andasync voidhandlers. - Centralised exception handling middleware for consistent JSON error responses and per-tenant DB logging.
- Lightweight
/healthendpoint with no database dependency — suitable for pipeline probes and load balancer checks without false negatives from tenant DB latency.
DevOps
Azure DevOps Pipeline Architecture
We built four separate environment pipelines with reusable YAML templates — each pipeline scoped to its own Azure service connection and resources, following least-privilege branch promotion from development through to production.
Four pipelines, one promotion path
- Development — build, unit tests, and security scan only; no deployment
- Testing — deploys to two App Service instances with full slot-swap flow
- Staging — same pattern with enhanced security scanning before production
- Production — six App Service instances across three public API surfaces, manual approval gates, and blocks on critical vulnerability findings
Fifteen reusable templates cover build, Trivy and .NET package vulnerability scanning, slot preparation, deploy, sequential swap, CORS configuration, health checks, and rollback. SQL migrations run through a separate pipeline family with versioned scripts and migration history tracking.
Health Checks
Verification at Every Stage
Health checking is layered — fast liveness inside the app, strict checks at swap time, and broader endpoint validation after the full fleet is live.
Application endpoint
A dedicated GET /health controller returns HTTP 200 with a plain-text liveness message. It deliberately avoids touching tenant databases so a single slow tenant cannot fail a deployment probe. The root route provides a secondary liveness check.
Pipeline-driven checks
- Post-swap (strict) — immediately after each slot swap, the pipeline queries Azure’s
availabilityStateand calls/healthon the production slot; failure blocks the pipeline. - Final fleet check — environment-specific JSON config lists every public endpoint across all three API surfaces; each is probed 10 times with timeout and status code validation.
- Configs in artifact — health and CORS JSON files ship with the build so the same artifact self-describes how it should be verified after deploy.
Technology
What We Used
Application
Platform & DevOps
The Result
Safe, Repeatable Production Releases
The ISV can promote code through four environments with automated gates, deploy to six production instances without downtime, and recover cleanly when something goes wrong.
Similar Challenge?
Need Safer Deployments?
Whether you’re modernising a legacy .NET platform, standing up Azure DevOps from scratch, or need zero-downtime releases across multiple App Service instances — we design pipeline architecture that fits how your product actually runs in production.