Migrate from ASP.NET Core 3.1 to 5.0

By Scott Addie

This article explains how to update an existing ASP.NET Core 3.1 project to ASP.NET Core 5.0.

Prerequisites

Update .NET Core SDK version in global.json

If you rely upon a global.json file to target a specific .NET Core SDK version, update the version property to the .NET 5.0 SDK version that's installed. For example:

{
  "sdk": {
-    "version": "3.1.200"
+    "version": "5.0.100"
  }
}

Update the target framework

If updating a Blazor WebAssembly project, skip to the Update Blazor WebAssembly projects section. For any other ASP.NET Core project type, update the project file's Target Framework Moniker (TFM) to net5.0:

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
  </PropertyGroup>

</Project>

Update Blazor WebAssembly and Blazor Server projects

The guidance in this section applies to both Blazor hosting models. Sections following this section provide additional guidance specific to hosting models and app types. Apply the guidance from all relevant sections to your app.

  1. In wwwroot/index.html of a Blazor WebAssembly app or the Pages/_Host.cshtml of a Blazor Server app, add a <link> element to the <head> element for styles. In the following <link> element href attribute values, the placeholder {ASSEMBLY NAME} is the app's assembly name.

    +<link href="{ASSEMBLY NAME}.styles.css" rel="stylesheet" />
    

    Standalone Blazor WebAssembly or Blazor Server example:

    +<link href="BlazorSample.styles.css" rel="stylesheet" />
    

    Client project of a hosted Blazor WebAssembly solution example:

    +<link href="BlazorSample.Client.styles.css" rel="stylesheet" />
    
  2. Include a new namespace in the app's _Imports.razor file for component virtualization, Microsoft.AspNetCore.Components.Web.Virtualization. The following _Imports.razor files show the default namespaces in apps generated from the Blazor project templates. The placeholder {ASSEMBLY NAME} is the app's assembly name.

    Blazor WebAssembly (_Imports.razor):

    @using System.Net.Http
    @using System.Net.Http.Json
    @using Microsoft.AspNetCore.Components.Forms
    @using Microsoft.AspNetCore.Components.Routing
    @using Microsoft.AspNetCore.Components.Web
    @using Microsoft.AspNetCore.Components.Web.Virtualization
    @using Microsoft.AspNetCore.Components.WebAssembly.Http
    @using Microsoft.JSInterop
    @using {ASSEMBLY NAME}
    @using {ASSEMBLY NAME}.Shared
    

    Blazor Server (_Imports.razor):

    @using System.Net.Http
    @using Microsoft.AspNetCore.Authorization
    @using Microsoft.AspNetCore.Components.Authorization
    @using Microsoft.AspNetCore.Components.Forms
    @using Microsoft.AspNetCore.Components.Routing
    @using Microsoft.AspNetCore.Components.Web
    @using Microsoft.AspNetCore.Components.Web.Virtualization
    @using Microsoft.JSInterop
    @using {ASSEMBLY NAME}
    @using {ASSEMBLY NAME}.Shared
    
  3. In the MainLayout component (Shared/MainLayout.razor), surround the component's HTML markup with a <div> element that has a class attribute set to page:

    <div class="page">
    
        ...
    
    </div>
    
  4. Add the following files to the Shared folder:

    MainLayout.razor.css:

    .page {
        position: relative;
        display: flex;
        flex-direction: column;
    }
    
    .main {
        flex: 1;
    }
    
    .sidebar {
        background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%);
    }
    
    .top-row {
        background-color: #f7f7f7;
        border-bottom: 1px solid #d6d5d5;
        justify-content: flex-end;
        height: 3.5rem;
        display: flex;
        align-items: center;
    }
    
        .top-row ::deep a, .top-row .btn-link {
            white-space: nowrap;
            margin-left: 1.5rem;
        }
    
        .top-row a:first-child {
            overflow: hidden;
            text-overflow: ellipsis;
        }
    
    @media (max-width: 767.98px) {
        .top-row:not(.auth) {
            display: none;
        }
    
        .top-row.auth {
            justify-content: space-between;
        }
    
        .top-row a, .top-row .btn-link {
            margin-left: 0;
        }
    }
    
    @media (min-width: 768px) {
        .page {
            flex-direction: row;
        }
    
        .sidebar {
            width: 250px;
            height: 100vh;
            position: sticky;
            top: 0;
        }
    
        .top-row {
            position: sticky;
            top: 0;
            z-index: 1;
        }
    
        .main > div {
            padding-left: 2rem !important;
            padding-right: 1.5rem !important;
        }
    }
    

    NavMenu.razor.css:

    .navbar-toggler {
        background-color: rgba(255, 255, 255, 0.1);
    }
    
    .top-row {
        height: 3.5rem;
        background-color: rgba(0,0,0,0.4);
    }
    
    .navbar-brand {
        font-size: 1.1rem;
    }
    
    .oi {
        width: 2rem;
        font-size: 1.1rem;
        vertical-align: text-top;
        top: -2px;
    }
    
    .nav-item {
        font-size: 0.9rem;
        padding-bottom: 0.5rem;
    }
    
        .nav-item:first-of-type {
            padding-top: 1rem;
        }
    
        .nav-item:last-of-type {
            padding-bottom: 1rem;
        }
    
        .nav-item ::deep a {
            color: #d7d7d7;
            border-radius: 4px;
            height: 3rem;
            display: flex;
            align-items: center;
            line-height: 3rem;
        }
    
    .nav-item ::deep a.active {
        background-color: rgba(255,255,255,0.25);
        color: white;
    }
    
    .nav-item ::deep a:hover {
        background-color: rgba(255,255,255,0.1);
        color: white;
    }
    
    @media (min-width: 768px) {
        .navbar-toggler {
            display: none;
        }
    
        .collapse {
            /* Never collapse the sidebar for wide screens */
            display: block;
        }
    }
    
  5. The latest base wwwroot/css/app.css file of a Blazor WebAssembly app or wwwroot/css/site.css file of a Blazor Server app includes the following styles. Remove extra styles leaving the following styles and any that you've added to the app.

    The following stylesheet only includes base styles and does not include custom styles added by the developer:

    @import url('open-iconic/font/css/open-iconic-bootstrap.min.css');
    
    html, body {
        font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
    }
    
    a, .btn-link {
        color: #0366d6;
    }
    
    .btn-primary {
        color: #fff;
        background-color: #1b6ec2;
        border-color: #1861ac;
    }
    
    .content {
        padding-top: 1.1rem;
    }
    
    .valid.modified:not([type=checkbox]) {
        outline: 1px solid #26b050;
    }
    
    .invalid {
        outline: 1px solid red;
    }
    
    .validation-message {
        color: red;
    }
    
    #blazor-error-ui {
        background: lightyellow;
        bottom: 0;
        box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
        display: none;
        left: 0;
        padding: 0.6rem 1.25rem 0.7rem 1.25rem;
        position: fixed;
        width: 100%;
        z-index: 1000;
    }
    
    #blazor-error-ui .dismiss {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }
    

