by Chris Simon
Principal Consultant at CMD Solutions

Every application is different; there is no ‘one size fits all’ approach to modernisation.  You will need to explore the options and come up with a modernisation strategy and roadmap that will suit your needs – either on your own or with the support of a trusted, experienced partner.

In part 1, we explored the reasons to modernise and upgrade a .NET Framework application to .NET.  In part 2, we reviewed several strategies for planning a migration of large codebase.

In this final post we’ll explore some of the technical elements involved in upgrading a .NET Framework codebase to compile for .NET 6.0.


Modernisation Tools

Various tools have been published to assist with planning and executing a .NET modernisation. There are typically two types of activities they support:

  1. Analysis – gives you an indication of how compatible your current codebase is for a target .NET version to assist with planning
  2. Upgrade – makes automated changes to your codebase to resolve common incompatibilities

Test Code

To review the tools, we’ll be looking at applying them to a publicly available .NET Framework codebase – the venerable “eShop” sample application published by Microsoft.

Note: this repo contains examples of “modernising” towards windows containers while staying in .net framework – we’ll be using the legacy codebases to explore modernising to .NET 6.0.

The test application we’ll focus on in that repository is eShopLegacyMVC. MVC Applications will generally have an easier time migrating – although the ASP.NET Core MVC library has changed at an API level, the general structure and philosophy of MVC applications is consistent.  Some thoughts on other application types are included below.

Analysis Tools

Analysis tools can give an indication of how many changes your current codebase would need to compile and run with a different framework.

In theory, you could use this to assess the effort it will take you to complete an upgrade. However, if you intend to use an upgrade tool, the analysis does not take into account the changes that the upgrade tool will apply, so the report does not reflect the real remaining effort that will be required.

An alternative is to utilise the upgrade tool and then evaluate the compilation errors. This will  give you a more realistic view of the remaining effort needed to get the code compiled and running.

Nonetheless, there may be circumstances in which you are unable to use an upgrade tool.  In those circumstances, the analysis tools can give you an initial indicative estimate of the complexity of the task ahead.

Microsoft Portability Analyzer

Microsoft Portability Analyzer is a tool published by Microsoft.  In their own words, it is:

a tool that analyzes assemblies and provides a detailed report on .NET APIs that are missing for the applications or libraries to be portable on your specified targeted .NET platforms

It is distributed as both a Visual Studio extension and a command line tool (ApiPort.exe). However, the extension is only supported on VS2017 and VS2019, so if you’re already on VS2022, you’ll need to use the CLI. To install the command line tool, download and unzip the Github Release.

The tool works by collecting metadata about your code and sending it to an online API (hosted by Microsoft) for analysis.  If you need to run in an offline mode, this is available. If you need to see the data that will be shared, you can use the dump command in the CLI tool.

The first step is to build your codebase.  The tool works by analysing the compiled assemblies, not the source code – so if you have not compiled, the tool will report:

After compiling, run:

.\ApiPort.exe analyze -f %pathToSourceFolder% -t “.NET”

The output is a spreadsheet, which includes a compatibility percentage based on looking at the number of method calls into the .NET Framework in your application, and the proportion of them that are still valid in your target Framework.

To illustrate, when running the Portability Analyzer on the eShopLegacyMVC application with a target of .NET 5.0, we get a compatibility of 80.59%.

Note: The tool only supports .NET 5.0 as a target, not .NET 6.0 as it has not been updated in a few years. The migration path from .NET 5.0 to .NET 6.0 is very straightforward, so even if you are targeting .NET 6.0, the results for .NET 5.0 are relevant.

On the next tab, there is a list of all the .NET Framework method calls that are not supported in the target framework:

In this case there are 75 of them (the first 14 shown above) and unsurprisingly, they are all in the System.Web namespace – as ASP.NET MVC for .NET Framework is not supported in .NET 5.0 – and its replacement, the ASP.NET Core MVC, has a very different set of APIs.

This illustrates the point made above – multiple upgrade tools are able to automatically upgrade from ASP.NET MVC to ASP.NET Core MVC and as such all these compatibility issues are automatically resolvable. However, after the upgrade tool is done, there is more work to be done (as we’ll see below), but this report cannot identify that work.

AWS Porting Assistant for .NET

AWS has increasingly broad support for .NET and as part of that offering have released the AWS Porting Assistant for .NET.  This tool does both analysis and upgrades – in this section we will focus on the analysis capability.

It’s available as a standalone tool (with both a GUI and a Command Line) or can be installed using the Visual Studio Extension Manager – it supports VS2017, VS2019 and VS2022, and supports .NET 6.0 as a target.

Unlike the Portability Analyzer, the Porting Assistant runs completely offline – however due to the way the Data Usage Sharing feature works and is configured, you may be led to believe that you need an AWS IAM User setup as an AWS Profile to complete the assessment (the Profile setting is marked as ‘required’).  This is NOT the case.  You only need to create an IAM User if you’d like to activate Data Usage Sharing with AWS.

