We’ve used ASP.NET Core since version 2 released, and while it made development a breeze, it wasn’t always clear how things worked, just that they did. In the midst of trying to fix all the breaking changes ASP.NET Core 3 introduced, we stumbled upon this GitHub issue, with a reply from divega, stating “From feedback we have received over the years, we know that users expect magic […]”.

With 3.0, there’s less “magic”, which is a welcoming change. It means that we must be more explicit with our actions and more understanding of what’s happening under the hood.

Please note that there is an official migration guide here. The official migration guide was sparse when we started our migration process, but it has been updated extensively since. This guide will instead cover the problems we encountered, hopefully making the process easier for others in the future.

Initial Setup

Project Files (.CSPROJ)

In the solution’s project files, change the TargetFramework from e.g. netstandard 2.0 or netcoreapp2.2 to netstandard 2.1 and netcoreapp3.0, respectively.

Next, update all packages via the Package Manager. For simple class libraries, that’s all there is to it. Migration complete! For larger ASP.NET Core applications, however, there’s a bunch of stuff that needs fixing. We’ll cover some of it in the following sections.


Enabling nullable reference types is a very good idea to have more explicit code, and will help you avoid potential null reference exceptions. Simply add <Nullable>enable</Nullable> in a property group (as seen in the code snippet above).

Be very careful where you decide to use the null-forgiving operator. If you have an API which for instance creates a user, and you use the null-forgiving operator on last name, then last name is required in the JSON body, otherwise a 400 Bad Request is thrown automatically.

By using the null-forgiving operator, you either tell the compiler that passing null is expected, or that an expression will never be null (which is what the code snippet above states). If there are properties that a class is required to have instantiated, set them in the constructor instead.

Enable Entity Framework Core Functionality

The .NET Core 3.0 SDK is included in Visual Studio 2019 v16.3 and later, but if you want to take advantage of all dotnet-ef commands (such as migrations) you will have to install it separately as well, as mentioned in the ef-core documentation. With 3.0 you can decide whether you want the tools to be installed globally or locally with the –global parameter.

We did, however, run into an issue where it attempted to install from a private NuGet feed:

To solve this issue, head into Visual Studio, NuGet Package Solutions, Package Source Options, and uncheck the private feed it complains about. Re-run the command and it should install successfully.

Please note that you will have to install Microsoft.EntityFrameworkCore.Design in your project to use the commands.



HttpMethod now also supports Patch, so you can use it without defining a new HttpMethod.

Startup.cs Ordering

The ordering in-between UseRouting and UseEndpoints is crucial. For instance, placing UseAuthorization before UseAuthentication will cause Unauthorized to be thrown everywhere. Make sure it’s defined in the following order.

Eliminate “Using” Brackets

Simplify the using statements by removing the brackets, by going from this

To this

Of course, if there are multiple contexts within the same test, or it lessens readability, keep the brackets.



You’ll want to target v3.0. Easy to forget, but also easy to fix.

We encountered an issue with globalization, covered in-depth in dotnet corefx, as well as in the dotnet docker samples.

The proposed solution is to install the required ICU package and reset the DOTNET_SYSTEM_GLOBALIZATION_INVARIANT variable.

However, this didn’t fully work for us. The root cause was that we were using System.Text.Encoding and System.Text.Encoding.CodePages for some operations. We decided to refactor our code such that it no longer referenced these packages, which made the error disappear.

Azure Pipelines

The first step in the build process should install the .NET Core 3.0 SDK.

Integration Tests

When all the compilation issues were resolved, we rebuilt the solution, ran the tests, and saw all our integration tests failing, resulting in a HTTP response with 404 Not Found.

Note that we are using a CustomWebApplicationFactory for our integration tests.

The cause could be an assembly problem, which can be solved in either of two ways.

Option 1: Altering Startup.cs

Add AddApplicationPart with Startup’s assembly in ConfigureServices to solve the problem.

Option 2: Altering the test project

Make sure you’ve installed Microsoft.AspNetCore.Mvc.Testing, and that the test project’s csproj-file is referring to SDK Microsoft.NET.Sdk.Web. In our case, it was previously referring to Microsoft.NET.Sdk.

