Back to blog

UISampleSpark: Seven Years of .NET Modernization

February 3, 202611 min read

Since Microsoft unified .NET under a single platform, a new major version ships every November. UISampleSpark adopted every release deliberately, documenting the friction points and upgrade strategies that real-world teams encounter across seven major migrations.

UISampleSpark: Seven Years of .NET Modernization

Part 2 of the UISampleSpark Series — Continuous Modernization

The .NET Treadmill

If there is one constant in the .NET ecosystem, it is change. Since Microsoft unified the platform under .NET 5 in November 2020, a new major version has shipped every November like clockwork. For production teams, this cadence creates a familiar tension: stay current and benefit from performance improvements, security patches, and new language features — or fall behind and accumulate migration debt that grows more expensive with every release.

UISampleSpark was designed to run on that treadmill deliberately. As an educational reference project, it served as a proving ground for each migration, documenting the friction points, breaking changes, and upgrade strategies that real-world teams encounter. Over seven years, the project navigated the full .NET progression:

YearFrameworkTag
2019.NET Core (initial)
2020.NET 5
2021.NET 6 LTSnet6-ga
2022.NET 7 STSnet7-ga
2023.NET 8 LTSnet8-ga
2024.NET 9 STSnet9-ga
2025.NET 10net10-ga

Each migration was tagged in the git history, creating navigable checkpoints that developers could compare side by side.

The Early Upgrades: .NET 5 and .NET 6

The first major migration came with .NET 5 in late 2020. Microsoft's unification of .NET Core and .NET Framework under a single brand was more than cosmetic — it signaled that .NET Core was the future. For UISampleSpark, this meant updating target framework monikers, refreshing NuGet packages, and verifying the test suite.

The .NET 6 migration (tagged net6-ga) was more substantial as a Long-Term Support (LTS) release. Key changes included:

  • Minimal hosting model: The traditional Startup.cs with ConfigureServices and Configure methods was replaced by a streamlined Program.cs combining configuration and middleware setup in a single file.
  • Bootstrap 5 upgrade: The project's UI moved from Bootstrap 4 to Bootstrap 5, with dynamic versioning for simpler future theme updates.
  • Azure Key Vault integration: Secret management moved from hardcoded values to Azure Key Vault, demonstrating production-grade credential handling.
  • CI/CD hardening: GitHub Actions workflows were established alongside existing Azure Pipelines.

Understanding LTS vs. STS

One of UISampleSpark's educational contributions was demonstrating the practical difference between Long-Term Support (LTS) and Standard Term Support (STS) releases:

LTS releases (.NET 6, .NET 8) receive security patches and bug fixes for three years. They are the safe choice for production applications where stability matters more than new features.

STS releases (.NET 5, .NET 7, .NET 9) are supported for only 18 months. They serve as innovation vehicles, shipping new language features and performance improvements that will be stabilized in the next LTS.

UISampleSpark adopted every release — LTS and STS alike. By upgrading annually, the project documented each migration's friction and demonstrated that staying current doesn't have to be painful when your codebase is well-architected.

The Middle Years: .NET 7 and .NET 8

The .NET 7 upgrade (tagged net7-ga) brought refinements rather than revolution. The project used this cycle to expand beyond core CRUD:

  • PivotTable.js integration: A data analysis view was added, demonstrating how JavaScript visualization libraries could consume the same REST API.
  • TreeNode helpers: Hierarchical data structures were added to the domain layer, exploring tree traversal patterns common in organizational data.
  • Docker support: The first Dockerfile was introduced, packaging the application into a container for consistent deployment.
  • Azure Linux deployment: The project expanded beyond Windows IIS hosting to Azure Linux App Service.

The .NET 8 upgrade (tagged net8-ga) was the most significant since .NET 6. As an LTS release, key changes included:

  • Streamlined Swagger integration: OpenAPI documentation was tightened with better response type annotations.
  • Docker automation: GitHub Actions workflows for Docker builds were refined, establishing the multi-stage build pattern.
  • EF Core improvements: Entity Framework Core 8 brought better query translation and JSON column support.
  • Minimal API sample: A new SampleMinimalApi project showcased the streamlined API development pattern.

The .NET 9 to .NET 10 Leap

The migration from .NET 8 to .NET 9 (tagged net9-ga) was documented step-by-step:

  1. Environment preparation: Updating Visual Studio, installing the .NET 9 SDK, verifying toolchain compatibility.
  2. Project file updates: Changing net8.0 to net9.0 across all .csproj files.
  3. NuGet package refresh: Updating Entity Framework Core, Swashbuckle, and all dependencies to .NET 9-compatible versions.
  4. Test verification: Running the full test suite to catch breaking changes in framework behavior.
  5. Docker rebuild: Updating base images from mcr.microsoft.com/dotnet/aspnet:8.0 to 9.0.

