Tales from the .NET Migration Trenches - Turning Off the Lights

Posts in this series:

In the last post, we looked at migrating our middleware, which we tackle in an as-needed basis. When a controller needs middleware to be migrated, we migrate that middleware over. If the entire app needs the middleware, it needs to come rather early.

Once we migrate much of our middleware over, it becomes much less work to incrementally migrate individual controllers and their subsequent actions/pages over. I won't go into deep detail into this part - mostly it's fixing namespaces, adjusting features (such as converting child actions into view components), but it can go quite fast. On recent teams I was working with, we migrated easily a dozen controllers a week amongst 3-4 developers. At this point, the bottleneck wasn't the conversion, but testing to make sure the pages still worked correctly

It's essentially testing the entire application, one page at a time, so hopefully you've got some regression tests in some form or fashion. I'm not skipping the incremental controller migration because it's not interesting - it's just because our teams really didn't encounter many challenges there. There will be something that comes up, there always is, but just the controller/action/view part is not too terrible.

But in this post I wanted to focus on getting to the end - what do we do once we've migrated everything but authentication? When there's just one controller left, we're now OK to proceed with migrating the last pieces over and "turning off the lights" on the .NET 4.x application.

Migrating Last Features

The last (or next-to-last) migration typically:

  • Migrates the last controller, usually authentication
  • Turns off proxying and all remote app features

You don't necessarily need to split this into two separate units of work/deployments, as once you've migrated the last set of requests you can migrate all final features from the .NET Framework application. If the last controller is authentication, we'll also need to remove remote authentication. Our current web adapter configuration before final migration is:

builder.Services.AddSystemWebAdapters()
    .AddJsonSessionSerializer(options =>
    {
        options.RegisterKey<string>("FavoriteInstructor");
    })
    .AddRemoteAppClient(options =>
    {
        // Provide the URL for the remote app that has enabled session querying
        options.RemoteAppUrl = new(builder.Configuration["ProxyTo"]);

        // Provide a strong API key that will be used to authenticate the request on the remote app for querying the session
        options.ApiKey = builder.Configuration["RemoteAppApiKey"];
    })
    .AddAuthenticationClient(true)
    .AddSessionClient();
builder.Services.AddHttpForwarder();

With middleware:

app.UseSystemWebAdapters();

app.MapDefaultControllerRoute();
app.MapForwarder("/{**catch-all}", app.Configuration["ProxyTo"]).Add(
    static builder => ((RouteEndpointBuilder)builder).Order = int.MaxValue);

Along with migrating the authentication piece and all related middleware, we'll remove the above from our application startup, as well as the package references to all the proxy and System.WebAdapters packages. Once that's complete, our .NET application should now handle all requests. There might still be a few extra features to enable in .NET 8, such as Session:

builder.Services.AddSession();

// later

app.UseSession();

With all that complete, our .NET 8 application should now serve all requests and host all features needed to run our entire system.

Turning off the lights

While our .NET 8 application may now be "complete", we're not quite done yet. In my typical last phase we will:

  • Deploy the completed .NET 8 application to production
  • Monitor for any errors and any activity from the .NET 4.8 application
  • Adjust our .NET 8 application as necessary

If we don't see any issues, then the final final cleanup is:

  • Remove all .NET 4.8 code from the repository
  • Remove any shims to bridge from .NET 8 to .NET 4.8
  • Remove all .NET 4.8 application pipelines and deployments
  • Remove all .NET 4.8 production resources

And we should end with something like:

So what's next? There's still probably quite a bit to do to ".NET-8-ify" our existing system - all those architectural improvements we skipped in order to fast track migration. But most important - celebrate!