10 Unpleasant Surprises When Migrating From .Net 4.8 to .Net 6

While our migration from .Net 4.8 to .Net 6 worked better than expected, we found a lot of annoying incompatibilities that took us hours to figure out. In this post I collect the 10 most frustrating differences to give you an overview on what to expect when you upgrade your applications to .Net 6.

This is part 2 of the mini-series on the .Net 6 migration:

  1. The Challenging Endeavour of a .Net 6 Migration
  2. 10 Unpleasant Surprises When Migrating From .Net 4.8 to .Net 6
  3. .Net 6 in Production: Same Same, but Different

 

SqlClient incompatibilities

Microsoft.Data.SqlClient is the official replacement for System.Data.SqlClient. Unfortunately, when we use Microsoft.Data.SqlClient with Dapper, we get this error message:

System.NotSupportedException : Enlisting in Ambient transactions is not supported.

Transactions are a must for our application and so we had to switch back to System.Data.SqlClient. I hope Microsoft will fix this problem soon or delivers a working solution for their replacement.

 

Hangfire changed how it connects to SQL Server

The parameter nameOrConnectionString in the method GlobalConfiguration.Configuration.UseSqlServerStorage() no longer accepts the name of the connection string. Instead, we need to add the full connection string. If you do not change the method call, you end up with this unhelpful error message:

System.ArgumentException : Format of the initialization string does not conform to specification starting at index 0.

This change in behaviour was tricky to track down, mainly because of the misleading name of the parameter.

 

Differences between Uri.EscapeUriString() and Uri.EscapeDataString()

Uri.EscapeUriString() is obsolete, because it can corrupt the Uri string in some cases. Visual Studio suggest we switch to Uri.EscapeDataString() and does the change for us. Unfortunately, the two methods do not produce the same results for our FHIR queries. That minimal difference was especially hard to track down since the FHIR URLs are long and full of escape sequences.

 

Declarative Configuration for WCF no longer works

In .Net 4.8 we could use WCF through the connected services feature and configure the NTLM binding in the App.config. In .Net 6 we still can use WCF through the connected services feature, but we can no longer use the declarative configuration for NTLM. There are endless examples online on how to do that, but nothing worked. We finally figured out a different way to initialise our service where we could pass the NTLM binding to our generated service:

 

App.Config does not work with all project types

We noticed an especially strange behaviour around the App.config files. While we got them working for command line applications, we failed with the NUnit projects. The same dependencies and the same loader code worked in one type of projects but did not work in another type. Prepare yourself to switch to appsettings.json for all your projects if you want to have a consistent way to store your configuration.

 

Gembox changed the handling of Tiff images

After the migration we got an error when we tried to convert Tiff images to PDFs with GemBox. GemBox cut the dependency to System.Drawing and created a replacement with GemBox.Imaging. Unfortunately, that was not enough for our use case, and we had to convert the images from Tiff to PNG before we could convert them into PDFs.

 

Cloudflare treats HTTPClient different than WebRequest

Some support application used the WebRequest library that we migrated to HTTPClient. The change in code was quickly done, but we got strange results back from our company web site. After a long debugging session, we were able to trace the changed behaviour back to Cloudflare. When Cloudflare detects HTTPClient, it gives back a 200 OK response for dead links. Changing the UserAgent string (to the one that Chrome uses) gave us the expected behaviour back.

 

OpenID Connect needs a different URL

When we reused the configuration for OpenID Connect from our .Net 4.8 application, we got errors about missing redirect endpoints. As it turned out, there is a tiny difference in the implementation between .Net 4.8 and .Net 6. Make sure that you change your client redirect URIs to include /signin-oidc at the end.

 

The Azure build pipeline does a lot of (buggy) magic

For most parts of the build pipeline we could switch to the .Net Core step. While the options for the step are named after the dotnet commands, there is a lot more going on than just running dotnet. We had a dotnet deploy command that truncated our appsettings.json file in unpredictable ways, leaving us with a broken web application. But that only happened on an interactive agent when we activated the option “Publish web projects“. As soon as we deactivated the option, we got our appsettings.json file correctly in the published folder.

 

ASP.Net Core 6 is a different framework

Even when you can reuse a lot of code and most concepts, there is a huge gap between ASP.Net for .Net 4.8 and ASP.Net “Core” in .Net 6. Things that worked in .Net 4.8 behind the scenes no longer work in .Net 6 (like routing details in web components). In other parts (like the pipeline) we get a lot of new magic with .Net 6. Prepare yourself for a lot of new things to learn even when you have years of experience with ASP.Net.

 

Next

When you do your migration, prepare yourself for tiny inconsistencies between .Net 4.8 and .Net 6/7. Those inconsistencies may be anywhere in your code, inside the .Net framework or in the 3rd party libraries. We finally overcame all those obstacles, but it cost us a lot of time to make that progress.

Next week we explore the operational side of running our application in .Net 6 and what new inconsistencies you have to expect.

4 thoughts on “10 Unpleasant Surprises When Migrating From .Net 4.8 to .Net 6”

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.