If you would like to activate Data Usage Sharing, also be aware that while the UI suggests that your IAM user should have the Admin or Power User role, under the principles of least privilege, this is not recommended.  An IAM Policy defined in the online documentation contains a minimal IAM role that we recommend you attach to your IAM user. 

Either way, to run the assessment in the VS Extension, right click on the Solution and choose “Run Full Assessment with Porting Assistant”:

For the VS extension, assessment results are represented in the Visual Studio Errors window as warnings – which is convenient as you can double-click on the warning to be taken to the line of code. And if you have “Enable Incremental Assessments with Porting Assistant” turned on, then those warnings will be updated as you work on the code – much like regular compilation errors & warnings:

In this case, the analysis report indicates the same results as the Microsoft Portability Analyzer – the issues are all with the System.Web namespace not being supported in net6.0.

Command Line

To run the CLI version of this tool, you need to install the
standalone tool.

The CLI documentation indicates the path of the CLI tool, which can be executed using:

& “C:\Users\%username%\AppData\Local\Programs\Porting Assistant for .NET\resources\netcore_build\PortingAssistant.Client.CLI.exe” assess –solution-path “C:\Path\To\Solution.sln” –output-path “C:\Path\To\Output\” –target “net5.0”

Note: The solution path must be an absolute path – relative paths are not supported.

Although the documentation suggests that net6.0 is supported in the CLI, the version currently available from the link on the AWS documentation page only supports net5.0 (although the VS 2022 extension does support net6.0).

The output is a folder structure containing two json files nested a few levels deep:

  • %solutionName%-analyze\
    • solution-analyze\
      • %solutionName%-api-analysis.json
      • %solutionName%-package-analysis.json

The first contains a json representation of the same warnings represented in the VS extension warnings window.

The package analysis contains a list of all the nuget packages and the compatibility status of the current version.  If the current version of the nuget package is not compatible with your target .NET version, it includes recommendations of how to proceed, including a list of potential upgrade versions for the nuget package that would be compatible.

On balance, the AWS Porting Assistant for .NET is a more up to date and useful tool for this kind of analysis.

However, as noted above, the better path is to utilise one of the upgrade tools, and then evaluate the resultant code.  The next section explores the use of two Upgrade tools and the common post-upgrade changes.

Upgrade Tools

Upgrade tools go further than the Analysis tools – they actually execute a set of automated changes intended to take care of the most common upgrade patterns.  We’ll be looking at two tools in this post, and while both will require some manual clean-up/adjustments after the upgrade has been applied, there are pros and cons to the way they each work.

.NET Upgrade Assistant

The .NET Upgrade Assistant is an interactive CLI tool from Microsoft that guides you through the process of upgrading your .NET Framework app to be compatible with a .NET target.

It is a dotnet tool and can be installed globally with:

dotnet tool install -g upgrade-assistant

The upgrade assistant works with a number of types of apps – in this demo, we’re going to look at the same MVC app we analysed earlier.

To run the upgrade assistant:

upgrade-assistant.exe upgrade %path\To\SolutionFile.sln%

After some initial analysis you will be presented with a set of expected steps, with a marker indicating the next step, and options for how to proceed:

Typically you’ll use option (1) – “Apply next step” (as this is the default, you can just press ENTER and don’t have to type in the number “1”), but in some circumstances you may need to skip a step or may like to see more details.

If a step has options, you will be given the chance to select them – such as the backup path, and then prompted to continue once the step is complete.

Typically, you can just hit ENTER repeatedly to move through the steps.

As the tool progresses you’ll see the completed steps marked with a green marker.  The remaining steps may also change as the tool detects more steps that are necessary or steps that are no longer required:

Eventually, the upgrade will complete and control will return to the command prompt:

Now it is possible to analyse the upgraded code and evaluate compilation errors and functional errors (functional errors can only be discovered by testing – ideally automated. This topic will be explored in more detail in part 3 of this blog series).

Next we’ll review the AWS Porting Assistant for .NET, and then in the Summary of this section compare the compilation errors remaining after each tool and the manual steps required to correct them.

AWS Porting Assistant for .NET

We’ve already seen this tool in terms of its analysis capabilities – in this section we’ll review its automated upgrade capabilities.

Visual Studio Extension

Command Line

To port a solution using the CLI, you will need to install the standalone tool, as described above.  You can then execute:

& “C:\Users\Administrator\AppData\Local\Programs\Porting Assistant for .NET\resources\netcore_build\PortingAssistant.Client.CLI.exe” assess –solution-path “C:\Path\To\Solution\Solution.sln” –output-path “C:\Path\To\Output” –target “net5.0” –porting-projects ProjectName