The .NET 10 migration (tagged net10-ga) included structural changes:

  • SkiaSharp adoption: System.Drawing was replaced with SkiaSharp for cross-platform image processing, eliminating Linux/macOS issues.
  • Project structure refactoring: The solution was reorganized to better separate concerns.
  • GitHub Actions hardening: Workflows were updated to use actions/checkout@v6 and actions/setup-dotnet@v5 with NuGet caching.
  • Global.json update: The SDK version was pinned to 10.* with allowPrerelease: true.

Beyond Framework Upgrades: SEO and Web Presence

Modernization extended beyond the .NET framework to the project's web presence:

  • Structured data: A sitemap controller generates XML sitemaps dynamically for search engine discovery.
  • RESTful URL patterns: Routes were designed to be human-readable and descriptive (/EmployeeReact, /EmployeeHtmx).
  • Open Graph metadata: Social sharing tags ensure rich previews on Twitter, LinkedIn, and Slack.
  • Favicon and branding: Consistent branding was applied across all layout pages.

The home page underwent a significant redesign in early 2025, replacing a README.md rendering dependency with a self-contained landing page featuring cards for each CRUD implementation and a comparison table across six dimensions.

Dependency Management as a Practice

UISampleSpark treated dependency management as a first-class practice:

  • Dependabot integration: Automated dependency update PRs ensured vulnerabilities were patched promptly.
  • Quarterly audit cycles: Manual dependency audits evaluated whether packages could be consolidated or replaced.
  • Strict version pinning: The global.json file enforced SDK consistency across all developers.
  • Security scanning: CodeQL analysis ran weekly and on every pull request.

The project's current dependency profile reflects seven years of careful curation:

PackageVersionPurpose
Entity Framework Core10.0.2Data access
Swashbuckle10.1.2OpenAPI/Swagger
Bogus35.6.5Mock data generation
SkiaSharp3.119.1Image processing
WebSpark.Bootswatch1.34.0Theme switching
Application Insights3.0.0Telemetry

The Upgrade Playbook

Through seven major version upgrades, UISampleSpark distilled a repeatable migration playbook:

  1. Read the migration guide: Microsoft publishes detailed breaking changes for each .NET release.
  2. Update the SDK first: Change global.json and install the new SDK.
  3. Update target frameworks: Change in all .csproj files simultaneously.
  4. Refresh NuGet packages: Update all packages to their latest compatible versions.
  5. Build and fix: Compile the solution and address compiler errors.
  6. Run tests: Execute the full test suite to catch behavioral regressions.
  7. Update Docker images: Change base image tags to match the new .NET version.
  8. Update CI/CD: Ensure GitHub Actions workflows specify the correct SDK version.
  9. Test in containers: Build and run the Docker image to verify the deployment pipeline.
  10. Tag and document: Create a git tag for the migration milestone and update the CHANGELOG.

This playbook, refined over seven iterations, made each subsequent migration faster and less error-prone than the last.

The Lesson of Continuous Modernization

The deepest lesson from UISampleSpark's modernization journey is deceptively simple: the best time to upgrade is continuously. Projects that skip multiple .NET versions face compounding migration costs — deprecated APIs accumulate, breaking changes multiply, and the gap between "current" and "latest" grows wider with each release.

By upgrading annually, UISampleSpark kept each migration manageable. The project never faced a "big bang" upgrade where three years of breaking changes had to be resolved simultaneously. Tests caught behavioral regressions. CI/CD automation verified that builds still produced working containers. And the living documentation — the CHANGELOG, the git tags, the blog articles — created a trail that other developers could follow.

The Swiss Army knife wasn't just gaining new blades. It was being sharpened with every release.

Additional Resources


UISampleSpark Series

This is part 2 of a five-part series tracing the evolution of UISampleSpark from a simple CRUD tutorial to a comprehensive web UI exploration platform.

  1. A Developer's Swiss Army Knife — The founding philosophy and core architecture
  2. Seven Years of .NET Modernization (this article) — Navigating the annual .NET upgrade cycle
  3. Constitution-Driven Development — How governance and AI transformed project quality
  4. Seven UI Paradigms, One Backend — Comparing seven frontend approaches side by side
  5. Modern DevOps as a Living Reference — Containerization, CI/CD, and cloud deployment

Project Links: GitHub | Live Demo | Docker Hub