Microsoft shipped .NET Core 3.0 GA on Sep 23rd with tons of new features and improvements. This blog has been updated to .NET Core 3.0 and still runs on Microsoft Azure with SCD deployment type on App Service. This blog post summarizes all key points for my blog migration.

Starting with .NET Core 3.0 preview 8, I've been investigating migration for this blog, from .NET Core 2.2 to .NET Core 3.0. Most of the migration path can follow Microsoft official document here. But as we all know, a regular ASP.NET project would never just use APIs and packages that come from Microsoft or .NET itself. There are a lot of third party packages that may not have been updated to support .NET Core 3.0 yet. Some libraries will still run on .NET Core 3.0, but not every library can run without any issue. A typical migration for an ASP.NET Core project can be stuck on these third party packages, so please check out any new release of third party dependencies before doing migration.

I won't repeat migration steps already in Microsoft document here. Please follow everything on the official document to migrate your project to .NET Core 3.0 first. The followings are not in the document so far, but you need to be aware of. 

About Runtime and SDK

.NET Core 3.0 runtime can be installed along with previous .NET Core versions. For example, you can have 1.1, 2.1, 2.2 and 3.0 on the same machine without fighting against each other.

For SDK, starting from 3.0, the SDK can remove old versions (only 3.x) when you install a new version so that you won't have a list of old SDKs installed on your machine. For more information, please check out this blog post.

To host .NET Core 3.0 applications on a Windows Server with IIS, you still need Runtime and Hosting Bundle (ANCM module)

Visual Studio and Tooling

A lot of people asks me why .NET Core 3.0 doesn't show up in Visual Studio even when they download and installed the SDK. This is because only the latest 16.3 release of Visual Studio fully supports .NET Core 3.0. Please upgrade to version 16.3 before developing .NET Core 3.0 solutions.

Visual Studio code doesn't require special care to use .NET Core 3.0.

C# 8 and Project File

C# 8 ships with .NET Core 3.0, the current compiler supports latest C# features. I used to have these workarounds in my project file to tell the build server use C# 7.3 to compile my class library:

  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">

This isn't the case anymore. With .NET Core 3.0 SDK on build servers, I can just remove this language indicator code.

Removed ASP.NET Core Packages

If you wrote a .NET Core 2.x / Standard 2.x class library, you may use ASP.NET Core packages like:

<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />

And you won't find any 3.0 release to these packages, because it was announced removed in this GitHub issue.

But some packages still have 3.0 versions like this:

<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="3.0.0" />

So, if you can't find a 3.0 version of the package you are using, just replace them with a single FrameworkRefrence to Microsoft.AspNetCore.App. This means you can remove any other ASP.NET Core package references even they have 3.0 versions as well if they are in the same class library.

<FrameworkReference Include="Microsoft.AspNetCore.App" />

Json.NET vs System.Text.Json

I personally would prefer migrating to System.Text.Json as much as possible, in fact, there is no Json.NET in my current blog code (despite library dependencies). But there are some known limits for System.Text.Json currently that can make it impossible to replace all features on Json.NET. Such as this issue I submitted, things like this can make your code suddenly blow up and you don't know why.

Code that didn't make exceptions aren't guaranteed to be run just like old times. For example, some characters that won't be escaped in Json.NET may be escaped in System.Text.Json. It can make your API client or web front-end blow up sky-high:

var obj = new { Id = 1, BlowUp = @"Make \things blow / <up>" };
var jsonNetResult = Newtonsoft.Json.JsonConvert.SerializeObject(obj);
var systemTextJsonResult = System.Text.Json.JsonSerializer.Serialize(obj);

The result would be:


{"Id":1,"BlowUp":"Make \\things blow / <up>"}


{"Id":1,"BlowUp":"Make \\things blow / \u003Cup\u003E"}

So, please be aware of these escapes, fully test your code with enough data before releasing to production.

Not only can serializing blows up, but deserializing also got different behaviours that can make you work 996 and get into ICU.

