从 .NET 5 中的 ASP.NET Core 迁移到 .NET 6

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

先决条件

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

如果依赖 global.json 文件以特定 .NET SDK 版本为目标,请将 version 属性更新为已安装的 .NET 6 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 最小托管模型只需要一个文件和几行代码。 迁移到 .NET 6 的应用不需要使用新的最小托管模型。 有关详细信息,请参阅以下部分中迁移到 .NET 6 的应用不需要使用新的最小托管模型

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

以下代码显示 .NET 5 Web 应用模板中的 Razor 页面和 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>();
                });
    }
}

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

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();

.NET 6 示例中的上述 ASP.NET Core 演示如何:

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

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

- 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 核心模板代码中, "Microsoft": "Warning" 已更改为 "Microsoft.AspNetCore": "Warning"。 此更改会导致记录命名空间 Microsoft 中的所有信息性消息,Microsoft.AspNetCore 除外。 例如, Microsoft.EntityFrameworkCore 现在在信息级别记录。

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

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

完全支持 ASP.NET Core 3.1 和 5.0 模板中使用的 StartupGeneric Host

将 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())块被删除,因为在开发模式下,开发人员异常页中间件默认是启用的。 有关详细信息,请参阅下一部分中 .NET 5 和 .NET 6 托管模型中的 ASP.NET Core 之间的差异

使用自定义依赖项注入(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 类手动解析。

.NET 5 中的 ASP.NET Core 与 .NET 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();
  • Startup类不能在WebApplicationBuilder.HostWebApplicationBuilder.WebHost中使用。 以下突出显示的代码引发异常:

    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();
    
  • IHostBuilder上的WebApplicationBuilderWebApplicationBuilder.Host实现不会延迟执行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 被调用时。 这意味着 Service1Service2 之前被添加到 IServiceCollection,并导致 Service1IService 的解析。

在 .NET 6 中为 ASP.NET Core 构建库

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

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

我们希望库作者在生成特定于 ASP.NET Core 的组件时继续关注IHostBuilderIWebHostBuilderIApplicationBuilderIEndpointRouteBuilder。 这可确保中间件、路由处理程序或其他扩展点可以跨不同的托管模型工作。

常见问题 (FAQ)

  • 新的最小托管模型的能力是否较弱?

    否。 新的托管模型在功能上等效于 98% 支持的 IHostBuilder 方案和方案 IWebHostBuilder。 有些高级情况需要针对 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

按照本文前面的指南将应用更新到 .NET 6 后,请遵循 .NET 6 中 ASP.NET Core 中的新增功能链接,采用特定功能。

若要 为 Blazor 应用采用所有新的 6.0 功能,建议执行以下过程:

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

迁移 SPA 项目

从 SPA 扩展迁移 Angular 应用

请参阅此 GitHub 问题

从 SPA 扩展迁移 React 应用

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

更新 Docker 映像

对于使用 Docker 的应用,请更新 DockerfileFROM 语句和脚本。 使用包含 .NET 6 运行时中的 ASP.NET Core 的基本映像。 请考虑在 .NET 5 和 .NET 6 中 ASP.NET Core 之间的以下命令 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 服务器

项目模板现在使用 Duende Identity 服务器

重要

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

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

添加一个名为DbSet<Key>Keys属性到每个IdentityDbContext中,以满足更新版本IPersistedGrantDbContext的新要求。 密钥是 Duende Identity Server 存储协定的一部分所必需的。

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

注释

必须为 Duende Identity 服务器重新创建现有迁移。

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

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

查看中断性变更

参阅以下资源:

可空引用类型(Nullable Reference Types, NRT)和 .NET 编译器的 null 状态静态分析

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

.NET 编译器的 null 状态静态分析警告可以作为本地更新文档示例或示例应用的指南,也可以被忽略。 若编译器警告在学习 .NET 过程中造成困扰,我们仅建议在文档示例和示例应用中通过在应用的项目文件中设置Nullabledisable来禁用 Null 状态静态分析。 不建议在生产项目中禁用 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

其他资源