从 ASP.NET Core 5.0 迁移到 6.0

本文介绍如何将现有 ASP.NET Core 5.0 项目更新为 ASP.NET Core 6.0。 有关如何从 ASP.NET Core 3.1 迁移到 ASP.NET Core 6.0 的说明,请参阅从 ASP.NET Core 3.1 迁移到 6.0

先决条件

更新 global.json 中的 .NET SDK 版本

如果依靠 global.json 文件来定向于特定 .NET SDK 版本,请将 version 属性更新为已安装的 .NET 6.0 SDK 版本。 例如:

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

更新目标框架

将项目文件的目标框架名字对象 (TFM) 更新为 net6.0

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

  <PropertyGroup>
-    <TargetFramework>net5.0</TargetFramework>
+    <TargetFramework>net6.0</TargetFramework>
  </PropertyGroup>

</Project>

更新包引用

在项目文件中,将每个 Microsoft.AspNetCore.*Microsoft.Extensions.* 包引用的 Version 特性更新为 6.0.0 或更高版本。 例如:

<ItemGroup>
-    <PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="5.0.3" />
-    <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="5.0.0" />
+    <PackageReference Include="Microsoft.AspNetCore.JsonPatch" Version="6.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Caching.Abstractions" Version="6.0.0" />
</ItemGroup>

新托管模型

适用于 ASP.NET Core 的新的 .NET 6 最小托管模型只需要一个文件和几行代码。 迁移到 6.0 的应用不需要使用新的最小托管模型。 有关详细信息,请参阅以下部分中的迁移到 6.0 的应用不需要使用新的最小托管模型

ASP.NET Core 空模板中的以下代码使用新的最小托管模型创建应用:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

最小托管模型:

  • 显著减少创建应用所需的文件和代码行数。 只需要一个文件和四行代码。
  • Startup.csProgram.cs 统一到单个 Program.cs 文件中。
  • 使用顶级语句最大程度减少应用所需的代码。
  • 使用全局 using 指令消除或最大程度减少所需的 using 语句行数。

以下代码显示 ASP.NET Core 5 Web 应用模板 (Razor Pages) 中的 Startup.csProgram.cs 文件,其中删除了未使用的 using 语句:

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
// Unused usings removed.

namespace WebAppRPv5
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddRazorPages();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Error");
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapRazorPages();
            });
        }
    }
}
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
// Unused usings removed.

namespace WebAppRPv5
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}

在 ASP.NET Core 6 中,上面的代码替换为以下内容:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();

var app = builder.Build();

// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();

上面的 ASP.NET Core 6 示例演示了以下操作方式:

本文档后面会提供使用最小托管模型将 ASP.NET Core 5 Startup 代码迁移到 ASP.NET Core 6 的详细示例。

对为 Web 应用模板生成的其他文件进行了几处更改:

  • Index.cshtmlPrivacy.cshtml 删除了未使用的 using 语句。
  • Error.cshtml 中的 RequestId 声明为空引用类型 (NRT)
- public string RequestId { get; set; }
+ public string? RequestId { get; set; }
  • appsettings.jsonappsettings.Development.json中的日志级别默认值已更改:
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
+ "Microsoft.AspNetCore": "Warning"

在前面的 ASP.NET Core 模板代码中,"Microsoft": "Warning"已更改为"Microsoft.AspNetCore": "Warning"。 此更改导致记录 Microsoft 命名空间中 Microsoft.AspNetCore 以外的所有信息性消息。 例如,Microsoft.EntityFrameworkCore 现记录在信息级别。

有关新托管模型的更多详细信息,请参阅常见问题解答部分。 有关采用 NRT 和 .NET 编译器 Null 状态分析的详细信息,请参阅空引用类型 (NRT) 和 .NET 编译器 Null 状态静态分析部分。

迁移到或使用 6.0 及更高版本的应用不需要使用新的最小托管模型

完全支持使用 Startup 以及由 ASP.NET Core 3.1 和 5.0 模板使用的通用主机

将 Startup 与新的最小托管模型结合使用

ASP.NET Core 3.1 和 5.0 应用可以将其 Startup 代码与新的最小托管模型结合使用。 将Startup与最小托管模型结合使用具有以下优点:

  • 没有隐藏反射用于调用Startup类。
  • 可以编写异步代码,因为开发人员控制对Startup的调用。
  • 可以编写交错ConfigureServicesConfigure的代码。

Startup代码与新的最小托管模型结合使用的一个次要限制是,要将依赖项注入到Configure中,必须手动解析Program.cs中的服务。

请考虑由 ASP.NET Core 3.1 或 5.0 Razor Pages 模板生成的代码:

public class Program
{
    public static void Main(string[] args)
    {
        CreateHostBuilder(args).Build().Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

上面的代码已迁移到新的最小托管模型:

using Microsoft.AspNetCore.Builder;

var builder = WebApplication.CreateBuilder(args);

var startup = new Startup(builder.Configuration);

startup.ConfigureServices(builder.Services);

var app = builder.Build();

startup.Configure(app, app.Environment);

app.Run();
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (!env.IsDevelopment())
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

在上面的代码中,删除了 if (env.IsDevelopment()) 程序块,因为在开发模式下,开发人员异常页中间件在默认情况下处于启用状态。 有关详细信息,请参阅下一部分中的 ASP.NET Core 5 与 6 托管模型之间的差异

使用自定义依赖项注入 (DI) 容器时,请添加以下突出显示的代码:

using Autofac;
using Autofac.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.Hosting;

var builder = WebApplication.CreateBuilder(args);

var startup = new Startup(builder.Configuration);

startup.ConfigureServices(builder.Services);

// Using a custom DI container.
builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(startup.ConfigureContainer);

var app = builder.Build();

startup.Configure(app, app.Environment);

app.Run();
using Autofac;
public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    }

    //  Using a custom DI container
    public void ConfigureContainer(ContainerBuilder builder)
    {
        // Configure custom container.
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (!env.IsDevelopment())
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapRazorPages();
        });
    }
}

使用最小托管模型时,终结点路由中间件会包装整个中间件管道,因此无需显式调用 UseRoutingUseEndpoints 来注册路由。 UseRouting 仍可用于指定进行路由匹配的位置,但如果应在中间件管道开头匹配路由,则无需显式调用 UseRouting

在下面的代码中,从 Startup 中删除了对 UseRoutingUseEndpoints 的调用。 MapRazorPagesProgram.cs中调用:

public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddRazorPages();
    }

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        if (!env.IsDevelopment())
        {
            app.UseExceptionHandler("/Error");
            app.UseHsts();
        }

        app.UseHttpsRedirection();
        app.UseStaticFiles();
        //app.UseRouting();

        //app.UseEndpoints(endpoints =>
        //{
        //    endpoints.MapRazorPages();
        //});
    }
}
using Microsoft.AspNetCore.Builder;

var builder = WebApplication.CreateBuilder(args);

var startup = new Startup(builder.Configuration);

startup.ConfigureServices(builder.Services);

var app = builder.Build();

startup.Configure(app, app.Environment);

app.MapRazorPages();

app.Run();

Startup 与新的最小托管模型结合使用时,请记住以下差异:

  • Program.cs控制Startup类的实例化和生存期。
  • 注入 Configure 方法中的其他任何服务都需要通过 Program 类手动解析。

ASP.NET Core 5 与 6 托管模型之间的差异

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

// WebHost