Which is similar to the assess command above, but with an additional “–porting-projects” parameter, where you have to opt-in specifically each project in the solution that you’d like to port, e.g. “–porting-projects eShopLegacyMVC” in the case of our sample solution.

The porting process will run without manual intervention and leave your codebase adjusted to work with the target .NET version you selected.

Compilation Errors & Warnings

The following warnings and errors occur after running each assistant, and resolutions are outlined for each.

📝 = manual step required

✅ = upgrade step handled automatically by the assistant

☑️ = issue not relevant for the assistant

Issue MS Upgrade Assistant AWS Porting Assistant for .NET Notes
NETSDK1086 A FrameworkReference for ‘Microsoft.AspNetCore.App’ was included in the project. 📝diff Microsoft.AspNetCore.App is included as an implicit reference, so can be removed from the .csproj (see the diff) to remove this warning.
System.Diagnostics.DiagnosticSource & System.Memory ☑️ 📝diff AWS Porting Assistant does upgrade most packages correctly, but a few need bumping further to compile.
Microsoft.Net.Compilers 📝diff AWS Porting Assistant doesn’t remove this dependency, which causes build errors.
Static Assets Not Found (404) 📝diff 📝diff ASP.NET Core MVC has a new Static Asset Root Folder.  You’ll need to move folders and update the .csproj as per the diffs.

Note: AWS Porting Assistant does relocate a number of static files, but the Images, Pics & Fonts folders still need manually moving.

Use of System.Web.Hosting.HostingEnvironment 📝diff 📝diff This is a .NET Framework API that has been replaced in .NET with an injectable Microsoft.Extensions.Hosting.IHostEnvironment

In this application, the CatalogDBInitializer relies on the HostingEnvironment to determine the local path of some sql files.  In the same file, the static System.Configuration.ConfigurationManager is used – and while this is supported in .NET, the recommendation is to migrate to injecting Microsoft.Extensions.Configuration.IConfiguration

In this application, this also affects PicController

Controller StatusCodes 📝diff 📝diff In ASP.NET MVC, controller methods could return status codes with either well defined methods, like HttpNotFound(), or with a more explicit StatusCodeResult().

In ASP.NET Core, these have been replaced with a simpler and more comprehensive set of methods, like BadRequest() and NotFound().

CatalogController and PicController both need updating.

Note: AWS Porting Assistant will automatically update NotFound(), but not BadRequest().

Bind Attribute Include 📝diff 📝diff In ASP.NET MVC, the “Bind” attribute had a default argument “Include” – this is no longer defined as default so the argument name should be removed.
SelectList Namespace 📝diff The use of ASP.NET MVC SelectList now needs Microsoft.AspNetCore.Mvc.Rendering to be added as a using namespace so that the new ASP.NET Core SelectList will be referenced.
Request Object Changes 📝diff 📝diff The shape of the Request object has changed, so we need to update how we retrieve the Scheme property.
HandleErrorAttribute 📝diff The capability provided by ASP.NET MVC’s HandleErrorAttribute is now provided by the UseExceptionHandler extension method as per the Error Handling documentation.
RouteConfig 📝diff 📝diff ASP.NET Core MVC Routing is a little different. Both the Upgrade Assistant and AWS Porting Assistant introduce the new endpoint routing configuration, however the Upgrade Assistant doesn’t remove the old RouteConfig file. With both tools, the endpoint configuration of the new route will need updating to match the controller.
DI Container Setup 📝diff 📝diff Since ASP.NET Core MVC includes its own DI Container system, there is no need to use a 3rd party system (in the ASP.NET MVC version, Autofac was used).  However, if you’d like to keep using Autofac, you can use an adaptor to integration Autofac into the ASP.NET Core MVC DI system.
Script References 📝diff 📝diff The ASP.NET MVC included scripts in HTML pages via a special @Scripts.Render tag which managed bundling and minification on the fly. ASP.NET Core MVC has moved to bundling and minification on compilation which means the HTML should include a <SCRIPT> tag to the bundled file. In the demo we have moved to using a CDN hosted version of Bootstrap and jQuery and removed the BundleConfig.cs file.
HttpContextHelper 📝diff 📝diff The HttpContextHelper class from ASP.NET MVC has been removed. Instead of having startup code add System.Environment values to the session, and using HttpContextHelper to obtain them from the session, in ASP.NET Core MVC it’s simpler to just obtain the values from System.Environment directly.
@HTML.Partial 📝diff 📝diff In ASP.NET Core MVC it is now best practice to use @HTML.PartialAsync.
Mock Database Configuration 📝diff ☑️ With the upgrade to EntityFramework 6.4, the general principles of DependencyInjection, and the new IConfiguration model, it is best to configure the use of the Mock data initializer in the DbContext constructor, rather than in Application_Start which was previously setting a static initializer configuration.
Global.asax -> Middleware 📝diff Global.asax has been removed to ensure the framework isn’t coupled to the hosting web server (historically IIS). The functionality from the lifecycle hooks needs to be moved into the ASP.NET Core MVC middleware pipeline
Database Connection String 📝diff ☑️ Instead of providing a connection string name, the connection string itself is passed into the DbContext as a parameter as configured in the DI Container setup.
EntityFramework.Core ☑️ 📝diff The AWS Porting Assistant automatically upgrades use of Entity Framework to Entity Framework Core.  Technically this isn’t required, as Entity Framework 6.4 is compatible with .NET Core, and it does introduce significant extra work to update data models and other DB configuration to work with EF Core.
EntityFramework Core Seeding ☑️ 📝diff With the update to EF Core, the seeding of mock data approach needs to be completely revamped.


