Modernizing ASP.NET MVC to .NET 8 with Amazon Q Developer

A Brief History of Porting .NET Framework to modern .NET

Pre-generative-AI tools and libraries
Modernizing legacy .NET Framework applications to modern, cross-platform versions of .NET (aka .NET Core) has been a laborious process that has gotten easier as tools and libraries have launched over the past half-decade. AWS launched their Porting Assistant for .NET in 2020, and Microsoft followed the next year with the .NET Upgrade Assistant. AWS later moved their porting functionality into a Visual Studio extension, the AWS Toolkit for .NET Refactoring. Both of these tools are heuristics based – they use rules and string replacements to update projects. In 2022, Microsoft released a set of compatibility libraries, SystemWebAdapters (GitHub), which simplifies using features from ASP.NET on .NET Framework with ASP.NET Core. These libraries significantly reduced the amount of refactoring required to port projects that use HttpHandlers, HttpModules, make extensive use of HttpContext and Session objects, and more.

LLMs to the rescue?
But even with assistants and libraries, a lot of manual changes are required to get a legacy project to build – much less run – under modern .NET. In December 2024, AWS introduced a new feature of their AI-powered coding assistant, Amazon Q Developer, called Amazon Q Developer: Transform for .NET. It’s currently in preview, and the AWS docs provide detail on what it currently covers (spoiler: no Web Forms). Q Developer Transform leverages an LLM that has been trained on C# code, as well as rules-based changes to move things from (for example) web.config to appsettings.json, and to create the Startup.cs file.

Q runs its transformation in the cloud
As of when I write this, Q works by uploading your source code to a secure AWS environment, runs its rules-based changes and re-targets to .NET 8, then iteratively does a build and fixes the build errors until there are no more errors or until it times out (if it’s unable to fix all the build errors). It only ports back-end code, not code in MVC view or Razor pages, although the AWS service team says those are on the roadmap. And as I said before, no web forms – it won’t even start the transformation if it finds .aspx pages in your solution.

The “guinea pig” app: MvcMovie
At Evolve, we believe in accelerating pretty much everything with genAI, so I decided to test out Q’s new capabilities with an old ASP.NET MVC application that lots of developers learned to build ~15 years ago: the MvcMovie sample application. If you’d like to build it from scratch, the walk-thru is in this eBook, “Intro to ASP.NET MVC 4 with Visual Studio“  (PDF) by Rick Anderson and Scott Hanselman (May 2012). I found the source code for it buried deep in the AspNetDocs GitHub repo (direct link to the MvcMovie folder). MvcMovie is an almost-trivially simple web application comprising only a single project, but it does use some features – like database access via Entity Framework, and authentication and authorization – that are fairly ubiquitous in real-world applications.

Getting MvcMovie to build and run under .NET Framework 4.8

After copying the MvcMovie application to a new repo (in my GitHub here), I opened it in Visual Studio, which prompted me to upgrade it to .NET Framework 4.8, as it targets 4.6.1 currently, and I didn’t want to install the targeting pack for it. After re-targeting to 4.8, I got to work getting it to build and run. This required updating dependencies as well as having Entity Framework generate the database by running Update-Database from the Package Manager console. I used SQL Server Management Studio to take a look at the tables that were generated. I also used the “Register” functionality to create a user, which duly appeared in the AspNetUsers table. Finally, I added the following line to the bottom of the shared view _Layout.cshtml to dump the .NET runtime version on which the app is running :

<p>.NET Version: @(System.Environment.Version.ToString())</p>

Note that running Update-Database in the package manager console seeds the database with sample movies.

Running Q Developer Transform in Visual Studio

The .NET Transformation feature in Q can be accessed either via the AWS Toolkit with Amazon Q extension for Visual Studio 2022 or the Q Developer Transform “web experience“ . Since I want to fix code issues and build the project in Visual Studio, I am using the extension. Warning: You’ll need a Q Developer Pro subscription to transform more than a trivial amount of lines per code. You can configure your Amazon Q connection settings from the menu: Extensions ► AWS Toolkit ► Getting Started

Once the extension is installed and configured, I opened up a .cs file because Q Transform feature can’t activate unless there is an open C# source file (this may be a limitation of Visual Studio itself). In the Solution Explorer pane, I right-clicked the project and selected “Port project with Amazon Q Developer” from the context menu. If your solution has multiple projects, you can also choose “Port solution…” from the solution node.

The MvcMovie project is very simple – it has no dependency projects. Q Developer’s Transform feature is smart enough to handle transforming projects that depend on other projects though; if you select a project that references other projects in the solution, Q will tell you it’s including those as well, and it will attempt to transform them all.

After clicking “Start”, Q first builds your project locally. If it doesn’t build locally, the transform will fail – after all, if it doesn’t build now, Q won’t be able to tell if it’s successfully ported to .NET 8, as it does iterative builds in the cloud. Once the transformation process starts, Q opens up two new Visual Studio panes where you can track progress. You see how many files are transformed on the right-side of the “Amazon Q Developer Code Transformation Hub” pane.

Once the transformation completes, Q doesn’t automatically apply the changes. It downloads them into a new folder in your project folder, artifactWorkspace. You don’t need to go into that folder though, as the toolkit provides a button to view the diffs, and download a summary:

An example of a code change Q made is in the AccountViewModels.cs file, where it removed the namespace from an instance of System.Web.Mvc.SelectListItem and added a using statement for Microsoft.AspNetCore.Mvc.Rendering.