try
{
    builder.WebHost.UseContentRoot(Directory.GetCurrentDirectory());
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

try
{
    builder.WebHost.UseEnvironment(Environments.Staging);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

try
{
    builder.WebHost.UseSetting(WebHostDefaults.ApplicationKey, "ApplicationName2");
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

try
{
    builder.WebHost.UseSetting(WebHostDefaults.ContentRootKey, Directory.GetCurrentDirectory());
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

try
{
    builder.WebHost.UseSetting(WebHostDefaults.EnvironmentKey, Environments.Staging);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

// Host
try
{
    builder.Host.UseEnvironment(Environments.Staging);
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

try
{
    // TODO: This does not throw
    builder.Host.UseContentRoot(Directory.GetCurrentDirectory());
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapRazorPages();

app.Run();
  • 无法从 WebApplicationBuilder.HostWebApplicationBuilder.WebHost 使用 Startup 类。 以下突出显示的代码会引发异常:

    var builder = WebApplication.CreateBuilder(args);
    
    try
    {
        builder.Host.ConfigureWebHostDefaults(webHostBuilder =>
        {
            webHostBuilder.UseStartup<Startup>();
        });
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;    
    }
    
    builder.Services.AddRazorPages();
    
    var app = builder.Build();
    
    var builder = WebApplication.CreateBuilder(args);
    
    try
    {
        builder.WebHost.UseStartup<Startup>();
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        throw;    
    }
    
    builder.Services.AddRazorPages();
    
    var app = builder.Build();
    
  • WebApplicationBuilder (WebApplicationBuilder.Host) 上的 IHostBuilder 实现不会延迟 ConfigureServicesConfigureAppConfigurationConfigureHostConfiguration 方法的执行。 不延迟执行使得使用 WebApplicationBuilder 的代码可以观察到对 IServiceCollectionIConfiguration 进行的更改。 下面的示例仅添加 Service1 作为 IService

    using Microsoft.Extensions.DependencyInjection.Extensions;
    
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Host.ConfigureServices(services =>
    {
        services.TryAddSingleton<IService, Service1>();
    });
    
    builder.Services.TryAddSingleton<IService, Service2>();
    
    var app = builder.Build();
    
    // Displays Service1 only.
    Console.WriteLine(app.Services.GetRequiredService<IService>());
    
    app.Run();
    
    class Service1 : IService
    {
    }
    
    class Service2 : IService
    {
    }
    
    interface IService
    {
    }
    

在上面的代码中,builder.Host.ConfigureServices 回调进行内联调用,而不是延迟到调用了 builder.Build。 这意味着 Service1 会在 Service2 之前添加到 IServiceCollection,从而导致为 IService 解析 Service1

为 ASP.NET Core 6 生成库

现有 .NET 生态系统围绕 IServiceCollectionIHostBuilderIWebHostBuilder 构建扩展性。 这些属性在 WebApplicationBuilder 上作为 ServicesHostWebHost 来提供。

WebApplication 实现了 Microsoft.AspNetCore.Builder.IApplicationBuilderMicrosoft.AspNetCore.Routing.IEndpointRouteBuilder

生成 ASP.NET Core 特定组件时,我们期望库创建者继续面向 IHostBuilderIWebHostBuilderIApplicationBuilderIEndpointRouteBuilder。 这可确保中间件、路由处理程序或其他扩展点可继续跨不同的托管模型正常工作。

常见问题解答 (FAQ)

  • 新的最小托管模型是否减少了功能?

    不是。 新的托管模型在功能上等效于 IHostBuilderIWebHostBuilder 支持的 98% 的方案。 有一些高级方案需要对 IHostBuilder 采用特定解决方法,但我们预计这种情况非常罕见。

  • 通用托管模型是否已弃用?

    不是。 通用托管模型是受到无限支持的替代模型。 通用主机支持新的托管模型,仍是托管基于辅助角色的应用程序的主要方式。

  • 是否必须迁移到新的托管模型?

    不是。 新的托管模型是使用 .NET 6 和更高版本托管新应用的首选方法,但不强制更改现有应用中的项目布局。 这意味着应用可以通过将项目文件中的目标框架从 net5.0 更改为 net6.0,从 .NET 5 升级到 .NET 6。 有关详细信息,请参阅本文中的更新目标框架部分。 但是,建议将应用迁移到新的托管模型,以利用仅适用于新的托管模型的新功能。

  • 是否必须使用顶级语句?

    不是。 新项目模板全都使用顶级语句,不过可以在任何 .NET 6 应用中使用新的托管 API 来托管 Web 服务器或 Web 应用。

  • 将存储为字段的状态放置在 ProgramStartup 类中的哪个位置?

    强烈建议在 ASP.NET Core 应用中使用依赖项注入 (DI) 来流式传输状态。

    有两种方法可用于在 DI 外部存储状态:

    • 将状态存储在其他类中。 存储在类中会采用可以从应用中的任何位置访问的静态状态。

    • 使用由顶级语句生成的 Program 类存储状态。 使用 Program 存储状态是语义方法:

      var builder = WebApplication.CreateBuilder(args);
      
      ConfigurationValue = builder.Configuration["SomeKey"] ?? "Hello";
      
      var app = builder.Build();
      
      app.MapGet("/", () => ConfigurationValue);
      
      app.Run();
      
      partial class Program
      {
          public static string? ConfigurationValue { get; private set; }
      }
      
  • 项,该?

    支持自定义 DI 容器。 有关示例,请参阅自定义依赖项注入 (DI) 容器

  • WebApplicationFactoryTestServer 是否仍可正常工作?

    是。 WebApplicationFactory<TEntryPoint> 是测试新托管模型的方法。 有关示例,请参见使用 WebApplicationFactoryTestServer 进行测试

Blazor

按照本文前面所述的指导将应用更新到 6.0 后,请按照 ASP.NET Core 6.0 的新增功能中的链接采用特定功能。

为 Blazor 应用采用所有新的 6.0 功能,我们建议使用以下过程:

  • 通过一个 Blazor 项目模板创建新的 6.0 Blazor 项目。 有关详细信息,请参阅用于 ASP.NET Core Blazor 的工具
  • 将应用的组件和代码移动到 6.0 应用,进行修改以采用新的 6.0 功能。

迁移 SPA 项目

从 SPA 扩展迁移 Angular 应用

请参阅此 GitHub 问题

从 SPA 扩展迁移 React 应用

请参阅此 GitHub 问题中的从 Spa 扩展迁移React 应用程序

更新 Docker 映像

对于使用 Docker 的应用,请更新Dockerfile FROM 语句和脚本。 使用包含 ASP.NET Core 6.0 运行时的基础映像。 请考虑 ASP.NET Core 5.0 和 6.0 之间的以下 docker pull 命令差异:

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

请参阅 GitHub 问题中断性变更:默认控制台记录器格式设置为 JSON

对 ASP.NET Core Razor SDK 的更改

Razor 编译器现在利用新的源生成器功能从项目中的 Razor 视图和页面生成编译的 C# 文件。 在以前的版本中:

  • 编译依赖于 RazorGenerateRazorCompile 目标来生成生成的代码。 这些目标不再有效。 在 .NET 6 中,对编译器的单个调用支持代码生成和编译。 RazorComponentGenerateDependsOn 仍受支持,可指定生成运行之前所需的依赖项。
  • 生成了单独的 Razor 程序集 (AppName.Views.dll),其中包含应用程序中编译的视图类型。 此行为已弃用,会生成单个程序集 AppName.dll,其中包含应用类型和生成的视图。
  • AppName.Views.dll 中的应用类型是公共的。 在 .NET 6 中,应用类型处于 AppName.dll 中,不过是 internal sealed。 对 AppName.Views.dll 进行类型发现的应用无法对 AppName.dll 进行类型发现。 下面显示了 API 更改:
- public class Views_Home_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>
+ internal sealed class Views_Home_Index : global::Microsoft.AspNetCore.Mvc.Razor.RazorPage<dynamic>

进行以下更改:

  • 以下属性不再适用于单步编译模型。
    • RazorTargetAssemblyAttribute
    • RazorTargetName
    • EnableDefaultRazorTargetAssemblyInfoAttributes
    • UseRazorBuildServer
    • GenerateRazorTargetAssemblyInfo
    • GenerateMvcApplicationPartsAssemblyAttributes

有关详细信息,请参阅Razor编译器不再生成 Views 程序集

项目模板使用 Duende Identity Server

项目模板使用Duende Identity Server。 有关迁移指南,请参阅 IdentityServer4 v4.1 到 Duende IdentityServer v5

重要

Duende Identity Server 是具有互惠许可协议的开放源代码产品。 如果计划在生产中使用 Duende Identity Server,则可能需要从 Duende Software 获取商业许可证并支付许可证费用。 有关详细信息,请参阅 Duende Software:许可证

要了解如何为 ASP.NET CoreIdentity使用Microsoft Azure Active Directory,请参阅Identity(dotnet/aspnetcore GitHub 存储库)

将名为 KeysDbSet<Key> 属性添加到每个 IdentityDbContext,以满足 IPersistedGrantDbContext 的更新版本中的新要求。 密钥需要包含在与 Duende Identity Server 存储的协定中。

public DbSet<Key> Keys { get; set; }

注意

必须为 Duende Identity Server 重新创建现有迁移。

迁移到 ASP.NET Core 6.0 的代码示例

迁移到 6.0 中新的最小托管模型的代码示例

查看中断性变更

请参阅以下资源:

空引用类型 (NRT) 和 .NET 编译器 Null 状态静态分析

ASP.NET Core 项目模板使用空引用类型 (NRT),.NET 编译器会执行 Null 状态静态分析。 这些功能随 C# 8 一起发布,默认已为使用 ASP.NET Core 6.0 (C# 10) 或更高版本生成的应用启用。

.NET 编译器的 Null 状态静态分析警告可充当本地更新文档示例或示例应用的指示,也可忽略。 可以通过在应用的项目文件中Nullable 设置为 disable 来禁用 Null 状态静态分析,建议仅将其用于文档示例和示例应用(如果编译器警告在你了解 .NET 时会分散你的注意力)。 不建议在生产项目中禁用 Null 状态检查。

有关 NRT、MSBuild Nullable 属性和更新应用(包括 #pragma 指南)的详细信息,请参阅 C# 文档中的以下资源:

ASP.NET Core 模块 (ANCM)

如果在安装 Visual Studio 时未选择 ASP.NET Core 模块 (ANCM) 组件,或者系统上安装了 ANCM 的早期版本,请下载最新的 .NET Core 托管捆绑包安装程序(直接下载)并运行该安装程序。 有关详细信息,请参阅托管捆绑包

应用程序名称更改

在 .NET 6 中,WebApplicationBuilder 会将内容根路径规范化以 DirectorySeparatorChar 结尾。 大多数从HostBuilderWebHostBuilder迁移的应用不会共享相同的应用名称,因为它们没有规范化。 有关详细信息,请参阅SetApplicationName

其他资源