Overall, the AWS Porting Assistant for .NET does a more thorough upgrade job – as the table above indicates, there are less manual steps to complete as more required changes are either fully or partially automated.

The main drawback is that by upgrading to Entity Framework Core, it generates a greater manual burden to complete the upgrade of your persistence layer.  If you want to avoid that, you can revert the EntityFramework changes made by the porting assistant and manually upgrade to Entity Framework 6.4, in which case the manual steps will look more similar to those required after using the MS Upgrade Assistant.

Other Challenges

ASP.NET WebForms

Unfortunately, ASP.NET WebForms from .NET Framework has no direct analogue in the .NET world.  The closest is Blazor and to support this path Microsoft has published an upgrade guide for migrating a Web Forms app to Blazor and a detailed manual for web forms developers to learn Blazor.  Some key points to be aware of:

  • Blazor can run in ‘client-side’ or ‘server-side’ mode – if you are migrating from WebForms, you’ll probably find that ‘server-side’ is the closest match to your existing design, however:
    • the dependency on SignalR Core may impact your server-hosting model – specifically the fact that it requires ‘sticky sessions’ if you have scaled out your web farm
    • because each user interaction involves a network call to the backend, users may experience higher latency/delay
  • A community project called “Web Forms Components for Blazor” is a fantastic resource for teams heading down this path, including both library components and documented strategies to ease the process.
  • Neither the Upgrade Assistant or AWS Porting Assistant for .NET will do anything useful with an ASP.NET WebForms application


.NET Windows Communications Foundation is another .NET Framework with no .NET equivalent.

For teams that are in a position to update both clients and servers, Microsoft recommends migrating to gPRC.  It is the communications platform of choice for the future, and has enough parallels that teams familiar with WCF but who want a more modern architectural style should be able to adopt it without too much trouble.

An alternative is a community project – CoreWCF – which is a direct port of WCF to .NET and is intended to support a less disruptive migration.

Third Party Dependencies

Another significant challenge for teams migrating from .NET Framework to .NET is a dependency on 3rd-party libraries which have not yet released .NET equivalents.

There are a number of potential pathways to resolve this, but in planning for a migration, these components should be allocated a significant amount of contingency time to handle.


Options include:

  1. Finding an alternative library that is functionality equivalent and available in .NET.
  2. Finding an alternative SAAS service that encapsulates the requirement functionality and integrating their API.
  3. Self-host an API that wraps the capabilities of the library.  While you will still have to self-host a .NET Framework service, the goal is to ensure that the service contains nothing other than the library – in this way, it should be fairly stable and require minimal maintenance, allowing you to focus your ongoing development effort on the extracted modern .NET services.

Platform-Specific Dependencies

Even though .NET is a cross-platform ecosystem there are some capabilities that are dependent on a specific platform.

After upgrading to .NET, you can use Microsoft’s Platform Compatibility Analyzer to determine if you have any API calls that would prevent you adopting an alternative platform layer – e.g. linux containers.  If any are identified, you can then choose how to handle each instance on a case by case basis.


Executing a .NET Framework to .NET Migration requires planning and consideration.  Vendors such as Microsoft and AWS have released tools to assist with analysing code bases and executing migrations:

Due to offering both analysis & upgrade facilities, more comprehensive analysis capabilities, and being able to execute an upgrade that requires less manual changes, we recommend AWS Porting Assistant for .NET.

What’s Next?

As noted above – every codebase is different.  While the above examples apply to the eShop application and include common & typical changes, there’s no doubt that your application will require further analysis and investigation to accommodate its unique needs when modernising.

Once you’ve modernised to .NET, to make the most of being able to use lightweight linux containers the next step is to start designing your hosting infrastructure, including selection of container orchestration service, designing auto-scaling capabilities and operational tooling such as observability and CI/CD Pipelines.

If you’re looking for assistance planning and executing such a modernisation, CMD Solutions can help! With expertise in cloud architecture, software architecture, devops and implementation, CMD can help put you on the right path towards a successful and valuable modernisation project.