class BlowUp
    public int Id { get; set; }

    public string Name { get; set; }

static void Main(string[] args)
    string rawJson = "{\"id\":1,\"name\":\"996ICU\"}";

    var obj1 = Newtonsoft.Json.JsonConvert.DeserializeObject<BlowUp>(rawJson);
    var obj2 = System.Text.Json.JsonSerializer.Deserialize<BlowUp>(rawJson);


Guess what's the output?

only obj1.Name is shown. Because obj2 got everything null or default!

This is because the JSON string we use is using lower case for property names. Json.NET by default automatically handles that, but System.Text.Json must use a workaround:

var obj2 = System.Text.Json.JsonSerializer.Deserialize<BlowUp>(rawJson, new JsonSerializerOptions
    PropertyNameCaseInsensitive = true

In real-world cases, ASP.NET Core web API by default return a lowercased JSON format, a lot of REST APIs besides ASP.NET Core also use lowercased naming conventions. When we consume these APIs, the new System.Text.Json can blow up sky high again.

As you can see, the new JSON API can have so many unexpected behaviors, so please please please make sure you have enough test data to cover every scenario before you migrate Json.NET to System.Text.Json.

Known Bugs

There are certain bugs that can blow up your website sky high, like this one:

My blog also use UseStatusCodePagesWithReExecute() to show custom error pages. I know a lot of people also using the same trick in their applications.

It's not so obvious to catch the bug because any requests that pointing to non-exist Controller / Action will still behave correctly and show a custom error page. 

However, returning a NotFound() result within an Action will make the Action execute twice and never hit the /error action.

When my blog meet a deleted post, it will return NotFound() and trigger the bug.

public async Task<IActionResult> Slug(int year, int month, int day, string slug)
    ViewBag.ErrorMessage = string.Empty;

    if (year > DateTime.UtcNow.Year || string.IsNullOrWhiteSpace(slug))
        Logger.LogWarning($"Invalid parameter year: {year}, slug: {slug}");
        return NotFound();

    var rsp = await _postService.GetPostAsync(year, month, day, slug);
    if (!rsp.IsSuccess) return ServerError(rsp.Message);

    var post = rsp.Item;
    if (post == null)
        Logger.LogWarning($"Post not found, parameter '{year}/{month}/{day}/{slug}'.");
        return NotFound();

    var viewModel = new PostSlugViewModelWrapper(post);

    ViewBag.TitlePrefix = $"{post.Title}";
    return View(viewModel);

There is a workaround now:

app.UseStatusCodePagesWithReExecute("/error", "?statusCode={0}");

// Workaround .NET Core 3.0 known bug
app.Use((context, next) => {
    return next();

So, when you update to .NET Core 3.0, and your code blow up, check GitHub issue first to see if there is any known issue and workaround.

Azure Application Insights Migration

Check out my previous blog post: Migrate Azure Application Insights to ASP.NET Core 3.0

Azure DevOps Blow Up

Azure DevOps build pipeline didn't have .NET Core 3.0 deployments currently. When you submit a .NET Core 3.0 project to Azure DevOps Pipeline, it won't build. The solution is to manually add the .NET Core 3.0 SDK before starting the build.

10/21/2019 Update: .NET Core 3.0 SDK is supported on windows-2019 build agents now, don't need to manually add SDK step anymore.

Azure App Service Blow Up

Azure App Service also doesn't' have .NET Core 3.0 runtime yet. You can hit an ANCM startup failure when using a default release configuration. The solution is to use SCD deployments.

If you are using Azure DevOps, modify the publish parameter, add "--self-contained -r win-x64" behind.

10/21/2019 Update: Azure App Service natively supports .NET Core 3.0 now, don't need SCD parameter anymore.


These are all the problems and tips I met when migrating this blog system to .NET Core 3.0. There are still other issues I haven't encountered before. Feel free to leave comments if you have anything to add.