Tales from the .NET Migration Trenches - Intro
Posts in this series:
- Intro
- Cataloging
- Empty Proxy
- Shared Library
- Our First Controller
- Migrating Initial Business Logic
- Our First Views
- Session State
- Hangfire
- Authentication
- Middleware
- Turning Off the Lights
Over the past year or so I've been part of a large-ish modernization effort, both migrating from .NET 4.8 to .NET 6 (the latest LTS at the time) and from an on-premise deployment to Azure. While these two workstreams were largely independent (luckily), we did have some dependencies between the two efforts. There are quite a few "how-to" articles on the more vanilla aspects of migration but I wanted to walk through a few more of the...messy aspects of migration that crop up when you've got a codebase that's been around for over a decade.
This isn't the first codebase I've migrated from .NET Framework to .NET...Core? BUT it's luckily a lot easier than it used to be. As a general strategy for modernization of any codebase, I vastly prefer the Strangler Fig pattern, incrementally migrating from the legacy application to the modern one, until the legacy application is retired. Or whatever portions we want to modernize are at least! This greatly reduces the risks over a "big-bang" migration, which I almost never see succeed.
When we first started examining migration techniques in the second half of 2022, Microsoft had released their preview of a set of tools and libraries that allow for incremental migration, making the Strangler Fig strategy MUCH easier to implement.
The general idea behind the System.Web Adapters starts with YARP, a reverse proxy hosted in a .NET 6 application. We start with our normal application:
Next we add the System.Web Adapters and its reverse proxy to a blank application:
External requests go to this initial application instead of the .NET 4.8 web application. If the .NET (6/7/8) application can't handle the request, the self-hosted reverse proxy forwards that request to your .NET 4.8 application. Any shared logic we'll need to refactor into common library(ies). Next, we migrate pages/controllers over to the .NET Core app:
The other piece the library provides are adapters for the legacy System.Web
references, where your existing code that uses say System.Web.HttpContext
will transparently forward those calls into the correct .NET Core libraries.
Eventually, all of the requests are handled by the ASP.NET Core application and we can safely remove the .NET Framework application:
Sounds easy, right? Wellll we found in practice that there are lots of little decisions and roadblocks along the way. From 3rd-party libraries, to un-migrateable code, to missing features, we had all sorts of challenges along the way. Nothing was insurmountable and we went live in Azure and converted from .NET 6 a little over 4 months after starting with very few issues after flipping the switch.
In this series, I'll walk through our entire process along the way, from analysis to production.