Update Blazor WebAssembly projects

Follow the guidance in the preceding Update Blazor WebAssembly and Blazor Server projects section.

For a Blazor WebAssembly project, including the Client project of a hosted Blazor solution, apply the following changes to the project file:

  1. Update the SDK from Microsoft.NET.Sdk.Web to Microsoft.NET.Sdk.BlazorWebAssembly:

    - <Project Sdk="Microsoft.NET.Sdk.Web">
    + <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
    

    Note

    This update only applies to standalone Blazor WebAssembly projects and the Client projects of hosted Blazor solutions.

  2. Update the following properties:

    <Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
    
      <PropertyGroup>
    -     <TargetFramework>netstandard2.1</TargetFramework>
    -     <RazorLangVersion>3.0</RazorLangVersion>
    +     <TargetFramework>net5.0</TargetFramework>
      </PropertyGroup>
    
  3. Remove the package reference to Microsoft.AspNetCore.Components.WebAssembly.Build:

    <ItemGroup>
    -    <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Build" Version="3.2.1" PrivateAssets="all" />
    
  4. Update other packages to their latest versions. The latest versions can be found at NuGet.org.

  5. In wwwroot/index.html, change the element that loads the App component to a <div> element with an id set to app:

    -<app>Loading...</app>
    +<div id="app">Loading...</div>
    
  6. In Program.Main (Program.cs):

    • Change the reference to the <app> element to a CSS selector by adding a hash # to it.
    • Change the HttpClient registration to scoped.
    -builder.RootComponents.Add<App>("app");
    +builder.RootComponents.Add<App>("#app");
    
    -builder.Services.AddTransient(sp => new HttpClient 
    -    { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
    +builder.Services.AddScoped(sp => new HttpClient 
    +    { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
    

Standalone Blazor WebAssembly app with Microsoft Accounts

Follow the guidance in the preceding Update Blazor WebAssembly and Blazor Server projects and Update Blazor WebAssembly projects sections.

For a standalone Blazor WebAssembly app registered in the Azure portal to use Azure Active Directory (AAD) for Microsoft Accounts:

  • The app requires the openid and offline_access scopes:

    options.ProviderOptions.DefaultAccessTokenScopes.Add("openid");
    options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access");
    
  • In the Azure portal app registration Authentication blade:

    1. Remove the Web platform configuration.
    2. Add a Single-page application platform configuration with the app's redirect URI.
    3. Disable Implicit grant for Access tokens and ID tokens.

For more information, see Secure an ASP.NET Core Blazor WebAssembly standalone app with Microsoft Accounts.

Standalone Blazor WebAssembly app with Azure Active Directory (AAD)

Follow the guidance in the preceding Update Blazor WebAssembly and Blazor Server projects and Update Blazor WebAssembly projects sections.

For a standalone Blazor WebAssembly app registered in the Azure portal to use Azure Active Directory (AAD):

  • The app requires the https://graph.microsoft.com/User.Read scope:

    options.ProviderOptions.DefaultAccessTokenScopes
        .Add("https://graph.microsoft.com/User.Read");
    
  • In the Azure portal app registration Authentication blade:

    1. Remove the Web platform configuration.
    2. Add a Single-page application platform configuration with the app's redirect URI.
    3. Disable Implicit grant for Access tokens and ID tokens.

For more information, see Secure an ASP.NET Core Blazor WebAssembly standalone app with Azure Active Directory.

Standalone Blazor WebAssembly app with Azure Active Directory (AAD) B2C

Follow the guidance in the preceding Update Blazor WebAssembly and Blazor Server projects and Update Blazor WebAssembly projects sections.

For a standalone Blazor WebAssembly app registered in the Azure portal to use Azure Active Directory (AAD) B2C:

  • The app requires the openid and offline_access scopes:

    options.ProviderOptions.DefaultAccessTokenScopes.Add("openid");
    options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access");
    
  • In the Azure portal app registration Authentication blade:

    1. Remove the Web platform configuration.
    2. Add a Single-page application platform configuration with the app's redirect URI.
    3. Disable Implicit grant for Access tokens and ID tokens.

For more information, see Secure an ASP.NET Core Blazor WebAssembly standalone app with Azure Active Directory B2C.

Hosted Blazor WebAssembly app with Azure Active Directory (AAD) or AAD B2C

Follow the guidance in the preceding Update Blazor WebAssembly and Blazor Server projects and Update Blazor WebAssembly projects sections.

The Client app registration of a hosted Blazor solution that uses AAD or AAD B2C for user authentication should use a Single-page application Azure Apps platform configuration.

In the Azure portal Client app registration Authentication blade:

  1. Remove the Web platform configuration.
  2. Add a Single-page application platform configuration with the app's redirect URI.
  3. Disable Implicit grant for Access tokens and ID tokens.

For more information, see:

Update the Server project of a hosted Blazor solution

Follow the guidance in the preceding sections:

Update the Server project of a hosted Blazor solution as an ASP.NET Core app following the general guidance in this article.

Additionally, Server projects that authenticate users to client Blazor WebAssembly apps with Azure Active Directory (AAD) or B2C should adopt new Microsoft Identity v2.0 packages:

For AAD:

-<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureAD.UI" Version="..." />
+<PackageReference Include="Microsoft.Identity.Web" Version="{VERSION}" />
+<PackageReference Include="Microsoft.Identity.Web.UI" Version="{VERSION}" />

For AAD B2C:

-<PackageReference Include="Microsoft.AspNetCore.Authentication.AzureADB2C.UI" Version="..." />
+<PackageReference Include="Microsoft.Identity.Web" Version="{VERSION}" />
+<PackageReference Include="Microsoft.Identity.Web.UI" Version="{VERSION}" />

For the preceding package references, determine the package versions for the {VERSION} placeholders at NuGet.org:

Note

The SDK of the Server project in a hosted Blazor WebAssembly solution remains Microsoft.NET.Sdk.Web:

<Project Sdk="Microsoft.NET.Sdk.Web">

For more information, see:

Clean and rebuild the solution

After migrating the app or solution to .NET 5, clean and rebuild the app or solution. If package incompatibilities exist between new package references and cached packages:

  1. Clear NuGet package caches by executing the following dotnet nuget locals command in a command shell:

    dotnet nuget locals --clear all
    
  2. Clean and rebuild the app or solution.

Troubleshoot

Follow the Troubleshoot guidance at the end of the Blazor WebAssembly security topic that applies to your app:

Standalone Blazor WebAssembly apps:

Hosted Blazor WebAssembly apps:

Unauthorized client for Azure Active Directory (AAD)

After upgrading a Blazor WebAssembly app that uses AAD for authentication, you may receive the following error on the login callback to the app after the user signs in with AAD:

info: Microsoft.AspNetCore.Authorization.DefaultAuthorizationService[2] Authorization failed. These requirements were not met: DenyAnonymousAuthorizationRequirement: Requires an authenticated user.

Login callback error from AAD:

  • Error: unauthorized_client
  • Description: AADB2C90058: The provided application is not configured to allow public clients.

To resolve the error:

  1. In the Azure portal, access the app's manifest.
  2. Set the allowPublicClient attribute to null or true.

Update Razor class libraries (RCLs)

Migrate Razor class libraries (RCLs) to take advantage of new APIs or features that are introduced as part of ASP.NET Core 5.0.

To update a RCL that targets components:

  1. Update the following properties in the project file:

    <Project Sdk="Microsoft.NET.Sdk.Razor">
    
      <PropertyGroup>
    -     <TargetFramework>netstandard2.0</TargetFramework>
    -     <RazorLangVersion>3.0</RazorLangVersion>
    +     <TargetFramework>net5.0</TargetFramework>
      </PropertyGroup>
    
  2. Update other packages to their latest versions. The latest versions can be found at NuGet.org.

To update an RCL targeting MVC, update the following properties in the project file:

<Project Sdk="Microsoft.NET.Sdk.Razor">

  <PropertyGroup>
-    <TargetFramework>netcoreapp3.1</TargetFramework>
+    <TargetFramework>net5.0</TargetFramework>
    <AddRazorSupportForMvc>true</AddRazorSupportForMvc>
  </PropertyGroup>

Update package references

In the project file, update each Microsoft.AspNetCore.*, Microsoft.EntityFrameworkCore.*, Microsoft.Extensions.*, and System.Net.Http.Json package reference's Version attribute to 5.0.0 or later. For example:

<ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="3.1.6" />
-    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.6">
-    <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="3.1.6" />
-    <PackageReference Include="System.Net.Http.Json" Version="3.2.1" />
+    <PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="5.0.0" />
+    <PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="5.0.0">
+    <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
+    <PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
</ItemGroup>

Update Docker images

For apps using Docker, update your Dockerfile FROM statements and scripts. Use a base image that includes the ASP.NET Core 5.0 runtime. Consider the following docker pull command difference between ASP.NET Core 3.1 and 5.0:

- docker pull mcr.microsoft.com/dotnet/core/aspnet:3.1
+ docker pull mcr.microsoft.com/dotnet/aspnet:5.0

As part of the move to ".NET" as the product name, the Docker images moved from the mcr.microsoft.com/dotnet/core repositories to mcr.microsoft.com/dotnet. For more information, see dotnet/dotnet-docker#1939.

Model binding changes in ASP.NET Core MVC and Razor Pages

DateTime values are model bound as UTC times

In ASP.NET Core 3.1 and earlier, DateTime values were model-bound as local time, where the timezone was determined by the server. DateTime values bound from input formatting (JSON) and DateTimeOffset values were bound as UTC timezones.

In ASP.NET Core 5.0 and later, model binding consistently binds DateTime values with the UTC timezone.

To retain the previous behavior, remove the DateTimeModelBinderProvider in Startup.ConfigureServices:

services.AddControllersWithViews(options => 
    options.ModelBinderProviders.RemoveType<DateTimeModelBinderProvider>());

ComplexObjectModelBinderProvider \ ComplexObjectModelBinder replace ComplexTypeModelBinderProvider \ ComplexTypeModelBinder

To add support for model binding C# 9 record types, the ComplexTypeModelBinderProvider is:

  • Annotated as obsolete.
  • No longer registered by default.

Apps that rely on the presence of the ComplexTypeModelBinderProvider in the ModelBinderProviders collection need to reference the new binder provider:

- var complexModelBinderProvider = options.ModelBinderProviders.OfType<ComplexTypeModelBinderProvider>();
+ var complexModelBinderProvider = options.ModelBinderProviders.OfType<ComplexObjectModelBinderProvider>();

Review breaking changes

For breaking changes from .NET Core 3.1 to .NET 5.0, see Breaking changes for migration from version 3.1 to 5.0. ASP.NET Core and Entity Framework Core are also included in the list.