On the “view diffs” pane, you can also see that Q added new files including Startup.cs and appsettings.json, and renamed a number of files no longer needed by appending “.bak” to the filenames. I selected all the changes and clicked “Apply changes” to update the project. Visual Studio immediately prompted to reload the project, as the .csproj file had changed. The MvcMovie project now targets .NET 8. From personal experience (I’ve been using Q’s transform feature since it was in internal beta last summer, when I was at AWS), I recommend deleting the contents of the obj and bin folders before continuing.

Manual intervention required!

Building the project under .NET 8 failed, as there were numerous issues with the C# code in the .cshtml view files. A few of the problems were:

  • The original project used bundling and minification with @section Scripts { } code sections and @Scripts.Render() in views, which ASP.NET Core doesn’t handle natively, although it works with other solutions like WebOptimizer
  • The views also had @Styles.Render() blocks, which also didn’t work
  • ASP.NET Core requires an antiforgery flag (bool) as an argument of the Html.BeginForm helper, which wasn’t present in the original views.
  • Two views had using statements for Microsoft.Owin.Security, which is no longer used in ASP.NET Core, and populated a List<AuthenticationDescription> by calling Context.GetOwinContext().Authentication. GetExternalAuthenticationTypes() which threw a build exception

My goal here was to get the project to build and run with minimal effort, so I replaced the scripts code section with <script> tags referencing the JavaScript files (which Q moved to wwwroot/js) and used <link> tags to import the style sheets, which also had been moved to wwwroot/Content. The two views that looped through the external auth providers list returned by GetExternalAuthenticationTypes needed a little investigation. I followed the advice in Microsoft’s docs (including here), and replaced this line:

var loginProviders = Context.GetOwinContext().Authentication.GetExternalAuthenticationTypes();

With this line:

var loginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();

I also injected SignInManager at the top of the page:

 @inject SignInManager<ApplicationUser> SignInManager

At this point, the project built successfully! That’s at least partial success!

When trying to run in debug mode, I immediately hit a few snags. One of them was that Q had updated AccountController.cs, switching the types of the injected userManager and signInManager from ApplicationUserManager to UserManager<ApplicationUser> and ApplicationSignInManager to SignInManager<ApplicationUser> respectively. Those are the correct the correct fixes (good job Q!), but it didn’t wire up the dependency injection in Startup.cs. I actually wasn’t sure how to set this up properly, so I decided to ask…. Q!

At the bottom of the code pane in Visual Studio, there’s a small Q icon . Clicking that reveals a full menu of features including “View Q Chat Panel” (you can also do this via Visual Studio’s “View” menu). So while Startup.cs was the open and active document, I opened up the chat, and asked Q to show me how to add UserManager<ApplicationUser> to the services container so that it could be used for dependency injection. Note that in the screenshot below, I actually asked it about UserManager<IdentityUser> because I wasn’t being careful, so I had to replace <IdentityUser> with <ApplicationUser> in Q’s otherwise flawless answer. It even showed me that I needed to add the ApplicationDbContext to the services container and configured it for SQL Server. Hugely helpful.

I added the sections that Q had commented with “// Add DbContext” and “// Add Identity Services” to the ConfigureServices method as shown, and also added app.UseAuthentication() and app.UseAuthorization() to the Configure method. Then I ran the project again, and it worked! At least, until I tried to log in, when an exception about missing column names in the AspNetUsers table was dumped to the page. Columns for NormalizedUserName, NormalizedEmail and LockoutEnd were missing. For a real-world application, I would take more care in resolving this, but for now, I just manually added the missing columns using SSMS and filled in the missing values for my lone user. Later, I ran into a similar issue with a missing ProviderDisplayName column in AspNetUserLogins (for external login providers) and fixed it the same way. Having fixed that, I tried to view the list of movies by clicking on the “MVC Movie” link/title on the home page, and that also failed. It turns out that the MovieDBContext by default was looking for the wrong database – I fixed this by adding the correct database name to the MovieDBContext constructor’s call to base:

public MovieDBContext() : base("aspnet-MvcMovie") { }

That was the last fix required to get the movies app working – I was able to add another movie to the list, and it properly showed up. Note that the page shows the .NET runtime version as 8.0.15, proof that this is running on ASP.NET Core.

I can also successfully click on my email (blurred) to see my account page, and then select “manage” next to external logins, and the view renders correctly, although there are no external auth providers configured:

The Verdict
I think this new Q feature is promising, and it made some intelligent updates to the source code in order to get it to build under .NET 8, but it still required a fair bit of manual effort. Not having the code in the views updated means lots clicking through build errors one by one and using search & replace to fix things. I like that it correctly updated some of the auth code, but it didn’t wire it up in Startup – probably because it built fine without that code, but of course, it won’t run without it. Having the Q chat feature there certainly helped unblock me, and I also used the Q menu’s “Fix” option to fix some of the View issues.

I also purposely chose a very simple and small project because Q Developer: Transform timed out attempting to transform a significantly larger project I had originally tried. And while the output (in Output tab) for “Amazon Q Language Client” does give you an idea of whether things are still happening or not, there is no real transparency into the build errors it can’t fix (assuming it times out). Providing the list of remaining build errors, especially if it’s stuck on one, would allow the user to manually fix it, and then continue, much like Microsoft’s new GitHub Copilot Upgrade for .NET, which sadly only handles upgrading from lower versions of modern .NET (like .NET Core 2.1) to newer versions like .NET 9, and has no support for .NET Framework. Meaning that for now, Amazon’s offering is the only game in town for generative AI assistance with updating. For now, it’s helpful when it works, but not yet a silver bullet.

Scroll to Top