本文介绍如何将 .NET 5 项目中的现有 ASP.NET Core 更新为 .NET 6。 有关如何从 ASP.NET Core 3.1 迁移到 .NET 6 的说明,请参阅 从 ASP.NET Core 3.1 迁移到 .NET 6。
先决条件
- 带有 ASP.NET 和 Web 开发工作负载的 Visual Studio 2022。
- .NET 6 SDK
更新 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.cs和Program.cs到单个Program.cs文件中。 - 使用 顶级语句 将应用所需的代码降到最低。
- 使用全局
using指令消除或最小化所需的语句行数using。
以下代码显示 .NET 5 Web 应用模板中的 Startup.cs 页面和 Program.cs 及 Razor 文件,并删除了未使用的 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 演示如何:
-
ConfigureServices 替换为
WebApplication.Services。 -
builder.Build()将已配置的 WebApplication 返回给变量app。 Configure 被替换为对使用app的同一服务的配置调用。
本文档后面提供了使用最小托管模型将 .NET 5 Startup 代码中的 ASP.NET Core 迁移到 .NET 6 的详细示例。
对为 Web 应用模板生成的其他文件进行了一些更改:
- 已删除
Index.cshtml和Privacy.cshtml中的未使用using语句。 -
RequestIdinError.cshtml被声明为可为 null 的引用类型(NRT)
- public string RequestId { get; set; }
+ public string? RequestId { get; set; }
- 日志级别默认值在
appsettings.json和appsettings.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 模板中使用的 Startup 和 Generic Host。
将 Startup 与新的最小托管模型配合使用
ASP.NET Core 3.1 和 5.0 应用可以使用其 Startup 代码与新的最小托管模型。 在最少的托管模型中使用 Startup 具有以下优势:
- 没有使用隐藏的反射来调用
Startup类。 - 可以编写异步代码,因为开发人员控制对的
Startup调用。 - 可以编写代码以交错
ConfigureServices和Configure。
使用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();
});
}
}
使用最小托管模型时,终结点路由中间件会包装整个中间件管道,因此无需显式调用 UseRouting 或 UseEndpoints 注册路由。
UseRouting 仍可用于指定路由匹配的发生位置,但如果中间件管道的开头应匹配路由,则无需显式调用 UseRouting。
在以下代码中,UseRouting 中的 UseEndpoints 和 Startup 的调用被移除。
MapRazorPages在Program.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 托管模型之间的差异
- 在 开发模式下,默认情况下启用开发人员异常页中间件。
- 应用名称默认为入口点程序集的名称:
Assembly.GetEntryAssembly().GetName().FullName。 在库中使用 WebApplicationBuilder 时,明确地将应用程序的名称更改为库的程序集,以便 MVC 的应用程序部件发现功能可以正常工作。 有关详细说明,请参阅本文档中的 更改内容根、应用名称和环境 。 - 终结点路由中间件包装整个中间件管道,因此无需显式调用
UseRouting或UseEndpoints注册路由。UseRouting仍可用于指定路由匹配的发生位置,但如果中间件管道的开头应匹配路由,则无需显式调用UseRouting。 -
管道在任何IStartupFilter运行之前创建,因此,在构建管道时产生的异常对
IStartupFilter调用链是不可见的。 - 某些工具(如 EF 迁移)使用
Program.CreateHostBuilder访问应用的IServiceProvider上下文以执行自定义逻辑。 这些工具已更新为使用新技术在应用上下文中执行自定义逻辑。 实体框架迁移 是一个以这种方式使用Program.CreateHostBuilder的工具示例。 我们正在努力确保更新工具以使用新模型。 -
Startup与类不同,在实例化服务提供商时,最小主机不会自动配置 DI 范围。 对于需要作用域的上下文,必须使用 IServiceScope 调用以实例化新范围。 有关详细信息,请参阅 如何在应用启动时解析服务。 -
无法在创建后WebApplicationBuilder。 有关更改主机设置的详细说明,请参阅 “自定义
IHostBuilder”或IWebHostBuilder。 以下突出显示的 API 引发异常:
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.Host或WebApplicationBuilder.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上的WebApplicationBuilder
WebApplicationBuilder.Host实现不会延迟执行ConfigureServices或ConfigureAppConfigurationConfigureHostConfiguration方法。 不延迟执行允许代码使用WebApplicationBuilder观察对IServiceCollection和IConfiguration所做的更改。 以下示例仅将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 在 IServiceCollection 之前被添加到 Service2,并导致 Service1 对 IService 的解析。
在 .NET 6 中为 ASP.NET Core 构建库
现有的 .NET 生态系统围绕 IServiceCollection、IHostBuilder 和 IWebHostBuilder 构建扩展性。 这些属性在WebApplicationBuilder上作为Services、Host和WebHost可用。
WebApplication实现了Microsoft.AspNetCore.Builder.IApplicationBuilder和Microsoft.AspNetCore.Routing.IEndpointRouteBuilder.
我们希望库作者在生成特定于 ASP.NET Core 的组件时继续关注IHostBuilder、IWebHostBuilder、IApplicationBuilder和IEndpointRouteBuilder。 这可确保中间件、路由处理程序或其他扩展点可以跨不同的托管模型工作。
常见问题 (FAQ)
新的最小托管模型的能力是否较弱?
否。 新的托管模型在功能上等效于 98% 支持的
IHostBuilder方案和方案IWebHostBuilder。 有些高级情况需要针对IHostBuilder的特定解决方案,但我们预计这些情况极为罕见。泛型托管模型是否弃用?
否。 泛型托管模型是无限期支持的替代模型。 泛型主机支撑着新的托管模型,仍然是托管基于辅助角色的应用程序的主要方法。
是否必须迁移到新的托管模型?
否。 新的托管模型是使用 .NET 6 或更高版本托管新应用的首选方法,但你不会被迫在现有应用中更改项目布局。 这意味着应用可以通过将项目文件中的目标框架从
net5.0更改为net6.0,来从 .NET 5 升级到 .NET 6。 有关详细信息,请参阅本文中的 “更新目标框架 ”部分。 但是,我们建议应用迁移到新的托管模型,以利用仅适用于新托管模型的新功能。我是否需要使用顶级语句?
否。 新项目模板均使用 顶级语句,但可在任何 .NET 6 应用中使用新的托管 API 来托管 Web 服务器或 Web 应用。
我应该把
Program或Startup类中存储为字段的状态放在哪?我们强烈建议在 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)容器。
WebApplicationFactory和TestServer还有效吗?是的。
WebApplicationFactory<TEntryPoint>是测试新托管模型的方法。 有关示例,请参阅测试WebApplicationFactory或TestServer。
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# 文件。 在以前的版本中:
- 编译依赖于
RazorGenerate和RazorCompile目标以生成代码。 这些目标不再有效。 在 .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>
进行以下更改:
- 以下属性不再适用于单步编译模型。
RazorTargetAssemblyAttributeRazorTargetNameEnableDefaultRazorTargetAssemblyInfoAttributesUseRazorBuildServerGenerateRazorTargetAssemblyInfoGenerateMvcApplicationPartsAssemblyAttributes
有关详细信息,请参阅 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 的代码示例
重大变化
使用 .NET 中重大更改 的文章查找在将应用升级到较新版本 .NET 时可能应用的中断性变更。
有关详细信息,请参阅 公告 GitHub 存储库(aspnet/Announcements标签 6.0.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 过程中造成困扰,我们仅建议在文档示例和示例应用中通过在应用的项目文件中设置Nullable为disable来禁用 Null 状态静态分析。
不建议在生产项目中禁用 null 状态检查。
有关 NRT、MSBuild Nullable 属性和更新应用(包括 #pragma 指南)的详细信息,请参阅 C# 文档中的以下资源:
- 可为空引用类型
- 可空引用类型 (C# 参考)
- 了解用于解析可为空警告的方法
- 使用可为 Null 的引用类型更新代码库以改进 null 诊断警告
- null 状态静态分析的属性
- ! (null 包容)运算符(C# 参考)
ASP.NET Core 模块 (ANCM)
如果在安装 Visual Studio 时未选择 ASP.NET Core 模块 (ANCM) 组件,或者系统上安装了 ANCM 的早期版本,请下载最新的 .NET Core 托管捆绑包安装程序(直接下载)并运行该安装程序。 有关详细信息,请参阅托管捆绑包。
应用程序名称更改
在 .NET 6 中,WebApplicationBuilder 会将内容根路径规范化以 DirectorySeparatorChar 结尾。 大多数从 HostBuilder 或 WebHostBuilder 迁移的应用不会具有相同的名称,因为它们没有规范化。 有关详细信息,请参阅 SetApplicationName