Upgrading from .NET Core 2.1 to .NET 6.0
Tags: dotnet
Published: 2021-12-31 03:01:49

---
title: Upgrading from .NET Core 2.1 to .NET 6.0
published: true
date: 2021-12-30 05:09:00 UTC
tags: dotnet
canonical_url: https://jason.sultana.net.au/dotnet/2021/12/30/upgrade-net2.1-net6.html
---

G’day guys!

Hope you all had a Merry Christmas! Today, since I’m in that strange limbo place between Christmas and New Years, I decided to migrate one of my old hobby projects from .NET 2.1 to .NET 6, and document the experience here. If any of you are perhaps planning on performing a similar upgrade yourself, you might find this a useful read before getting started.

### If it ain’t broke, don’t fix it?

I guess the first question that must be asked is, so long as the application is working, why bother doing an upgrade at all? It’s a good question, and there are a few reasons for this:

- Any new code samples or packages that get released from now will likely target the newer .NET version, so we’ll probably have an easier time integrating them if we’re using an up-to-date .NET version, too.
- [.NET 2.1 reached End-of-support back in August, 2021](https://devblogs.microsoft.com/dotnet/net-core-2-1-will-reach-end-of-support-on-august-21-2021/). This means that any improvements that get added to newer .NET versions won’t be backported to 2.1. FOMO much?
- If you’re running .NET apps in the cloud (eg: AWS Lambda), your cloud provider may force you to upgrade.
- [.NET 6 is reported to be significantly faster than previous versions.](https://devblogs.microsoft.com/dotnet/performance-improvements-in-net-6/)

### Which version should I upgrade to?

I think this is a very good question, since there are usually at least 2-3 reasonable choices at any given time. I’d probably recommend looking at the [currently supported .NET versions](https://dotnet.microsoft.com/en-us/platform/support/policy/dotnet-core), take note of when each of them will reach End of Support, and then decide accordingly. I usually opt to choose an LTS (Long-Term-Support) option where possible, so I don’t have to worry about another upgrade for a couple of years, hopefully. Currently, the latest LTS version is .NET 6, which will be supported until November 2024.

### Installing .NET 6

First, let’s find out if you currently have the .NET 6 SDK installed on your machine. An easy way to do this is to run the following in your favourite terminal:

```
dotnet --list-sdks

```

If you don’t see an entry for .NET 6, you’ll need to install it. You could do this using Visual Studio, but just in case you happen to be using a different code editor or IDE (vim perhaps?), here’s a [link](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) where you can download an installer from directly.

After installing the new SDK, the previous command should now include `6.0.101` (or whichever version you just installed).

### Upgrading projects

After installing the SDK, the next step is to update our `TargetFramework` in each `csproj` file. If you’re migrating from .NET Core 2.1, this will likely be set to `netcoreapp2.1`. Change it in each project to:

```
    <TargetFramework>net6.0</TargetFramework>

```

After saving, it’s highly likely that you’ll encounter 1000 errors and every file in your solution will be granted an honorary red squiggly underscore. To fix these issues, we’ll need to start by upgrading your solution packages to .NET 6-compatible versions.

### Upgrading solution

If you have a `global.json` file in your solution, you’ll want to upgrade the `sdk` property. Eg:

```
    "sdk": {
        "version": "6.0.0"
    }

```

You can read more about `global.json` [here](https://docs.microsoft.com/en-us/dotnet/core/tools/global-json?tabs=netcore3x). Essentially, this value is just used for CLI tools, but it makes sense to keep this set to the same version as defined in the projects themselves.

### Upgrading packages

If you’re using Visual Studio (or Rider), right-click on your Solution and click `Manage nuget packages`. Specifically, we’re interested in packages which have an update available. You could try upgrading these one at a time starting with the Microsoft packages, or you can just YOLO it and upgrade everything at once, and deal with any issues that arise post-upgrade. Up to you how you prefer to tackle this step. Since this was just a hobby project, I just upgraded all the packages at once.

### Upgrading MSBuild version in IDE

This step may only be applicable for Rider users. In my case, I needed to upgrade my MSBuild directory under Preferences -> Toolset and build as described [here](https://newbedev.com/how-to-change-net-framework-in-rider-ide). You’ll likely want to choose the directory with the highest MSBuild version. Visual Studio users can probably skip this step.

After completing this step, I noticed the vast majority of my errors disappear. Note that you may also need to clean and rebuild your solution.

### Microsoft.AspNetCore.App warning

```
  Microsoft.NET.Sdk.DefaultItems.Shared.targets(111, 5): [NETSDK1080] A PackageReference to Microsoft.AspNetCore.App is not necessary when targeting .NET Core 3.0 or higher. If Microsoft.NET.Sdk.Web is used, the shared framework will be referenced automatically. Otherwise, the PackageReference should be replaced with a FrameworkReference.

```

This is more of a warning than an error, but it’s still good to address this. Find any existing package reference to `Microsoft.AspNetCore.App` and remove it, since it’s now redundant. This will likely be in your top-level API projects.

### Entity Framework breaking changes

#### 1. builder.UseMySql(string) no longer exists.

This may or may not affect other SQL variants, but the `UseMysql(string)` extension method on `DbContextOptionsBuilder` now requires a second parameter, which specifies the server version. An easy fix just to get things compiling again is to auto-detect the version. Eg: `builder.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString));`.

#### 2. Relational() missing from IMutableProperty

If you were setting an explicit database column type (eg for decimal precision), you may have noticed that `Relational()` is missing from `IMutableProperty`. You can replace this with a call to `SetColumnType(string)` instead.

#### 3. String.Format cannot be translated to SQL

In .NET Core 2.1, I had some LINQ-To-SQL queries that looked like:

```
await _dbContext.SomeDbSet
    .Where(a => a.SomeID == id)
    .Select(a => new
    {
        a.SomeID,
        // ...

        Name = a.NavigationProperty == null ?
            "" :
            $"{a.NavigationProperty.FirstName} {a.NavigationProperty.LastName}"

```

In .NET 6 (or somewhere along the line), this stopped being supported, so I need to retrieve both values and perform the concatenation in-code after the query results are returned.

#### 4. Unable to return nested queryables

I had a data structure that looks like:

```
    private class ListItemsQuery
    {
        public Item Item { get; set; }
        public IQueryable<SubItem> SubItems { get; set; }
    }

```

And in .NET Core 2.1, I was able to return an `IQueryable<ListItemsQuery>` which itself contains an `IQueryable<SubItem>` property. In .NET 6 though, this was producing the following:

```
The query contains a projection '<>h __TransparentIdentifier1 => <>h__ TransparentIdentifier1.a' of type 'IQueryable<SubItem>'. Collections in the final projection must be an 'IEnumerable<T>' type such as 'List<T>'. Consider using 'ToList' or some other mechanism to convert the 'IQueryable<T>' or 'IOrderedEnumerable<T>' into an 'IEnumerable<T>'.

```

Changing the nested IQueryable to a List worked fine - though it means that part of the query does need to be materialised earlier than it was before. There were a couple of other complex LINQ queries that needed to be fixed up as well.

* * *

### Swagger breaking changes

#### 5. Cannot resolve symbol Info

Simply change to `OpenApiInfo`.

#### 6. Cannot resolve symbol ApiKeyScheme

Simply change to `OpenApiSecurityScheme`. However, the `In` and `Type` properties also have breaking changes.

`In` was previously a string, and is now a type of `ParameterLocation`. Replace the string `"header"` with `ParameterLocation.Header`.`Type` was also previously a string, and is now a type of `SecuritySchemeType`. Replace the previous string with `SecuritySchemeType.Http`, or other value as appropriate.

#### 7. DocExpansion signature change

This changed from a string to a `DocExpansion` enum value.

#### 8. SwaggerResponse attribute missing

This can simply be replaced with `ProducesResponseType`.

#### 9. SwaggerOperation attribute missing

Basically, all of the Swagger-specific attributes were extracted to `Swashbuckle.AspNetCore.Annotations`. Often, these aren’t needed and you can get away with just using .NET Core attrubutes (like `ProducesResponseType`), but you can install the swashbuckle annotations package if you want to.

* * *

### Startup breaking changes

#### 10. CorsAuthorizationFilterFactory missing

We can simply remove the Mvc filter.

#### 11. CORS Policy changes

If you were using `CorsPolicyBuilder` with a very open policy, allowing both a header and credentials, you’ll likely receive something similar to the following exception:

```
Application startup exception: System.InvalidOperationException: The CORS protocol does not allow specifying a wildcard (any) origin and credentials at the same time. Configure the CORS policy by listing individual origins if credentials needs to be supported.

```

This can be easily resolved by removing either the call to `AllowCredentials` or the call to `AllowAnyOrigin`, depending on your circumstances. Eg:

```
    var corsBuilder = new CorsPolicyBuilder();
    corsBuilder.AllowAnyHeader();
    corsBuilder.AllowAnyMethod();
    corsBuilder.AllowAnyOrigin(); 
    //corsBuilder.AllowCredentials();

```

#### 12. IHostingEnvironment obsolete

Simply replace with `IWebHostEnvironment`.

#### 13. MVC Changes

Replace `AddMvc()` with `AddControllers()` (assuming you have an API project). The difference is discussed in a bit more detail [here](https://www.strathweb.com/2020/02/asp-net-core-mvc-3-x-addmvc-addmvccore-addcontrollers-and-other-bootstrapping-approaches/).

You’ll also want to replace your `app.UseMvc()` line with:

```
    app.UseRouting();
    app.UseEndpoints(opts =>
    {
        opts.MapControllers();
    });

```

This is basically for performance reasons, and consistency with newer code samples and practices. `UseRouting()` must appear before `UseEndpoints()` to avoid an exception on startup.

* * *

### Miscellaneous

#### 14. NodeServices GONE

It looks like though a lot of developers were using `NodeServices` to invoke custom NodeJS logic from C#, the main purpose of `NodeServices` was actually for SSR of SPAs served from .NET APIs. Since the landscape of most SPAs has now changed to include command line tooling (eg: Angular, React), `NodeServices` has been removed. This is discussed in a bit more detail [here](https://github.com/dotnet/AspNetCore/issues/12890). At this stage, it looks like the simplest replacement for `NodeServices` is a 3rd-party package called [Javascript.NodeJS](https://github.com/JeringTech/Javascript.NodeJS), though it does seem to be well maintained.

The instead of injecting `INodeServices`, you inject `INodeJSService`. The API is quite similar, but the internals do have some pretty big differences. In my case, I was using a NodeJS library to render reports, so I was passing in an HTML template string and expecting back a byte array. This doesn’t play well out-of-the-box with Javascript.NodeJS, as it expects the input to be a JSON string. You can get it working, but it does take some tweaking.

* * *

#### 15. Incompatible packages

So far, only MSBump, though this has since been archived by the project maintainer anyway.

* * *

#### 16. ServiceFilter stack overflow

Quite similar to [this post](https://github.com/dotnet/aspnetcore/issues/8831), I was previously using service filters to create filters that would support controller dependencies. According to the reply from Microsoft, they recommend implementing an `IFilterFactory`, but I found that simply removing `ServiceFilterAttribute` from the attribute implementation solved the problem. i.e - simply have your attribute implement `IAsyncAuthorizationFilter` or `IAsyncActionFilter` as needed, without extending `ServiceFilterAttribute`, which was the recommended approach in .NET Core 2.1 (or at least it worked in 2.1).

#### 17. Model Binding

It looks like numbers can no longer be implicitly deserialised as strings. Eg given the following data model:

```
    public class AddressModel
    {
        [StringLength(4)]
        public string PostCode { get; set; } = "";
    }

```

and the following payload:

```
{ "postCode":2145 }

```

This results in the following 400 response:

```
{"type":"https://tools.ietf.org/html/rfc7231#section-6.5.1","title":"One or more validation errors occurred.","status":400,"traceId":"00-9d782bbecbc2107262258e79a7df080e-8d29b1c98b0ea23e-00","errors":{"$.address.postCode":["The JSON value could not be converted to System.String. Path: $.address.postCode | LineNumber: 0 | BytePositionInLine: 151."]}}

```

This looks like a side-effect of .NET Core 3 swapping out `Newtonsoft.Json` for `System.Text.Json` as the default JSON serialiser. Installing `Microsoft.AspNetCore.Mvc.NewtonsoftJson` and adding `AddNewtonsoftJson()` to your existing `AddControllers()` line in `Startup::ConfigureServices()` appears to fix this. If you were using .NET Core 2.1, Newtonsoft.Json was the default JSON serialiser then anyway, so it might make sense to keep this consistent anyway just to prevent little issues like this from popping up.

Anyway, that was pretty much it! It took me about an hour to do the upgrade and then a few hours to fix the issues that cropped up. The biggest breaking change was by far the dropping of Node Services, but there were also some Entity Framework and JSON serialisation changes to be on the look out for. Long story short, the upgrade itself doesn’t take long - but you should be prepared to spend some time catching and fixing little things that broke as a result of the upgrade.

Have you noticed anything else that broke or changed significantly between .NET Core 2.1 and .NET 6? Let me know in the comments! Have a happy new year, and see you in 2022!

Catch ya!