Please see the next code snippet for definitions (the first line describes which SDK to use).


We’re still sticking with NewtonsoftJson for now, as Microsoft’s JSON library broke all our PATCH and POST operations, and still has a long road ahead of itself before it can handle the operations we require it to.

The change is simple, replace AddJsonOptions with AddNewtonSoftJson. JsonOptions is now referring to Microsoft.AspNetCore.Mvc.JsonOptions.

Also make sure that Microsoft.AspNetCore.JsonPatch, and Microsoft.AspNetCore.Mvc.NewtonsoftJson is installed.

The new way is to inject IHttpContextAccessor. Note that some code has been removed for brevity.


If you’re implementing an AuthorizationHandler, there are some important changes to be made.

Prior, we had to add a reference to Microsoft.AspNetCore.Mvc.Filters to get the HttpContext.


Plenty of packages have been removed from ASP.NET Core.

One of which is Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore, allowing us to see exceptions thrown by the database. Feel free to add it back to help speed up the development debugging process, and use it by calling

The new way is to inject IHttpContextAccessor. Note that some code has been removed for brevity.


As of ASP.NET Core 3.0, Microsoft has made a big change regarding LINQ and client evaluation. For specific details, please see this blog post, or this section regarding breaking changes.

In short, the LINQ queries were translated to SQL, and those that couldn’t be translated, were run on the client. The fact that this was default is terrible, and plenty of people were unaware of this, including us.

Thankfully, ASP.NET Core 3.0 made this client evaluation opt-in, instead of default, which exposes the problems clearly, and allows us to fix the root cause instead of loading everything onto the client without knowing it.

LINQ & Composite Keys

In ASP.NET Core 2.2 with client evaluation, this would be a valid operation.

In ASP.NET Core 3.0, make sure that you order by the key as well when using composite keys.

This slightly more complex operation was performed on the client previously.

ith client evaluation, this would be a valid operation.

Run it without client evaluation, and this error will appear.

Good! We’re now getting an error telling us the problem, instead of trying to fix it without us knowing about it. Let’s rewrite it so we do not pull everything onto the client, but instead just what we need in order to handle the problem at hand.


Probably the most time-consuming activity was to set make sure our database setup was correct.

This involves removing cycles (i.e. properly defining the parent in the relationship), mapping correct relationships (one-to-one, one-to-many, many-to-many), managing composite keys, non-duplicate column names with the expected name, etc.

It’s worth mentioning that the service we’re currently building has not yet released, which allowed us to quickly do the changes without worrying about migrating existing data.

One of the exceptions we ran into is the following one, where the parent is undetermined.

The following code snippets show an AdSetModel and an AdSetBudgetModel as they were defined before and after the migration process. Please note that code has been omitted for the sake of brevity. Before, we said that an AdSetBudget has an AdSet (i.e. that the AdSetBudget is the parent in the relationship). That doesn’t make sense and is certainly something we overlooked in the moment. It compiled, it built, and we didn’t experience any issues with it in ASP.NET Core 2.2. Here’s the code.

When upgrading, we got the InvalidOperationException as mentioned at the start of this section, which made us realize our mistake. We want to say that an AdSet has one AdSetBudget, and then use the foreign key as defined in the AdSet, as seen in the next code snippet.

It’s also crucial that if you’re mapping an integer to another, that they’re defined in the correct order. It will throw an error if for instance a string is defined where an int should’ve been, but it doesn’t warn you if you’re for instance using CampaignId in place for AdSetId.

The error above would look something like this.

There are lots of smaller intricacies regarding the DbContext that we encountered. Specifying the problem and solution for all of them is quite the time-consuming activity, but hopefully this section can guide you in the right direction.


There you have it! Our experience with the migration process was that for smaller projects it was a breeze. For our larger API solutions, however, it required a lot of effort. In the end, the direction where Microsoft is taking ASP.NET Core seems to rely less on magic, and more about being explicit with one’s actions.

Vil du høre mer om hva vi holder på med?